@targlobal/mission-control 1.2.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 ADDED
@@ -0,0 +1,1793 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import Table from 'cli-table3';
7
+ import inquirer from 'inquirer';
8
+ import { getConfig, setConfig, isAuthenticated, clearConfig, setUser, getUser } from './config';
9
+ import { api, Task, Board, Payout, PayoutMetrics, resetApiClient } from './api';
10
+ import { execSync, spawn } from 'child_process';
11
+
12
+ const VERSION = '1.2.0';
13
+ const program = new Command();
14
+
15
+ // Set terminal title
16
+ const setTitle = (context: string) => {
17
+ process.stdout.write(`\x1b]0;MC | ${context}\x07`);
18
+ };
19
+
20
+ // ASCII Art Logo
21
+ const logo = `
22
+ ${chalk.bold.white('███╗ ███╗ ██╗ ███████╗ ███████╗ ██╗ ██████╗ ███╗ ██╗')}
23
+ ${chalk.bold.white('████╗ ████║ ██║ ██╔════╝ ██╔════╝ ██║ ██╔═══██╗ ████╗ ██║')}
24
+ ${chalk.bold.white('██╔████╔██║ ██║ ███████╗ ███████╗ ██║ ██║ ██║ ██╔██╗ ██║')}
25
+ ${chalk.bold.white('██║╚██╔╝██║ ██║ ╚════██║ ╚════██║ ██║ ██║ ██║ ██║╚██╗██║')}
26
+ ${chalk.bold.white('██║ ╚═╝ ██║ ██║ ███████║ ███████║ ██║ ╚██████╔╝ ██║ ╚████║')}
27
+ ${chalk.bold.white('╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝')}
28
+
29
+ ${chalk.yellow('>>>')} ${chalk.bold.yellow('CONTROL')} ${chalk.dim('TAR Global Management')}
30
+ `;
31
+
32
+ const miniLogo = chalk.cyan('>>>') + chalk.bold.white(' Mission Control') + chalk.dim(' | TAR Global');
33
+
34
+ // Compact mode detection
35
+ const isCompact = (): boolean => {
36
+ const config = getConfig();
37
+ if (config.compact === 'on') return true;
38
+ if (config.compact === 'off') return false;
39
+ // Auto-detect based on terminal width
40
+ const width = process.stdout.columns || 80;
41
+ return width < 60;
42
+ };
43
+
44
+ const getTermWidth = (): number => process.stdout.columns || 80;
45
+
46
+ // Status bar showing user info
47
+ const showStatusBar = () => {
48
+ const user = getUser();
49
+ if (user) {
50
+ console.log(chalk.dim('─'.repeat(50)));
51
+ console.log(chalk.bold.white(` ${user.name}`) + chalk.dim(` | ${user.email}`));
52
+ console.log('');
53
+ }
54
+ };
55
+
56
+ // Fun completion messages (ASCII-safe)
57
+ const celebrations = [
58
+ chalk.green('*** Task completed! You\'re on fire! ***'),
59
+ chalk.green('*** Done! Another one bites the dust! ***'),
60
+ chalk.green('*** Shipped! To infinity and beyond! ***'),
61
+ chalk.green('*** Crushed it! Keep that momentum! ***'),
62
+ chalk.green('*** Victory! You\'re unstoppable! ***'),
63
+ chalk.green('*** Stellar work! Task obliterated! ***'),
64
+ chalk.green('*** Boom! Task destroyed! ***'),
65
+ chalk.green('*** Bullseye! Nailed it! ***'),
66
+ ];
67
+
68
+ const getRandomCelebration = () => celebrations[Math.floor(Math.random() * celebrations.length)];
69
+
70
+ // Priority colors and icons (ASCII-safe for terminal compatibility)
71
+ const priorityStyle: Record<string, { icon: string; color: (s: string) => string }> = {
72
+ critical: { icon: chalk.red('●'), color: chalk.red },
73
+ high: { icon: chalk.yellow('●'), color: chalk.yellow },
74
+ medium: { icon: chalk.blue('●'), color: chalk.blue },
75
+ low: { icon: chalk.green('●'), color: chalk.green },
76
+ };
77
+
78
+ // Type icons (ASCII-safe)
79
+ const typeIcons: Record<string, string> = {
80
+ bug: chalk.red('[BUG]'),
81
+ feature: chalk.green('[FTR]'),
82
+ improvement: chalk.cyan('[IMP]'),
83
+ task: chalk.white('[TSK]'),
84
+ escalation: chalk.magenta('[ESC]'),
85
+ };
86
+
87
+ // Helper to check auth
88
+ const requireAuth = () => {
89
+ if (!isAuthenticated()) {
90
+ console.log(chalk.red('\n[!] Not authenticated. Run `mc login` first.\n'));
91
+ process.exit(1);
92
+ }
93
+ };
94
+
95
+ // Helper to parse task number (supports MC-1001 or just 1001)
96
+ const parseTaskNumber = (input: string): string => {
97
+ return input.toUpperCase().replace('MC-', '');
98
+ };
99
+
100
+ // Helper to format date
101
+ const formatDate = (dateStr: string): string => {
102
+ const date = new Date(dateStr);
103
+ const now = new Date();
104
+ const diffMs = now.getTime() - date.getTime();
105
+ const diffMins = Math.floor(diffMs / 60000);
106
+ const diffHours = Math.floor(diffMs / 3600000);
107
+ const diffDays = Math.floor(diffMs / 86400000);
108
+
109
+ if (diffMins < 1) return 'just now';
110
+ if (diffMins < 60) return `${diffMins}m ago`;
111
+ if (diffHours < 24) return `${diffHours}h ago`;
112
+ if (diffDays < 7) return `${diffDays}d ago`;
113
+ return date.toLocaleDateString();
114
+ };
115
+
116
+ // Fetch and store user info
117
+ const fetchUserInfo = async () => {
118
+ try {
119
+ const response = await api.getCurrentUser();
120
+ if (response.user) {
121
+ setUser({
122
+ name: response.user.display_name,
123
+ email: response.user.email,
124
+ });
125
+ }
126
+ } catch (e) {
127
+ // Silently fail - user info is optional
128
+ }
129
+ };
130
+
131
+ program
132
+ .name('mc')
133
+ .description('Mission Control CLI - TAR Global Task Management')
134
+ .version(VERSION)
135
+ .addHelpText('before', logo);
136
+
137
+ // ==================== INTERACTIVE SHELL ====================
138
+ program
139
+ .command('shell')
140
+ .alias('i')
141
+ .description('Start interactive dashboard mode')
142
+ .action(async () => {
143
+ requireAuth();
144
+ await runInteractiveShell();
145
+ });
146
+
147
+ // Command functions for interactive mode
148
+ const listTasksCmd = async (options: { all?: boolean }) => {
149
+ const spinner = ora('Loading tasks...').start();
150
+ try {
151
+ const data = options.all ? await api.listTasks({}) : await api.getMyTasks();
152
+ const tasks: Task[] = data.tasks || [];
153
+ spinner.stop();
154
+
155
+ if (tasks.length === 0) {
156
+ console.log(chalk.dim('\n No tasks found\n'));
157
+ return;
158
+ }
159
+
160
+ const compact = isCompact();
161
+ const table = new Table({
162
+ head: compact
163
+ ? [chalk.cyan('ID'), chalk.cyan('Title'), chalk.cyan('Pri')]
164
+ : [chalk.cyan('ID'), chalk.cyan('Title'), chalk.cyan('Priority'), chalk.cyan('Status'), chalk.cyan('Type')],
165
+ style: { head: [], border: [] },
166
+ colWidths: compact ? [8, 24, 6] : [10, 40, 12, 15, 8],
167
+ });
168
+
169
+ tasks.forEach((task) => {
170
+ const p = priorityStyle[task.priority] || priorityStyle.medium;
171
+ const titleLen = compact ? 21 : 37;
172
+ const title = task.title.length > titleLen ? task.title.substring(0, titleLen) + '...' : task.title;
173
+
174
+ if (compact) {
175
+ table.push([
176
+ chalk.dim(task.display_id.replace('MC-', '')),
177
+ title,
178
+ p.icon,
179
+ ]);
180
+ } else {
181
+ table.push([
182
+ chalk.white(task.display_id),
183
+ title,
184
+ p.icon + ' ' + p.color(task.priority),
185
+ task.column_name,
186
+ typeIcons[task.task_type] || chalk.white('[TSK]'),
187
+ ]);
188
+ }
189
+ });
190
+
191
+ console.log('\n' + table.toString() + '\n');
192
+ console.log(chalk.dim(` ${tasks.length} task(s) found\n`));
193
+ } catch (error: any) {
194
+ spinner.fail(chalk.red('Failed to load tasks'));
195
+ }
196
+ };
197
+
198
+ const showTaskCmd = async (taskId: string) => {
199
+ const spinner = ora('Loading task...').start();
200
+ const compact = isCompact();
201
+
202
+ try {
203
+ const task: Task = await api.getTask(parseTaskNumber(taskId));
204
+ spinner.stop();
205
+
206
+ const p = priorityStyle[task.priority] || priorityStyle.medium;
207
+ const typeIcon = typeIcons[task.task_type] || chalk.white('[TSK]');
208
+
209
+ if (compact) {
210
+ // Compact task view
211
+ console.log('');
212
+ console.log(chalk.bold.white(`${task.display_id}: ${task.title.substring(0, 28)}`));
213
+ console.log(chalk.dim('─'.repeat(30)));
214
+ console.log(`${typeIcon} ${p.icon} ${p.color(task.priority)} ${chalk.dim('|')} ${task.column_name}`);
215
+ console.log(chalk.dim(`Board: ${task.board_name}`));
216
+ console.log(chalk.dim(`Created: ${formatDate(task.created_at)}`));
217
+
218
+ if (task.assignees.length > 0) {
219
+ const names = task.assignees.map((a) => a.display_name.split(' ')[0]).join(', ');
220
+ console.log(chalk.dim(`Assigned: ${names.substring(0, 25)}`));
221
+ }
222
+
223
+ if (task.is_completed) {
224
+ console.log(chalk.green(`✓ Done ${formatDate(task.completed_at!)}`));
225
+ }
226
+
227
+ if (task.description) {
228
+ console.log(chalk.dim('\n' + task.description.substring(0, 100)));
229
+ if (task.description.length > 100) console.log(chalk.dim('...'));
230
+ }
231
+ console.log('');
232
+ } else {
233
+ console.log('\n' + chalk.bold.white('╭─────────────────────────────────────────────────────╮'));
234
+ console.log(chalk.bold.white(`│ ${task.display_id}: ${task.title.substring(0, 43).padEnd(43)} │`));
235
+ console.log(chalk.bold.white('├─────────────────────────────────────────────────────┤'));
236
+ console.log(`│ Type: ${typeIcon} ${task.task_type.padEnd(12)} Priority: ${p.icon} ${p.color(task.priority.padEnd(10))} │`);
237
+ console.log(`│ Board: ${task.board_name.padEnd(12)} Column: ${task.column_name.padEnd(15)} │`);
238
+ console.log(`│ Created: ${formatDate(task.created_at).padEnd(10)} by ${(task.created_by_name || 'Unknown').padEnd(20)} │`);
239
+
240
+ if (task.assignees.length > 0) {
241
+ const assigneeNames = task.assignees.map((a) => a.display_name).join(', ');
242
+ console.log(`│ Assignees: ${assigneeNames.substring(0, 40).padEnd(40)} │`);
243
+ }
244
+
245
+ if (task.is_completed) {
246
+ console.log(`│ ${chalk.green('[DONE]')} ${formatDate(task.completed_at!).padEnd(41)} │`);
247
+ }
248
+
249
+ console.log(chalk.bold.white('╰─────────────────────────────────────────────────────╯'));
250
+
251
+ if (task.description) {
252
+ console.log(chalk.dim('\nDescription:'));
253
+ console.log(task.description.substring(0, 200));
254
+ if (task.description.length > 200) console.log(chalk.dim('...'));
255
+ }
256
+
257
+ console.log('');
258
+ }
259
+ } catch (error: any) {
260
+ spinner.fail(chalk.red('Failed to load task'));
261
+ }
262
+ };
263
+
264
+ const createTaskInteractive = async () => {
265
+ const boards = await api.listBoards();
266
+
267
+ const answers = await inquirer.prompt([
268
+ { type: 'input', name: 'title', message: 'Task title:' },
269
+ { type: 'input', name: 'description', message: 'Description (optional):' },
270
+ {
271
+ type: 'list',
272
+ name: 'board',
273
+ message: 'Board:',
274
+ choices: boards.boards.map((b: Board) => ({ name: b.name, value: b.id })),
275
+ },
276
+ {
277
+ type: 'list',
278
+ name: 'priority',
279
+ message: 'Priority:',
280
+ choices: ['low', 'medium', 'high', 'critical'],
281
+ default: 'medium',
282
+ },
283
+ {
284
+ type: 'list',
285
+ name: 'type',
286
+ message: 'Type:',
287
+ choices: ['task', 'bug', 'feature', 'improvement'],
288
+ default: 'task',
289
+ },
290
+ ]);
291
+
292
+ const spinner = ora('Creating task...').start();
293
+ try {
294
+ const task = await api.createTask({
295
+ title: answers.title,
296
+ description: answers.description || undefined,
297
+ board: answers.board,
298
+ priority: answers.priority,
299
+ task_type: answers.type,
300
+ });
301
+ spinner.succeed(chalk.green(`Task created: ${task.display_id}`));
302
+ console.log(chalk.dim(` ${task.title}\n`));
303
+ } catch (error: any) {
304
+ spinner.fail(chalk.red('Failed to create task'));
305
+ }
306
+ };
307
+
308
+ const startTaskCmd = async (taskId: string) => {
309
+ const spinner = ora('Starting task...').start();
310
+ try {
311
+ const task = await api.getTask(parseTaskNumber(taskId));
312
+ const boardData = await api.getBoard(task.board_name.toLowerCase().replace(' ', '-'));
313
+ const inProgressCol = boardData.columns.find((c: any) => c.name.toLowerCase().includes('progress'));
314
+
315
+ if (!inProgressCol) {
316
+ spinner.fail(chalk.red('Could not find In Progress column'));
317
+ return;
318
+ }
319
+
320
+ await api.moveTask(parseTaskNumber(taskId), inProgressCol.id);
321
+ spinner.succeed(chalk.green(`${task.display_id} moved to In Progress`));
322
+ } catch (error: any) {
323
+ spinner.fail(chalk.red('Failed to start task'));
324
+ }
325
+ };
326
+
327
+ const completeTaskCmd = async (taskId: string) => {
328
+ const spinner = ora('Completing task...').start();
329
+ try {
330
+ const result = await api.completeTask(parseTaskNumber(taskId));
331
+ spinner.stop();
332
+ console.log('\n' + getRandomCelebration());
333
+ console.log(chalk.dim(` ${result.task.display_id}: ${result.task.title}`));
334
+ console.log(chalk.green('\n ╭────────────────────────────────╮'));
335
+ console.log(chalk.green(' │') + chalk.bold.yellow(' [*] TASK COMPLETED! [*] ') + chalk.green('│'));
336
+ console.log(chalk.green(' ╰────────────────────────────────╯\n'));
337
+ } catch (error: any) {
338
+ spinner.fail(chalk.red('Failed to complete task'));
339
+ }
340
+ };
341
+
342
+ const listBoardsCmd = async () => {
343
+ const spinner = ora('Loading boards...').start();
344
+ const compact = isCompact();
345
+
346
+ try {
347
+ const data = await api.listBoards();
348
+ spinner.stop();
349
+
350
+ if (compact) {
351
+ console.log(chalk.cyan('\n Boards:'));
352
+ data.boards.forEach((board: Board) => {
353
+ console.log(` ${chalk.white(board.slug.padEnd(10))} ${chalk.dim(board.task_count + ' tasks')}`);
354
+ });
355
+ console.log('');
356
+ } else {
357
+ const table = new Table({
358
+ head: [chalk.cyan('Slug'), chalk.cyan('Name'), chalk.cyan('Team'), chalk.cyan('Tasks')],
359
+ style: { head: [], border: [] },
360
+ });
361
+
362
+ data.boards.forEach((board: Board) => {
363
+ table.push([board.slug, board.name, board.team, board.task_count.toString()]);
364
+ });
365
+
366
+ console.log('\n' + table.toString() + '\n');
367
+ }
368
+ } catch (error: any) {
369
+ spinner.fail(chalk.red('Failed to load boards'));
370
+ }
371
+ };
372
+
373
+ const showStatsCmd = async () => {
374
+ const spinner = ora('Loading stats...').start();
375
+ const compact = isCompact();
376
+
377
+ try {
378
+ const stats = await api.getStats();
379
+ spinner.stop();
380
+
381
+ if (compact) {
382
+ console.log(chalk.cyan('\n YOUR STATS'));
383
+ console.log(chalk.dim(' ──────────────────'));
384
+ console.log(` Tasks: ${chalk.bold.white(stats.my_tasks)}`);
385
+ console.log(` Done: ${chalk.bold.green(stats.my_completed)}`);
386
+ console.log(` Overdue: ${chalk.bold.red(stats.overdue_tasks)}`);
387
+ console.log(` Critical: ${chalk.bold.yellow(stats.critical_count)}`);
388
+ console.log(` High: ${chalk.bold.yellow(stats.high_count)}`);
389
+ console.log('');
390
+ } else {
391
+ console.log('\n' + miniLogo);
392
+ console.log(chalk.cyan('\n ╭──────────────────────────────╮'));
393
+ console.log(chalk.cyan(' │') + chalk.bold.white(' [=] YOUR STATS [=] ') + chalk.cyan('│'));
394
+ console.log(chalk.cyan(' ├──────────────────────────────┤'));
395
+ console.log(chalk.cyan(' │') + ` [T] My Tasks: ${chalk.bold.white(String(stats.my_tasks).padStart(6))} ` + chalk.cyan('│'));
396
+ console.log(chalk.cyan(' │') + ` [+] Completed: ${chalk.bold.green(String(stats.my_completed).padStart(6))} ` + chalk.cyan('│'));
397
+ console.log(chalk.cyan(' │') + ` [!] Overdue: ${chalk.bold.red(String(stats.overdue_tasks).padStart(6))} ` + chalk.cyan('│'));
398
+ console.log(chalk.cyan(' │') + ` ${chalk.red('●')} Critical: ${chalk.bold.yellow(String(stats.critical_count).padStart(6))} ` + chalk.cyan('│'));
399
+ console.log(chalk.cyan(' │') + ` ${chalk.yellow('●')} High: ${chalk.bold.yellow(String(stats.high_count).padStart(6))} ` + chalk.cyan('│'));
400
+ console.log(chalk.cyan(' ╰──────────────────────────────╯\n'));
401
+ }
402
+ } catch (error: any) {
403
+ spinner.fail(chalk.red('Failed to load stats'));
404
+ }
405
+ };
406
+
407
+ const showUrgentCmd = async () => {
408
+ const spinner = ora('Loading urgent tasks...').start();
409
+ try {
410
+ const data = await api.listTasks({ priority: 'critical' });
411
+ const tasks: Task[] = data.tasks || [];
412
+ spinner.stop();
413
+
414
+ if (tasks.length === 0) {
415
+ console.log(chalk.green('\n [OK] No critical tasks! All clear.\n'));
416
+ return;
417
+ }
418
+
419
+ console.log(chalk.red.bold(`\n [!!!] ${tasks.length} critical task(s) need attention:\n`));
420
+ tasks.forEach((task) => {
421
+ console.log(` ${chalk.red('●')} ${task.display_id}: ${task.title}`);
422
+ console.log(chalk.dim(` ${task.board_name} → ${task.column_name}`));
423
+ });
424
+ console.log('');
425
+ } catch (error: any) {
426
+ spinner.fail(chalk.red('Failed to load tasks'));
427
+ }
428
+ };
429
+
430
+ const showWhoami = () => {
431
+ const config = getConfig();
432
+ const user = getUser();
433
+ const termWidth = getTermWidth();
434
+
435
+ console.log('\n' + chalk.bold(' Mission Control Config'));
436
+ console.log(chalk.dim(' ──────────────────────'));
437
+ if (user) {
438
+ console.log(` User: ${chalk.bold.white(user.name)}`);
439
+ console.log(` Email: ${user.email}`);
440
+ }
441
+ console.log(` API URL: ${config.apiUrl}`);
442
+ console.log(` Token: ${config.token ? chalk.green('●') + ' configured' : chalk.red('○') + ' not set'}`);
443
+ console.log(` Compact: ${chalk.cyan(config.compact)} ${chalk.dim(`(term: ${termWidth} cols)`)}`);
444
+ console.log(` Version: ${VERSION}`);
445
+ console.log('');
446
+ };
447
+
448
+ const showInteractiveHelp = () => {
449
+ const compact = isCompact();
450
+
451
+ if (compact) {
452
+ console.log(chalk.cyan('\n Commands:'));
453
+ console.log(chalk.dim(' ─────────────'));
454
+ console.log(' tasks/ls List tasks');
455
+ console.log(' show <id> Task details');
456
+ console.log(' new Create task');
457
+ console.log(' done <id> Complete');
458
+ console.log(' stats Statistics');
459
+ console.log(' payouts Payouts');
460
+ console.log(' config Settings');
461
+ console.log(' exit/q Quit');
462
+ console.log('');
463
+ } else {
464
+ console.log(chalk.cyan('\n Available Commands:'));
465
+ console.log(chalk.dim(' ──────────────────────'));
466
+ console.log(' tasks, ls List your tasks');
467
+ console.log(' tasks -a List all tasks');
468
+ console.log(' show <id> Show task details');
469
+ console.log(' new Create new task (interactive)');
470
+ console.log(' start <id> Move task to In Progress');
471
+ console.log(' done <id> Complete a task');
472
+ console.log(' boards List boards');
473
+ console.log(' stats Show your statistics');
474
+ console.log(' urgent Show critical tasks');
475
+ console.log(chalk.yellow(' payouts Payout processing dashboard'));
476
+ console.log(' whoami Show current user/config');
477
+ console.log(' config View/set config (compact mode)');
478
+ console.log(' clear Clear screen');
479
+ console.log(' help, ? Show this help');
480
+ console.log(' exit, quit, q Exit shell');
481
+ console.log('');
482
+ }
483
+ };
484
+
485
+ // ==================== INTERACTIVE DASHBOARD SHELL ====================
486
+
487
+ const showDashboard = async () => {
488
+ try {
489
+ const stats = await api.getStats();
490
+ const user = getUser();
491
+ const now = new Date();
492
+ const greeting = now.getHours() < 12 ? 'Good morning' : now.getHours() < 18 ? 'Good afternoon' : 'Good evening';
493
+ const compact = isCompact();
494
+
495
+ console.clear();
496
+
497
+ if (compact) {
498
+ // Compact mobile dashboard
499
+ console.log(miniLogo);
500
+ console.log('');
501
+ const firstName = (user?.name || 'Agent').split(' ')[0];
502
+ console.log(chalk.dim(`${greeting}, `) + chalk.bold.yellow(firstName));
503
+ console.log('');
504
+ console.log(chalk.cyan('┌─────────────────────────────┐'));
505
+ console.log(chalk.cyan('│') + ` ${chalk.bgCyan.black('TASKS')} ${chalk.bold.white(String(stats.my_tasks).padStart(3))} ${chalk.bgRed.white('CRIT')} ${chalk.bold.red(String(stats.critical_count).padStart(2))} ${chalk.bgYellow.black('OVR')} ${chalk.bold.yellow(String(stats.overdue_tasks).padStart(2))} ` + chalk.cyan('│'));
506
+ console.log(chalk.cyan('├─────────────────────────────┤'));
507
+ console.log(chalk.cyan('│') + ` ${chalk.cyan('[1]')}Tasks ${chalk.cyan('[2]')}New ${chalk.cyan('[3]')}Pay ${chalk.cyan('[4]')}Urg ` + chalk.cyan('│'));
508
+ console.log(chalk.cyan('└─────────────────────────────┘'));
509
+ console.log('');
510
+
511
+ // Show urgent count only in compact mode
512
+ if (stats.critical_count > 0 || stats.overdue_tasks > 0) {
513
+ console.log(chalk.red(`! ${stats.critical_count} critical task(s)`));
514
+ }
515
+ } else {
516
+ // Full dashboard
517
+ const blinkDots = chalk.cyan('\x1b[5m●●●\x1b[0m'); // Blinking dots
518
+ console.log(logo);
519
+ console.log('');
520
+ console.log(chalk.cyan('╔══════════════════════════════════════════════════════════════════════╗'));
521
+ console.log(chalk.cyan('║') + ` ${chalk.bold.white(greeting)}, ${chalk.bold.yellow((user?.name || 'Agent').substring(0, 35))}`.padEnd(60) + ` ${blinkDots} ` + chalk.cyan('║'));
522
+ console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════════════╣'));
523
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
524
+ console.log(chalk.cyan('║') + ` ${chalk.bgCyan.black(' MY TASKS ')} ${chalk.bgRed.white(' CRITICAL ')} ${chalk.bgYellow.black(' OVERDUE ')} ${chalk.bgGreen.black(' DONE ')} ` + chalk.cyan('║'));
525
+ console.log(chalk.cyan('║') + ` ${chalk.bold.white(String(stats.my_tasks).padStart(3))} ${chalk.bold.red(String(stats.critical_count).padStart(3))} ${chalk.bold.yellow(String(stats.overdue_tasks).padStart(3))} ${chalk.bold.green(String(stats.my_completed).padStart(3))} ` + chalk.cyan('║'));
526
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
527
+ console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════════════╣'));
528
+ console.log(chalk.cyan('║') + chalk.dim(' Quick Actions: ') + chalk.cyan('║'));
529
+ console.log(chalk.cyan('║') + ` ${chalk.cyan('[1]')} My Tasks ${chalk.cyan('[2]')} New Task ${chalk.cyan('[3]')} Payouts ${chalk.cyan('[4]')} Urgent ` + chalk.cyan('║'));
530
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
531
+ console.log(chalk.cyan('╚══════════════════════════════════════════════════════════════════════╝'));
532
+ console.log('');
533
+
534
+ // Show recent urgent tasks if any
535
+ if (stats.critical_count > 0 || stats.overdue_tasks > 0) {
536
+ console.log(chalk.red.bold(' ⚠️ ATTENTION REQUIRED:'));
537
+ const urgentData = await api.listTasks({ priority: 'critical' });
538
+ const urgentTasks: Task[] = (urgentData.tasks || []).slice(0, 3);
539
+ urgentTasks.forEach((t: Task) => {
540
+ console.log(` ${chalk.red('●')} ${chalk.dim(t.display_id)} ${t.title.substring(0, 45)}`);
541
+ });
542
+ console.log('');
543
+ }
544
+ }
545
+ } catch (e) {
546
+ // Silently fail - show basic prompt
547
+ }
548
+ };
549
+
550
+ const runInteractiveShell = async () => {
551
+ setTitle('Mission Control');
552
+ await showDashboard();
553
+
554
+ const runLoop = async (): Promise<void> => {
555
+ const user = getUser();
556
+ const prompt = chalk.cyan('mc') + chalk.dim(` ${user?.name?.split(' ')[0] || ''}`) + chalk.cyan(' ❯ ');
557
+
558
+ const { command } = await inquirer.prompt([
559
+ {
560
+ type: 'input',
561
+ name: 'command',
562
+ message: prompt,
563
+ prefix: '',
564
+ },
565
+ ]);
566
+
567
+ const cmd = command.trim().toLowerCase();
568
+ const parts = command.trim().split(/\s+/);
569
+ const mainCmd = parts[0]?.toLowerCase();
570
+ const args = parts.slice(1);
571
+
572
+ // Exit commands
573
+ if (cmd === 'exit' || cmd === 'quit' || cmd === 'q') {
574
+ console.log(chalk.dim('\n 👋 See you later!\n'));
575
+ process.exit(0);
576
+ }
577
+
578
+ if (cmd === '') {
579
+ return runLoop();
580
+ }
581
+
582
+ try {
583
+ // Quick number shortcuts
584
+ if (cmd === '1') {
585
+ setTitle('My Tasks');
586
+ await listTasksCmd({});
587
+ } else if (cmd === '2') {
588
+ setTitle('New Task');
589
+ await createTaskInteractive();
590
+ } else if (cmd === '3') {
591
+ await runPayoutsShell();
592
+ await showDashboard();
593
+ } else if (cmd === '4') {
594
+ setTitle('Urgent');
595
+ await showUrgentCmd();
596
+ } else {
597
+ // Regular commands
598
+ switch (mainCmd) {
599
+ case 'dashboard':
600
+ case 'home':
601
+ case 'h':
602
+ await showDashboard();
603
+ break;
604
+ case 'tasks':
605
+ case 'ls':
606
+ setTitle('Tasks');
607
+ await listTasksCmd({ all: args.includes('-a') || args.includes('--all') });
608
+ break;
609
+ case 'show':
610
+ case 'view':
611
+ if (args[0]) {
612
+ setTitle(`Task ${args[0]}`);
613
+ await showTaskCmd(args[0]);
614
+ } else {
615
+ console.log(chalk.red(' Usage: show <taskId>\n'));
616
+ }
617
+ break;
618
+ case 'new':
619
+ case 'create':
620
+ setTitle('New Task');
621
+ await createTaskInteractive();
622
+ break;
623
+ case 'start':
624
+ if (args[0]) {
625
+ await startTaskCmd(args[0]);
626
+ } else {
627
+ console.log(chalk.red(' Usage: start <taskId>\n'));
628
+ }
629
+ break;
630
+ case 'done':
631
+ case 'complete':
632
+ if (args[0]) {
633
+ await completeTaskCmd(args[0]);
634
+ } else {
635
+ console.log(chalk.red(' Usage: done <taskId>\n'));
636
+ }
637
+ break;
638
+ case 'boards':
639
+ setTitle('Boards');
640
+ await listBoardsCmd();
641
+ break;
642
+ case 'stats':
643
+ setTitle('Stats');
644
+ await showStatsCmd();
645
+ break;
646
+ case 'urgent':
647
+ setTitle('Urgent');
648
+ await showUrgentCmd();
649
+ break;
650
+ case 'payouts':
651
+ case 'payout':
652
+ case 'pay':
653
+ await runPayoutsShell();
654
+ await showDashboard();
655
+ break;
656
+ case 'whoami':
657
+ showWhoami();
658
+ break;
659
+ case 'help':
660
+ case '?':
661
+ showInteractiveHelp();
662
+ break;
663
+ case 'clear':
664
+ case 'cls':
665
+ await showDashboard();
666
+ break;
667
+ case 'refresh':
668
+ case 'r':
669
+ await showDashboard();
670
+ break;
671
+ case 'config':
672
+ if (args[0] === 'compact' && args[1]) {
673
+ if (['auto', 'on', 'off'].includes(args[1])) {
674
+ setConfig('compact', args[1]);
675
+ console.log(chalk.green(`\n ✓ Compact mode: ${args[1]}\n`));
676
+ await showDashboard();
677
+ } else {
678
+ console.log(chalk.red('\n Use: config compact auto/on/off\n'));
679
+ }
680
+ } else {
681
+ const cfg = getConfig();
682
+ console.log(chalk.cyan('\n Config:'));
683
+ console.log(` compact: ${chalk.bold(cfg.compact)} ${chalk.dim('(auto/on/off)')}`);
684
+ console.log(chalk.dim('\n Usage: config compact on\n'));
685
+ }
686
+ break;
687
+ default:
688
+ console.log(chalk.red(` Unknown command: ${mainCmd}`));
689
+ console.log(chalk.dim(' Type "help" for available commands\n'));
690
+ }
691
+ }
692
+ } catch (e: any) {
693
+ console.log(chalk.red(` Error: ${e.message}\n`));
694
+ }
695
+
696
+ setTitle('Mission Control');
697
+ return runLoop();
698
+ };
699
+
700
+ await runLoop();
701
+ };
702
+
703
+ // ==================== PAYOUT FUNCTIONS ====================
704
+
705
+ const PLAN_COLORS: Record<string, (s: string) => string> = {
706
+ hermes: chalk.yellow,
707
+ alpha: chalk.cyan,
708
+ mematic: chalk.magenta,
709
+ validator_v2: chalk.blue,
710
+ booster: chalk.green,
711
+ dumpster: chalk.gray,
712
+ };
713
+
714
+ const PLAN_ICONS: Record<string, string> = {
715
+ hermes: '⚡',
716
+ alpha: '🔷',
717
+ mematic: '💎',
718
+ validator_v2: '🔐',
719
+ booster: '🚀',
720
+ dumpster: '🗑️',
721
+ };
722
+
723
+ const formatAmount = (amount: number, crypto?: string): string => {
724
+ const formatted = amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 4 });
725
+ return crypto ? `${formatted} ${crypto}` : `$${formatted}`;
726
+ };
727
+
728
+ const formatWallet = (address: string, length: number = 16): string => {
729
+ if (address.length <= length) return address;
730
+ return address.substring(0, 8) + '...' + address.substring(address.length - 6);
731
+ };
732
+
733
+ const showPayoutDashboard = async () => {
734
+ const spinner = ora('Loading payout dashboard...').start();
735
+ const compact = isCompact();
736
+
737
+ try {
738
+ // Fetch metrics for all plans
739
+ const metricsData = await api.getPayoutMetrics();
740
+ const metrics: PayoutMetrics = metricsData.data;
741
+
742
+ // Fetch pending payouts
743
+ const payoutsData = await api.listPayouts({ status: 'pending', limit: 50 });
744
+ const payouts: Payout[] = payoutsData.data || [];
745
+
746
+ spinner.stop();
747
+
748
+ if (compact) {
749
+ // Compact mobile payout dashboard
750
+ console.log('\n' + chalk.yellow('>>> PAYOUTS'));
751
+ console.log('');
752
+ console.log(chalk.cyan('┌─────────────────────────────┐'));
753
+ console.log(chalk.cyan('│') + ` ${chalk.yellow('PEND')} ${String(metrics.pending_payouts).padStart(3)} ${chalk.green('DONE')} ${String(metrics.completed_payouts).padStart(4)} ${chalk.red('FAIL')} ${String(metrics.failed_payouts).padStart(2)} ` + chalk.cyan('│'));
754
+ console.log(chalk.cyan('├─────────────────────────────┤'));
755
+ console.log(chalk.cyan('│') + ` Pending: ${chalk.yellow('$' + metrics.pending_amount.toLocaleString())}`.padEnd(29) + chalk.cyan('│'));
756
+ console.log(chalk.cyan('│') + ` Paid: ${chalk.green('$' + metrics.paid_amount.toLocaleString())}`.padEnd(29) + chalk.cyan('│'));
757
+ console.log(chalk.cyan('└─────────────────────────────┘'));
758
+
759
+ if (payouts.length === 0) {
760
+ console.log(chalk.green('\n ✓ No pending payouts\n'));
761
+ return;
762
+ }
763
+
764
+ // Group payouts by plan
765
+ const byPlan: Record<string, Payout[]> = {};
766
+ payouts.forEach((p) => {
767
+ if (!byPlan[p.plan]) byPlan[p.plan] = [];
768
+ byPlan[p.plan].push(p);
769
+ });
770
+
771
+ console.log(chalk.dim('\nPENDING BY PLAN:'));
772
+
773
+ Object.entries(byPlan).forEach(([plan, planPayouts]) => {
774
+ const planColor = PLAN_COLORS[plan] || chalk.white;
775
+ const byCrypto: Record<string, number> = {};
776
+ planPayouts.forEach((p) => {
777
+ if (!byCrypto[p.crypto_type]) byCrypto[p.crypto_type] = 0;
778
+ byCrypto[p.crypto_type] += p.amount;
779
+ });
780
+ const cryptoTotals = Object.entries(byCrypto).map(([crypto, amt]) => `${amt.toFixed(2)} ${crypto}`).join(', ');
781
+
782
+ console.log(` ${planColor(plan.toUpperCase())} ${chalk.dim('|')} ${planPayouts.length} ${chalk.dim('|')} ${chalk.yellow(cryptoTotals)}`);
783
+
784
+ // Show first 2 payouts
785
+ planPayouts.slice(0, 2).forEach((p) => {
786
+ const email = p.user_email.length > 15 ? p.user_email.substring(0, 13) + '..' : p.user_email;
787
+ console.log(chalk.dim(` #${p.id} ${email} ${chalk.green(p.amount.toFixed(2))} ${p.crypto_type}`));
788
+ });
789
+
790
+ if (planPayouts.length > 2) {
791
+ console.log(chalk.dim(` ... +${planPayouts.length - 2} more`));
792
+ }
793
+ });
794
+
795
+ console.log(chalk.dim('\nCmds: list, approve, cancel, back\n'));
796
+
797
+ } else {
798
+ // Full dashboard
799
+ console.log('\n' + chalk.cyan('╔════════════════════════════════════════════════════════════════╗'));
800
+ console.log(chalk.cyan('║') + chalk.bold.white(' 💰 PAYOUT CONTROL CENTER 💰 ') + chalk.cyan('║'));
801
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
802
+
803
+ // Metrics Row
804
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
805
+ console.log(chalk.cyan('║') + ` ${chalk.bold.yellow('PENDING')} ${chalk.bold.green('COMPLETED')} ${chalk.bold.red('FAILED')} ${chalk.bold.white('TOTAL')} ` + chalk.cyan('║'));
806
+ console.log(chalk.cyan('║') + ` ${chalk.yellow(String(metrics.pending_payouts).padStart(4))} ${chalk.green(String(metrics.completed_payouts).padStart(5))} ${chalk.red(String(metrics.failed_payouts).padStart(3))} ${chalk.white(String(metrics.total_payouts).padStart(5))} ` + chalk.cyan('║'));
807
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
808
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
809
+
810
+ // Amount Stats
811
+ console.log(chalk.cyan('║') + ` ${chalk.dim('Pending Amount:')} ${chalk.yellow('$' + metrics.pending_amount.toLocaleString())}`.padEnd(64) + chalk.cyan('║'));
812
+ console.log(chalk.cyan('║') + ` ${chalk.dim('Paid Amount:')} ${chalk.green('$' + metrics.paid_amount.toLocaleString())}`.padEnd(64) + chalk.cyan('║'));
813
+ console.log(chalk.cyan('║') + ` ${chalk.dim('Total Volume:')} ${chalk.white('$' + metrics.total_amount.toLocaleString())}`.padEnd(64) + chalk.cyan('║'));
814
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
815
+
816
+ if (payouts.length === 0) {
817
+ console.log(chalk.cyan('║') + chalk.green(' ✓ No pending payouts. All clear! ') + chalk.cyan('║'));
818
+ console.log(chalk.cyan('╚════════════════════════════════════════════════════════════════╝\n'));
819
+ return;
820
+ }
821
+
822
+ // Group payouts by plan
823
+ const byPlan: Record<string, Payout[]> = {};
824
+ payouts.forEach((p) => {
825
+ if (!byPlan[p.plan]) byPlan[p.plan] = [];
826
+ byPlan[p.plan].push(p);
827
+ });
828
+
829
+ console.log(chalk.cyan('║') + chalk.bold.white(' PENDING PAYOUTS BY PLAN ') + chalk.cyan('║'));
830
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
831
+
832
+ // Show payouts by plan
833
+ Object.entries(byPlan).forEach(([plan, planPayouts]) => {
834
+ const planColor = PLAN_COLORS[plan] || chalk.white;
835
+ const planIcon = PLAN_ICONS[plan] || '📋';
836
+
837
+ // Group amounts by crypto type
838
+ const byCrypto: Record<string, number> = {};
839
+ planPayouts.forEach((p) => {
840
+ if (!byCrypto[p.crypto_type]) byCrypto[p.crypto_type] = 0;
841
+ byCrypto[p.crypto_type] += p.amount;
842
+ });
843
+ const cryptoTotals = Object.entries(byCrypto).map(([crypto, amt]) => `${amt.toFixed(2)} ${crypto}`).join(', ');
844
+
845
+ console.log(chalk.cyan('║') + ` ${planIcon} ${planColor(plan.toUpperCase().padEnd(12))} ${chalk.dim('|')} ${chalk.white(planPayouts.length + ' payouts')} ${chalk.dim('|')} ${chalk.yellow(cryptoTotals)}`.padEnd(72) + chalk.cyan('║'));
846
+
847
+ // Show first 3 payouts for this plan
848
+ planPayouts.slice(0, 3).forEach((p) => {
849
+ const idStr = chalk.dim(`#${p.id}`);
850
+ const emailStr = p.user_email.length > 20 ? p.user_email.substring(0, 18) + '..' : p.user_email;
851
+ const amountStr = formatAmount(p.amount, p.crypto_type);
852
+ const walletStr = formatWallet(p.wallet_address);
853
+ console.log(chalk.cyan('║') + ` ${idStr} ${chalk.white(emailStr.padEnd(20))} ${chalk.green(amountStr.padStart(14))} ${chalk.dim(walletStr)}`.padEnd(72) + chalk.cyan('║'));
854
+ });
855
+
856
+ if (planPayouts.length > 3) {
857
+ console.log(chalk.cyan('║') + chalk.dim(` ... and ${planPayouts.length - 3} more`).padEnd(64) + chalk.cyan('║'));
858
+ }
859
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
860
+ });
861
+
862
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
863
+ console.log(chalk.cyan('║') + chalk.dim(' Commands: list, approve <ids>, cancel <ids>, process, back ') + chalk.cyan('║'));
864
+ console.log(chalk.cyan('╚════════════════════════════════════════════════════════════════╝\n'));
865
+ }
866
+
867
+ } catch (error: any) {
868
+ spinner.fail(chalk.red('Failed to load payout dashboard'));
869
+ if (error.response?.status === 403) {
870
+ console.log(chalk.red('\n ⛔ Access Denied: Only managers and executors can access payouts\n'));
871
+ } else {
872
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
873
+ }
874
+ }
875
+ };
876
+
877
+ const listPayoutsCmd = async (options: { plan?: string; status?: string; page?: number }) => {
878
+ const spinner = ora('Loading payouts...').start();
879
+ const compact = isCompact();
880
+
881
+ try {
882
+ const data = await api.listPayouts({
883
+ plan: options.plan,
884
+ status: options.status || 'pending',
885
+ page: options.page || 1,
886
+ limit: compact ? 10 : 20,
887
+ });
888
+ const payouts: Payout[] = data.data || [];
889
+ spinner.stop();
890
+
891
+ if (payouts.length === 0) {
892
+ console.log(chalk.dim('\n No payouts found\n'));
893
+ return;
894
+ }
895
+
896
+ if (compact) {
897
+ // Compact list view
898
+ console.log('');
899
+ payouts.forEach((p) => {
900
+ const planColor = PLAN_COLORS[p.plan] || chalk.white;
901
+ const statusIcon = p.payed ? chalk.green('✓') : p.triggered ? chalk.yellow('~') : chalk.red('●');
902
+ const email = p.user_email.length > 12 ? p.user_email.substring(0, 10) + '..' : p.user_email;
903
+ console.log(` ${statusIcon} ${chalk.dim('#' + p.id)} ${planColor(p.plan.substring(0, 6))} ${email} ${chalk.green(p.amount.toFixed(2))} ${p.crypto_type}`);
904
+ });
905
+ console.log(chalk.dim(`\n Pg ${data.pagination.page}/${data.pagination.pages} (${data.pagination.total})\n`));
906
+ } else {
907
+ const table = new Table({
908
+ head: [
909
+ chalk.cyan('ID'),
910
+ chalk.cyan('User'),
911
+ chalk.cyan('Plan'),
912
+ chalk.cyan('Amount'),
913
+ chalk.cyan('Crypto'),
914
+ chalk.cyan('Wallet'),
915
+ chalk.cyan('Status'),
916
+ ],
917
+ style: { head: [], border: [] },
918
+ colWidths: [8, 22, 12, 14, 10, 18, 10],
919
+ });
920
+
921
+ payouts.forEach((p) => {
922
+ const planColor = PLAN_COLORS[p.plan] || chalk.white;
923
+ const statusIcon = p.payed ? chalk.green('✓ Paid') : p.triggered ? chalk.yellow('⏳ Proc') : chalk.red('● Pend');
924
+
925
+ table.push([
926
+ chalk.dim(String(p.id)),
927
+ p.user_email.length > 20 ? p.user_email.substring(0, 18) + '..' : p.user_email,
928
+ planColor(p.plan),
929
+ chalk.green('$' + p.amount.toFixed(2)),
930
+ p.crypto_type,
931
+ formatWallet(p.wallet_address, 16),
932
+ statusIcon,
933
+ ]);
934
+ });
935
+
936
+ console.log('\n' + table.toString());
937
+ console.log(chalk.dim(`\n Page ${data.pagination.page}/${data.pagination.pages} (${data.pagination.total} total)\n`));
938
+ }
939
+
940
+ } catch (error: any) {
941
+ spinner.fail(chalk.red('Failed to load payouts'));
942
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
943
+ }
944
+ };
945
+
946
+ const approvePayoutsCmd = async (ids: number[]) => {
947
+ if (ids.length === 0) {
948
+ console.log(chalk.red('\n No payout IDs provided\n'));
949
+ return;
950
+ }
951
+
952
+ // Confirmation
953
+ const { confirm } = await inquirer.prompt([
954
+ {
955
+ type: 'confirm',
956
+ name: 'confirm',
957
+ message: chalk.yellow(`⚠️ Approve and send ${ids.length} payout(s) via TarPay?`),
958
+ default: false,
959
+ },
960
+ ]);
961
+
962
+ if (!confirm) {
963
+ console.log(chalk.dim('\n Cancelled\n'));
964
+ return;
965
+ }
966
+
967
+ const spinner = ora(`Processing ${ids.length} payouts...`).start();
968
+
969
+ try {
970
+ const result = await api.batchApprovePayouts(ids);
971
+ spinner.stop();
972
+
973
+ if (result.success) {
974
+ console.log('\n' + chalk.green('╔══════════════════════════════════════════╗'));
975
+ console.log(chalk.green('║') + chalk.bold.white(' ✓ PAYOUTS APPROVED & SUBMITTED ') + chalk.green('║'));
976
+ console.log(chalk.green('╠══════════════════════════════════════════╣'));
977
+ console.log(chalk.green('║') + ` Successful: ${chalk.bold.green(result.success_count)}`.padEnd(42) + chalk.green('║'));
978
+ console.log(chalk.green('║') + ` Failed: ${chalk.bold.red(result.failed_count)}`.padEnd(42) + chalk.green('║'));
979
+ console.log(chalk.green('║') + ` Total: ${chalk.bold.yellow('$' + result.total_amount.toFixed(2))}`.padEnd(42) + chalk.green('║'));
980
+ console.log(chalk.green('╚══════════════════════════════════════════╝\n'));
981
+
982
+ if (result.errors && result.errors.length > 0) {
983
+ console.log(chalk.red(' Errors:'));
984
+ result.errors.forEach((err: string) => {
985
+ console.log(chalk.dim(` - ${err}`));
986
+ });
987
+ console.log('');
988
+ }
989
+ } else {
990
+ console.log(chalk.red(`\n Failed: ${result.error || 'Unknown error'}\n`));
991
+ }
992
+ } catch (error: any) {
993
+ spinner.fail(chalk.red('Failed to approve payouts'));
994
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
995
+ }
996
+ };
997
+
998
+ const cancelPayoutsCmd = async (ids: number[]) => {
999
+ if (ids.length === 0) {
1000
+ console.log(chalk.red('\n No payout IDs provided\n'));
1001
+ return;
1002
+ }
1003
+
1004
+ // Ask for cooldown options
1005
+ const { confirm, addCooldown, cooldownHours, cooldownReason } = await inquirer.prompt([
1006
+ {
1007
+ type: 'confirm',
1008
+ name: 'confirm',
1009
+ message: chalk.yellow(`⚠️ Cancel ${ids.length} payout(s) and return funds to wallets?`),
1010
+ default: false,
1011
+ },
1012
+ {
1013
+ type: 'confirm',
1014
+ name: 'addCooldown',
1015
+ message: 'Add withdrawal cooldown for these users?',
1016
+ default: false,
1017
+ when: (answers: any) => answers.confirm,
1018
+ },
1019
+ {
1020
+ type: 'list',
1021
+ name: 'cooldownHours',
1022
+ message: 'Cooldown duration:',
1023
+ choices: [
1024
+ { name: '24 hours', value: 24 },
1025
+ { name: '48 hours', value: 48 },
1026
+ { name: '72 hours', value: 72 },
1027
+ { name: '1 week', value: 168 },
1028
+ ],
1029
+ when: (answers: any) => answers.addCooldown,
1030
+ },
1031
+ {
1032
+ type: 'input',
1033
+ name: 'cooldownReason',
1034
+ message: 'Reason for cancellation:',
1035
+ default: 'Payout cancelled by admin',
1036
+ when: (answers: any) => answers.addCooldown,
1037
+ },
1038
+ ]);
1039
+
1040
+ if (!confirm) {
1041
+ console.log(chalk.dim('\n Cancelled\n'));
1042
+ return;
1043
+ }
1044
+
1045
+ const spinner = ora(`Cancelling ${ids.length} payouts...`).start();
1046
+
1047
+ try {
1048
+ const options: any = {};
1049
+ if (addCooldown) {
1050
+ options.cooldown_hours = cooldownHours;
1051
+ options.cooldown_reason = cooldownReason;
1052
+ }
1053
+
1054
+ const result = await api.batchCancelPayouts(ids, options);
1055
+ spinner.stop();
1056
+
1057
+ if (result.success) {
1058
+ console.log('\n' + chalk.yellow('╔══════════════════════════════════════════╗'));
1059
+ console.log(chalk.yellow('║') + chalk.bold.white(' ↩ PAYOUTS CANCELLED & RETURNED ') + chalk.yellow('║'));
1060
+ console.log(chalk.yellow('╠══════════════════════════════════════════╣'));
1061
+ console.log(chalk.yellow('║') + ` Cancelled: ${chalk.bold.green(result.success_count)}`.padEnd(42) + chalk.yellow('║'));
1062
+ console.log(chalk.yellow('║') + ` Failed: ${chalk.bold.red(result.failed_count)}`.padEnd(42) + chalk.yellow('║'));
1063
+ console.log(chalk.yellow('║') + ` Returned: ${chalk.bold.green('$' + result.total_amount.toFixed(2))}`.padEnd(42) + chalk.yellow('║'));
1064
+ console.log(chalk.yellow('╚══════════════════════════════════════════╝\n'));
1065
+
1066
+ if (result.errors && result.errors.length > 0) {
1067
+ console.log(chalk.red(' Errors:'));
1068
+ result.errors.forEach((err: any) => {
1069
+ console.log(chalk.dim(` - Payout ${err.payout_id}: ${err.error}`));
1070
+ });
1071
+ console.log('');
1072
+ }
1073
+ } else {
1074
+ console.log(chalk.red(`\n Failed: ${result.error || 'Unknown error'}\n`));
1075
+ }
1076
+ } catch (error: any) {
1077
+ spinner.fail(chalk.red('Failed to cancel payouts'));
1078
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
1079
+ }
1080
+ };
1081
+
1082
+ const selectPayoutsInteractive = async (plan?: string): Promise<number[]> => {
1083
+ const spinner = ora('Loading payouts...').start();
1084
+
1085
+ try {
1086
+ const data = await api.listPayouts({ plan, status: 'pending', limit: 100 });
1087
+ const payouts: Payout[] = data.data || [];
1088
+ spinner.stop();
1089
+
1090
+ if (payouts.length === 0) {
1091
+ console.log(chalk.dim('\n No pending payouts found\n'));
1092
+ return [];
1093
+ }
1094
+
1095
+ // Group by plan for better display
1096
+ const choices = payouts.map((p) => {
1097
+ const planColor = PLAN_COLORS[p.plan] || chalk.white;
1098
+ const planIcon = PLAN_ICONS[p.plan] || '📋';
1099
+ return {
1100
+ name: `${planIcon} ${chalk.dim('#' + p.id)} ${planColor(p.plan.padEnd(12))} ${p.user_email.substring(0, 25).padEnd(25)} ${chalk.green('$' + p.amount.toFixed(2).padStart(10))} ${chalk.dim(p.crypto_type)}`,
1101
+ value: p.id,
1102
+ short: `#${p.id}`,
1103
+ };
1104
+ });
1105
+
1106
+ const { selected } = await inquirer.prompt([
1107
+ {
1108
+ type: 'checkbox',
1109
+ name: 'selected',
1110
+ message: 'Select payouts to process:',
1111
+ choices,
1112
+ pageSize: 15,
1113
+ loop: false,
1114
+ },
1115
+ ]);
1116
+
1117
+ return selected;
1118
+ } catch (error: any) {
1119
+ spinner.fail(chalk.red('Failed to load payouts'));
1120
+ return [];
1121
+ }
1122
+ };
1123
+
1124
+ const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'cancel') => {
1125
+ console.log('\n' + chalk.cyan('╔══════════════════════════════════════════════════════════════╗'));
1126
+ console.log(chalk.cyan('║') + chalk.bold.white(` 🔄 PROCESSING ${ids.length} PAYOUT(S)... `) + chalk.cyan('║'));
1127
+ console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════╣'));
1128
+
1129
+ const startTime = Date.now();
1130
+
1131
+ // Show progress
1132
+ let processed = 0;
1133
+ const progressBar = (current: number, total: number) => {
1134
+ const percent = Math.round((current / total) * 100);
1135
+ const filled = Math.round(percent / 2);
1136
+ const empty = 50 - filled;
1137
+ return chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(empty)) + ` ${percent}%`;
1138
+ };
1139
+
1140
+ // Simulate progress while waiting for API
1141
+ const progressInterval = setInterval(() => {
1142
+ processed = Math.min(processed + Math.random() * 10, 90);
1143
+ process.stdout.write(`\r${chalk.cyan('║')} ${progressBar(processed, 100)} ${chalk.cyan('║')}`);
1144
+ }, 200);
1145
+
1146
+ try {
1147
+ let result;
1148
+ if (action === 'approve') {
1149
+ result = await api.batchApprovePayouts(ids);
1150
+ } else {
1151
+ result = await api.batchCancelPayouts(ids);
1152
+ }
1153
+
1154
+ clearInterval(progressInterval);
1155
+ processed = 100;
1156
+ process.stdout.write(`\r${chalk.cyan('║')} ${progressBar(100, 100)} ${chalk.cyan('║')}\n`);
1157
+
1158
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
1159
+ console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════╣'));
1160
+
1161
+ if (result.success) {
1162
+ const icon = action === 'approve' ? '✓' : '↩';
1163
+ const verb = action === 'approve' ? 'APPROVED & SENT' : 'CANCELLED & RETURNED';
1164
+ console.log(chalk.cyan('║') + chalk.green(` ${icon} ${verb} `) + chalk.cyan('║'));
1165
+ console.log(chalk.cyan('║') + ` Successful: ${chalk.bold.green(String(result.success_count).padEnd(5))} Failed: ${chalk.bold.red(String(result.failed_count).padEnd(5))} Time: ${chalk.dim(elapsed + 's')}`.padEnd(62) + chalk.cyan('║'));
1166
+ console.log(chalk.cyan('║') + ` Total Amount: ${chalk.bold.yellow('$' + result.total_amount.toFixed(2))}`.padEnd(62) + chalk.cyan('║'));
1167
+ } else {
1168
+ console.log(chalk.cyan('║') + chalk.red(` ✗ FAILED: ${result.error || 'Unknown error'}`.substring(0, 58).padEnd(60)) + chalk.cyan('║'));
1169
+ }
1170
+
1171
+ console.log(chalk.cyan('╚══════════════════════════════════════════════════════════════╝\n'));
1172
+
1173
+ // Show errors if any
1174
+ if (result.errors && result.errors.length > 0) {
1175
+ console.log(chalk.red(' Errors:'));
1176
+ result.errors.slice(0, 5).forEach((err: any) => {
1177
+ const errMsg = typeof err === 'string' ? err : `Payout ${err.payout_id}: ${err.error}`;
1178
+ console.log(chalk.dim(` - ${errMsg}`));
1179
+ });
1180
+ if (result.errors.length > 5) {
1181
+ console.log(chalk.dim(` ... and ${result.errors.length - 5} more errors`));
1182
+ }
1183
+ console.log('');
1184
+ }
1185
+
1186
+ return result;
1187
+ } catch (error: any) {
1188
+ clearInterval(progressInterval);
1189
+ console.log(chalk.cyan('║') + chalk.red(' ✗ ERROR: ' + (error.response?.data?.error || error.message).substring(0, 50).padEnd(52)) + chalk.cyan('║'));
1190
+ console.log(chalk.cyan('╚══════════════════════════════════════════════════════════════╝\n'));
1191
+ return { success: false };
1192
+ }
1193
+ };
1194
+
1195
+ const runPayoutsShell = async () => {
1196
+ setTitle('Payouts');
1197
+ await showPayoutDashboard();
1198
+
1199
+ const runLoop = async () => {
1200
+ const { command } = await inquirer.prompt([
1201
+ {
1202
+ type: 'input',
1203
+ name: 'command',
1204
+ message: chalk.yellow('payouts') + chalk.cyan(' > '),
1205
+ prefix: '',
1206
+ },
1207
+ ]);
1208
+
1209
+ const parts = command.trim().split(/\s+/);
1210
+ const cmd = parts[0]?.toLowerCase();
1211
+ const args = parts.slice(1);
1212
+
1213
+ if (cmd === 'back' || cmd === 'exit' || cmd === 'q') {
1214
+ setTitle('Home');
1215
+ return;
1216
+ }
1217
+
1218
+ if (cmd === '') {
1219
+ return runLoop();
1220
+ }
1221
+
1222
+ try {
1223
+ switch (cmd) {
1224
+ case 'refresh':
1225
+ case 'r':
1226
+ await showPayoutDashboard();
1227
+ break;
1228
+ case 'list':
1229
+ case 'ls':
1230
+ const listPlan = args.find((a: string) => !a.startsWith('-'));
1231
+ await listPayoutsCmd({ plan: listPlan });
1232
+ break;
1233
+ case 'select':
1234
+ case 's':
1235
+ // Interactive selection mode
1236
+ const selectPlan = args.find((a: string) => !a.startsWith('-'));
1237
+ const selectedIds = await selectPayoutsInteractive(selectPlan);
1238
+ if (selectedIds.length > 0) {
1239
+ const { action } = await inquirer.prompt([
1240
+ {
1241
+ type: 'list',
1242
+ name: 'action',
1243
+ message: `What do you want to do with ${selectedIds.length} selected payout(s)?`,
1244
+ choices: [
1245
+ { name: chalk.green('✓ Approve & Send via TarPay'), value: 'approve' },
1246
+ { name: chalk.yellow('↩ Cancel & Return to Wallets'), value: 'cancel' },
1247
+ { name: chalk.dim('✗ Cancel'), value: 'none' },
1248
+ ],
1249
+ },
1250
+ ]);
1251
+
1252
+ if (action === 'approve') {
1253
+ const { confirm } = await inquirer.prompt([
1254
+ {
1255
+ type: 'confirm',
1256
+ name: 'confirm',
1257
+ message: chalk.yellow(`⚠️ Confirm: Send ${selectedIds.length} payout(s)?`),
1258
+ default: false,
1259
+ },
1260
+ ]);
1261
+ if (confirm) {
1262
+ await processPayoutsWithProgress(selectedIds, 'approve');
1263
+ }
1264
+ } else if (action === 'cancel') {
1265
+ const { confirm } = await inquirer.prompt([
1266
+ {
1267
+ type: 'confirm',
1268
+ name: 'confirm',
1269
+ message: chalk.yellow(`⚠️ Confirm: Cancel ${selectedIds.length} payout(s)?`),
1270
+ default: false,
1271
+ },
1272
+ ]);
1273
+ if (confirm) {
1274
+ await processPayoutsWithProgress(selectedIds, 'cancel');
1275
+ }
1276
+ }
1277
+ }
1278
+ break;
1279
+ case 'approve':
1280
+ case 'a':
1281
+ if (args.length === 0) {
1282
+ // Interactive mode
1283
+ const approveSelected = await selectPayoutsInteractive();
1284
+ if (approveSelected.length > 0) {
1285
+ const { confirm } = await inquirer.prompt([
1286
+ {
1287
+ type: 'confirm',
1288
+ name: 'confirm',
1289
+ message: chalk.yellow(`⚠️ Approve & send ${approveSelected.length} payout(s)?`),
1290
+ default: false,
1291
+ },
1292
+ ]);
1293
+ if (confirm) {
1294
+ await processPayoutsWithProgress(approveSelected, 'approve');
1295
+ }
1296
+ }
1297
+ } else {
1298
+ const approveIds = args.map(Number).filter((n: number) => !isNaN(n));
1299
+ await processPayoutsWithProgress(approveIds, 'approve');
1300
+ }
1301
+ break;
1302
+ case 'cancel':
1303
+ case 'c':
1304
+ if (args.length === 0) {
1305
+ // Interactive mode
1306
+ const cancelSelected = await selectPayoutsInteractive();
1307
+ if (cancelSelected.length > 0) {
1308
+ const { confirm } = await inquirer.prompt([
1309
+ {
1310
+ type: 'confirm',
1311
+ name: 'confirm',
1312
+ message: chalk.yellow(`⚠️ Cancel ${cancelSelected.length} payout(s)?`),
1313
+ default: false,
1314
+ },
1315
+ ]);
1316
+ if (confirm) {
1317
+ await processPayoutsWithProgress(cancelSelected, 'cancel');
1318
+ }
1319
+ }
1320
+ } else {
1321
+ const cancelIds = args.map(Number).filter((n: number) => !isNaN(n));
1322
+ await processPayoutsWithProgress(cancelIds, 'cancel');
1323
+ }
1324
+ break;
1325
+ case 'hermes':
1326
+ case 'alpha':
1327
+ case 'mematic':
1328
+ case 'booster':
1329
+ case 'validator_v2':
1330
+ await listPayoutsCmd({ plan: cmd });
1331
+ break;
1332
+ case 'process':
1333
+ // Process specific plan with interactive selection
1334
+ const { processPlan } = await inquirer.prompt([
1335
+ {
1336
+ type: 'list',
1337
+ name: 'processPlan',
1338
+ message: 'Select plan to process:',
1339
+ choices: [
1340
+ { name: '⚡ Hermes', value: 'hermes' },
1341
+ { name: '🔷 Alpha', value: 'alpha' },
1342
+ { name: '💎 Mematic', value: 'mematic' },
1343
+ { name: '🔐 Validator V2', value: 'validator_v2' },
1344
+ { name: '🚀 Booster', value: 'booster' },
1345
+ { name: '📋 All Plans', value: undefined },
1346
+ ],
1347
+ },
1348
+ ]);
1349
+ const toProcess = await selectPayoutsInteractive(processPlan);
1350
+ if (toProcess.length > 0) {
1351
+ const { confirmProcess } = await inquirer.prompt([
1352
+ {
1353
+ type: 'confirm',
1354
+ name: 'confirmProcess',
1355
+ message: chalk.yellow(`⚠️ Approve & send ${toProcess.length} payout(s)?`),
1356
+ default: false,
1357
+ },
1358
+ ]);
1359
+ if (confirmProcess) {
1360
+ await processPayoutsWithProgress(toProcess, 'approve');
1361
+ }
1362
+ }
1363
+ break;
1364
+ case 'all':
1365
+ // Approve ALL pending payouts
1366
+ const allSpinner = ora('Loading all pending payouts...').start();
1367
+ try {
1368
+ const allData = await api.listPayouts({ status: 'pending', limit: 500 });
1369
+ const allPayouts: Payout[] = allData.data || [];
1370
+ allSpinner.stop();
1371
+
1372
+ if (allPayouts.length === 0) {
1373
+ console.log(chalk.green('\n ✓ No pending payouts to approve\n'));
1374
+ break;
1375
+ }
1376
+
1377
+ const totalAmount = allPayouts.reduce((sum, p) => sum + p.amount, 0);
1378
+ console.log(chalk.yellow(`\n ⚠️ APPROVE ALL: ${allPayouts.length} payouts totaling $${totalAmount.toFixed(2)}\n`));
1379
+
1380
+ const { confirmAll } = await inquirer.prompt([
1381
+ {
1382
+ type: 'confirm',
1383
+ name: 'confirmAll',
1384
+ message: chalk.red.bold(`Are you sure you want to approve ALL ${allPayouts.length} pending payouts?`),
1385
+ default: false,
1386
+ },
1387
+ ]);
1388
+
1389
+ if (confirmAll) {
1390
+ const allIds = allPayouts.map((p) => p.id);
1391
+ await processPayoutsWithProgress(allIds, 'approve');
1392
+ } else {
1393
+ console.log(chalk.dim('\n Cancelled\n'));
1394
+ }
1395
+ } catch (e: any) {
1396
+ allSpinner.fail(chalk.red('Failed to load payouts'));
1397
+ }
1398
+ break;
1399
+ case 'help':
1400
+ case '?':
1401
+ console.log(chalk.cyan('\n Payout Commands:'));
1402
+ console.log(chalk.dim(' ────────────────────'));
1403
+ console.log(' refresh, r Refresh dashboard');
1404
+ console.log(' list [plan] List pending payouts');
1405
+ console.log(' select [plan] Interactive payout selection');
1406
+ console.log(' approve [ids] Approve payouts (interactive if no IDs)');
1407
+ console.log(chalk.yellow(' all Approve ALL pending payouts'));
1408
+ console.log(' cancel [ids] Cancel payouts (interactive if no IDs)');
1409
+ console.log(' process Process payouts by plan (interactive)');
1410
+ console.log(' hermes/alpha/etc List payouts for specific plan');
1411
+ console.log(' back, q Return to main menu');
1412
+ console.log('');
1413
+ break;
1414
+ default:
1415
+ console.log(chalk.red(` Unknown command: ${cmd}`));
1416
+ console.log(chalk.dim(' Type "help" for available commands\n'));
1417
+ }
1418
+ } catch (e: any) {
1419
+ console.log(chalk.red(` Error: ${e.message}\n`));
1420
+ }
1421
+
1422
+ await runLoop();
1423
+ };
1424
+
1425
+ await runLoop();
1426
+ };
1427
+
1428
+ // ==================== LOGIN ====================
1429
+ program
1430
+ .command('login')
1431
+ .description('Authenticate with Mission Control via Google SSO')
1432
+ .option('-t, --token <token>', 'API token (skip browser login)')
1433
+ .option('--type <type>', 'Token type (bearer or token)', 'bearer')
1434
+ .option('--url <url>', 'API URL', 'https://api.targlobal.org')
1435
+ .action(async (options) => {
1436
+ setTitle('Login');
1437
+ let token = options.token;
1438
+
1439
+ if (!token) {
1440
+ console.log(miniLogo);
1441
+ console.log('');
1442
+ console.log(chalk.cyan(' Opening browser for Google SSO login...'));
1443
+ console.log('');
1444
+
1445
+ const authUrl = 'https://agents.targlobal.org/cli-auth';
1446
+
1447
+ try {
1448
+ const open = (await import('open')).default;
1449
+ await open(authUrl);
1450
+ console.log(chalk.dim(` If browser doesn't open, visit:`));
1451
+ console.log(chalk.white(` ${authUrl}`));
1452
+ console.log('');
1453
+ } catch (e) {
1454
+ console.log(chalk.dim(` Open this URL in your browser:`));
1455
+ console.log(chalk.white(` ${authUrl}`));
1456
+ console.log('');
1457
+ }
1458
+
1459
+ const answers = await inquirer.prompt([
1460
+ {
1461
+ type: 'password',
1462
+ name: 'token',
1463
+ message: 'Paste your token from the browser:',
1464
+ mask: '*',
1465
+ },
1466
+ ]);
1467
+ token = answers.token;
1468
+ options.type = 'bearer';
1469
+ }
1470
+
1471
+ setConfig('token', token);
1472
+ setConfig('tokenType', options.type);
1473
+ setConfig('apiUrl', options.url);
1474
+ resetApiClient();
1475
+
1476
+ const spinner = ora('Verifying credentials...').start();
1477
+
1478
+ try {
1479
+ // Fetch user info and stats
1480
+ await fetchUserInfo();
1481
+ const stats = await api.getStats();
1482
+ spinner.succeed(chalk.green('Authenticated successfully!'));
1483
+
1484
+ const user = getUser();
1485
+
1486
+ // Show welcome dashboard
1487
+ console.log('');
1488
+ console.log(chalk.cyan('╔════════════════════════════════════════════════════════════════╗'));
1489
+ console.log(chalk.cyan('║') + chalk.bold.white(` Welcome back, ${(user?.name || 'Agent').padEnd(45)}`) + chalk.cyan('║'));
1490
+ console.log(chalk.cyan('╠════════════════════════════════════════════════════════════════╣'));
1491
+ console.log(chalk.cyan('║') + ` ${chalk.yellow('⚡')} My Tasks: ${chalk.bold.white(String(stats.my_tasks).padEnd(8))} ${chalk.red('●')} Critical: ${chalk.bold.red(String(stats.critical_count).padEnd(8))} ${chalk.yellow('!')} Overdue: ${chalk.bold.yellow(String(stats.overdue_tasks))}`.padEnd(71) + chalk.cyan('║'));
1492
+ console.log(chalk.cyan('╚════════════════════════════════════════════════════════════════╝'));
1493
+ console.log('');
1494
+
1495
+ // Ask if they want to enter interactive mode
1496
+ const { startShell } = await inquirer.prompt([
1497
+ {
1498
+ type: 'confirm',
1499
+ name: 'startShell',
1500
+ message: 'Enter Mission Control?',
1501
+ default: true,
1502
+ },
1503
+ ]);
1504
+
1505
+ if (startShell) {
1506
+ // Launch interactive shell
1507
+ await runInteractiveShell();
1508
+ }
1509
+ } catch (error: any) {
1510
+ spinner.fail(chalk.red('Authentication failed'));
1511
+ clearConfig();
1512
+ console.log(chalk.dim(' Check your token and try again\n'));
1513
+ }
1514
+ });
1515
+
1516
+ // ==================== LOGOUT ====================
1517
+ program
1518
+ .command('logout')
1519
+ .description('Clear stored credentials')
1520
+ .action(() => {
1521
+ clearConfig();
1522
+ console.log(chalk.green('\n[OK] Logged out successfully\n'));
1523
+ });
1524
+
1525
+ // ==================== UPDATE ====================
1526
+ program
1527
+ .command('update')
1528
+ .description('Update Mission Control CLI to latest version')
1529
+ .action(async () => {
1530
+ setTitle('Updating');
1531
+ console.log(miniLogo);
1532
+ console.log('');
1533
+
1534
+ const spinner = ora('Checking for updates...').start();
1535
+
1536
+ try {
1537
+ // Re-run the installer
1538
+ spinner.text = 'Downloading latest version...';
1539
+
1540
+ execSync('curl -fsSL https://targlobal.org/download/mc | bash', {
1541
+ stdio: 'inherit',
1542
+ });
1543
+
1544
+ spinner.succeed(chalk.green('Updated successfully!'));
1545
+ console.log(chalk.dim(' Restart your terminal to use the new version\n'));
1546
+ } catch (error: any) {
1547
+ spinner.fail(chalk.red('Update failed'));
1548
+ console.log(chalk.dim(' Try manually: curl -fsSL https://targlobal.org/download/mc | bash\n'));
1549
+ }
1550
+ });
1551
+
1552
+ // ==================== TASKS (list) ====================
1553
+ program
1554
+ .command('tasks')
1555
+ .alias('ls')
1556
+ .description('List your tasks')
1557
+ .option('-a, --all', 'Show all tasks, not just mine')
1558
+ .option('-b, --board <board>', 'Filter by board')
1559
+ .option('-p, --priority <priority>', 'Filter by priority')
1560
+ .action(async (options) => {
1561
+ setTitle('Tasks');
1562
+ requireAuth();
1563
+ await listTasksCmd(options);
1564
+ showStatusBar();
1565
+ });
1566
+
1567
+ // ==================== SHOW (task detail) ====================
1568
+ program
1569
+ .command('show <taskId>')
1570
+ .alias('view')
1571
+ .description('Show task details')
1572
+ .action(async (taskId) => {
1573
+ setTitle(`Task ${taskId}`);
1574
+ requireAuth();
1575
+ await showTaskCmd(taskId);
1576
+ showStatusBar();
1577
+ });
1578
+
1579
+ // ==================== NEW (create task) ====================
1580
+ program
1581
+ .command('new <title>')
1582
+ .alias('create')
1583
+ .description('Create a new task')
1584
+ .option('-d, --description <desc>', 'Task description')
1585
+ .option('-p, --priority <priority>', 'Priority: critical, high, medium, low', 'medium')
1586
+ .option('-t, --type <type>', 'Type: bug, feature, improvement, task', 'task')
1587
+ .option('-b, --board <board>', 'Board slug', 'backend')
1588
+ .action(async (title, options) => {
1589
+ setTitle('New Task');
1590
+ requireAuth();
1591
+ const spinner = ora('Creating task...').start();
1592
+
1593
+ try {
1594
+ const boards = await api.listBoards();
1595
+ const board = boards.boards.find((b: Board) => b.slug === options.board);
1596
+
1597
+ if (!board) {
1598
+ spinner.fail(chalk.red(`Board '${options.board}' not found`));
1599
+ console.log(chalk.dim(' Available boards: ' + boards.boards.map((b: Board) => b.slug).join(', ') + '\n'));
1600
+ return;
1601
+ }
1602
+
1603
+ const task = await api.createTask({
1604
+ title,
1605
+ description: options.description,
1606
+ board: board.id,
1607
+ priority: options.priority,
1608
+ task_type: options.type,
1609
+ });
1610
+
1611
+ spinner.succeed(chalk.green(`Task created: ${task.display_id}`));
1612
+ console.log(chalk.dim(` ${task.title}\n`));
1613
+ } catch (error: any) {
1614
+ spinner.fail(chalk.red('Failed to create task'));
1615
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
1616
+ }
1617
+ showStatusBar();
1618
+ });
1619
+
1620
+ // ==================== START (move to In Progress) ====================
1621
+ program
1622
+ .command('start <taskId>')
1623
+ .description('Move task to In Progress')
1624
+ .action(async (taskId) => {
1625
+ setTitle('Starting Task');
1626
+ requireAuth();
1627
+ await startTaskCmd(taskId);
1628
+ showStatusBar();
1629
+ });
1630
+
1631
+ // ==================== DONE (complete task) ====================
1632
+ program
1633
+ .command('done <taskId>')
1634
+ .alias('complete')
1635
+ .description('Mark task as complete')
1636
+ .action(async (taskId) => {
1637
+ setTitle('Completing Task');
1638
+ requireAuth();
1639
+ await completeTaskCmd(taskId);
1640
+ showStatusBar();
1641
+ });
1642
+
1643
+ // ==================== COMMENT ====================
1644
+ program
1645
+ .command('comment <taskId> <message>')
1646
+ .alias('note')
1647
+ .description('Add a comment to a task')
1648
+ .action(async (taskId, message) => {
1649
+ setTitle('Adding Comment');
1650
+ requireAuth();
1651
+ const spinner = ora('Adding comment...').start();
1652
+
1653
+ try {
1654
+ await api.addComment(parseTaskNumber(taskId), message);
1655
+ spinner.succeed(chalk.green('Comment added'));
1656
+ } catch (error: any) {
1657
+ spinner.fail(chalk.red('Failed to add comment'));
1658
+ console.log(chalk.dim(` ${error.response?.data?.error || error.message}\n`));
1659
+ }
1660
+ showStatusBar();
1661
+ });
1662
+
1663
+ // ==================== BOARDS ====================
1664
+ program
1665
+ .command('boards')
1666
+ .description('List available boards')
1667
+ .action(async () => {
1668
+ setTitle('Boards');
1669
+ requireAuth();
1670
+ await listBoardsCmd();
1671
+ showStatusBar();
1672
+ });
1673
+
1674
+ // ==================== STATS ====================
1675
+ program
1676
+ .command('stats')
1677
+ .description('Show task statistics')
1678
+ .action(async () => {
1679
+ setTitle('Stats');
1680
+ requireAuth();
1681
+ await showStatsCmd();
1682
+ showStatusBar();
1683
+ });
1684
+
1685
+ // ==================== URGENT ====================
1686
+ program
1687
+ .command('urgent')
1688
+ .description('Show critical and overdue tasks')
1689
+ .action(async () => {
1690
+ setTitle('Urgent');
1691
+ requireAuth();
1692
+ await showUrgentCmd();
1693
+ showStatusBar();
1694
+ });
1695
+
1696
+ // ==================== WHOAMI ====================
1697
+ program
1698
+ .command('whoami')
1699
+ .description('Show current user and configuration')
1700
+ .action(() => {
1701
+ setTitle('Who Am I');
1702
+ showWhoami();
1703
+ });
1704
+
1705
+ // ==================== CONFIG ====================
1706
+ program
1707
+ .command('config [key] [value]')
1708
+ .description('View or set configuration (compact: auto/on/off)')
1709
+ .action((key, value) => {
1710
+ setTitle('Config');
1711
+ const config = getConfig();
1712
+
1713
+ if (!key) {
1714
+ // Show all config
1715
+ console.log('\n' + chalk.bold(' Mission Control Config'));
1716
+ console.log(chalk.dim(' ──────────────────────'));
1717
+ console.log(` compact: ${chalk.cyan(config.compact)} ${chalk.dim('(auto/on/off)')}`);
1718
+ console.log(` apiUrl: ${config.apiUrl}`);
1719
+ console.log(` token: ${config.token ? chalk.green('●') + ' set' : chalk.red('○') + ' not set'}`);
1720
+ console.log('');
1721
+ console.log(chalk.dim(' Usage: mc config <key> <value>'));
1722
+ console.log(chalk.dim(' Example: mc config compact on'));
1723
+ console.log('');
1724
+ return;
1725
+ }
1726
+
1727
+ if (key === 'compact') {
1728
+ if (!value || !['auto', 'on', 'off'].includes(value)) {
1729
+ console.log(chalk.red('\n Invalid value. Use: auto, on, or off\n'));
1730
+ return;
1731
+ }
1732
+ setConfig('compact', value);
1733
+ console.log(chalk.green(`\n ✓ Compact mode set to: ${value}\n`));
1734
+
1735
+ if (value === 'on') {
1736
+ console.log(chalk.dim(' Mobile-friendly compact UI enabled'));
1737
+ } else if (value === 'off') {
1738
+ console.log(chalk.dim(' Full desktop UI enabled'));
1739
+ } else {
1740
+ console.log(chalk.dim(' Will auto-detect based on terminal width (<60 = compact)'));
1741
+ }
1742
+ console.log('');
1743
+ } else {
1744
+ console.log(chalk.red(`\n Unknown config key: ${key}`));
1745
+ console.log(chalk.dim(' Available keys: compact\n'));
1746
+ }
1747
+ });
1748
+
1749
+ // ==================== PAYOUTS (Admin) ====================
1750
+ program
1751
+ .command('payouts')
1752
+ .alias('pay')
1753
+ .description('Payout processing dashboard (managers/executors only)')
1754
+ .option('-l, --list', 'List pending payouts')
1755
+ .option('-p, --plan <plan>', 'Filter by plan (hermes, alpha, mematic, validator_v2, booster)')
1756
+ .option('-a, --approve <ids...>', 'Approve payout IDs')
1757
+ .option('-c, --cancel <ids...>', 'Cancel payout IDs')
1758
+ .action(async (options) => {
1759
+ setTitle('Payouts');
1760
+ requireAuth();
1761
+
1762
+ if (options.list) {
1763
+ await listPayoutsCmd({ plan: options.plan });
1764
+ } else if (options.approve) {
1765
+ const ids = options.approve.map(Number).filter((n: number) => !isNaN(n));
1766
+ if (ids.length > 0) {
1767
+ await processPayoutsWithProgress(ids, 'approve');
1768
+ }
1769
+ } else if (options.cancel) {
1770
+ const ids = options.cancel.map(Number).filter((n: number) => !isNaN(n));
1771
+ if (ids.length > 0) {
1772
+ await processPayoutsWithProgress(ids, 'cancel');
1773
+ }
1774
+ } else {
1775
+ // Interactive dashboard mode
1776
+ await runPayoutsShell();
1777
+ }
1778
+ showStatusBar();
1779
+ });
1780
+
1781
+ // Default action - show help or run shell if authenticated
1782
+ if (process.argv.length === 2) {
1783
+ if (isAuthenticated()) {
1784
+ // Run interactive shell
1785
+ process.argv.push('shell');
1786
+ } else {
1787
+ // Show help
1788
+ program.outputHelp();
1789
+ }
1790
+ }
1791
+
1792
+ // Parse and run
1793
+ program.parse();