@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/.gitattributes +2 -0
- package/README.md +142 -0
- package/dist/api.d.ts +119 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +137 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1703 -0
- package/dist/index.js.map +1 -0
- package/install.sh +124 -0
- package/package.json +48 -0
- package/src/api.ts +233 -0
- package/src/config.ts +50 -0
- package/src/index.ts +1793 -0
- package/tsconfig.json +19 -0
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();
|