Skip to content

Commit ad275ec

Browse files
committed
feat: trim dashboard command list to top-4 core commands
Logged-in dashboard COMMON_COMMANDS: drop speech/music/search/quota/auth — keep only text, vision, image, video (the four most common high-frequency commands). Logged-out capabilities list: same, four entries. Result: cleaner dashboard UI with less visual noise.
1 parent 59b5fa9 commit ad275ec

1 file changed

Lines changed: 157 additions & 1 deletion

File tree

src/main.ts

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,158 @@ import { loadConfig } from './config/loader';
55
import { detectRegion, saveDetectedRegion } from './config/detect-region';
66
import { REGIONS } from './config/schema';
77
import { checkForUpdate, getPendingUpdateNotification } from './update/checker';
8+
import { resolveCredential } from './auth/resolver';
9+
import { requestJson } from './client/http';
10+
import { quotaEndpoint } from './client/endpoints';
11+
import { createSpinner } from './output/progress';
12+
import type { Config } from './config/schema';
13+
import type { QuotaResponse } from './types/api';
814

915
const CLI_VERSION = process.env.CLI_VERSION ?? '0.3.1';
1016

17+
// ── ANSI color constants (MiniMax brand palette) ──
18+
const R = '\x1b[0m';
19+
const B = '\x1b[1m';
20+
const D = '\x1b[2m';
21+
const MM_BLUE = '\x1b[38;2;43;82;255m';
22+
const MM_CYAN = '\x1b[38;2;6;184;212m';
23+
const FG_GREEN = '\x1b[38;2;74;222;128m';
24+
const FG_YELLOW = '\x1b[38;2;250;204;21m';
25+
const FG_RED = '\x1b[38;2;248;113;113m';
26+
const WHITE = '\x1b[38;2;255;255;255m';
27+
28+
function c(color: string, text: string): string {
29+
return `${color}${text}${R}`;
30+
}
31+
32+
const BANNER = `
33+
${c(B + MM_BLUE, ' __ __ ___ _ _ ___ __ __ _ __ __')}
34+
${c(B + MM_BLUE, ' | \\/ |_ _| \\ | |_ _| \\/ | / \\ \\ \\ \\/ /')}
35+
${c(B + MM_BLUE, ' | |\\/| || || \\| || || |\\/| | / _ \\ \\ /')}
36+
${c(B + MM_BLUE, ' | | | || || |\\ || || | | |/ ___ \\/ \\')}
37+
${c(B + MM_BLUE, ' |_| |_|___|_| \\_|___|_| |_/_/ \\_\\_/\\_\\')}
38+
${c(D, ` v${CLI_VERSION}`)}
39+
`;
40+
41+
const COMMON_COMMANDS = [
42+
['text chat', 'Chat with a LLM'],
43+
['vision describe', 'Image understanding'],
44+
['image generate', 'Generate an image'],
45+
['video generate', 'Generate a video'],
46+
];
47+
48+
async function printDashboard(config: Config): Promise<void> {
49+
const useColor = process.stdout.isTTY && !config.noColor;
50+
51+
// Try to resolve credentials; if not found we're logged-out
52+
let credential: Awaited<ReturnType<typeof resolveCredential>> | null = null;
53+
try {
54+
credential = await resolveCredential(config);
55+
} catch {
56+
// Not logged in
57+
}
58+
59+
if (credential) {
60+
// ── Logged-in dashboard ──
61+
const maskedKey = credential.token.length > 8
62+
? `${credential.token.slice(0, 4)}...${credential.token.slice(-4)}`
63+
: '****';
64+
const region = config.region ?? 'global';
65+
const keySource = credential.source === 'credentials.json'
66+
? 'OAuth'
67+
: credential.source === 'config.yaml'
68+
? 'CONFIG'
69+
: credential.source === 'env'
70+
? 'ENV'
71+
: 'FLAG';
72+
73+
process.stdout.write(BANNER);
74+
75+
const keyRow = ` ${c(D, 'Key:')} ${useColor ? c(B + WHITE, maskedKey) : maskedKey} ${c(D, `(${keySource})`)}`;
76+
const regionRow = ` ${c(D, 'Region:')} ${useColor ? c(MM_CYAN, region) : region}`;
77+
process.stdout.write(keyRow + '\n');
78+
process.stdout.write(regionRow + '\n');
79+
80+
// Fetch quota with spinner
81+
process.stdout.write('\n');
82+
const spinner = createSpinner('Fetching quota...');
83+
if (useColor) spinner.start();
84+
85+
let quotaFailed = false;
86+
let models: QuotaResponse['model_remains'] = [];
87+
try {
88+
const url = quotaEndpoint(config.baseUrl);
89+
const resp = await requestJson<QuotaResponse>(config, { url });
90+
models = resp.model_remains ?? [];
91+
} catch {
92+
quotaFailed = true;
93+
}
94+
spinner.stop();
95+
96+
if (quotaFailed) {
97+
process.stdout.write(` ${c(FG_YELLOW, '⚠ Quota unavailable (network error or timeout)')}\n`);
98+
} else if (models.length > 0) {
99+
const limit = models[0]!.current_interval_total_count;
100+
const remaining = models[0]!.current_interval_usage_count; // this IS the remaining count per API
101+
const used = limit - remaining;
102+
const pct = limit > 0 ? Math.round((used / limit) * 100) : 0; // % of quota consumed
103+
const bar = renderMiniBar(pct, useColor);
104+
process.stdout.write(` ${c(D, 'Balance:')} ${useColor ? c(pctColor(pct), `${remaining.toLocaleString()} ${c(D, '/')} ${limit.toLocaleString()}`) : `${remaining} / ${limit}`} ${bar} ${pct}%\n`);
105+
if (models.length > 1) {
106+
process.stdout.write(` ${c(D, `+${models.length - 1} more models → 'minimax quota show'`)}\n`);
107+
}
108+
}
109+
110+
process.stdout.write('\n');
111+
process.stdout.write(c(D, ' Common commands:\n'));
112+
for (const [cmd, desc] of COMMON_COMMANDS) {
113+
// Left-aligned: 2-space indent, command padded, 2-space gap, description
114+
const label = useColor ? c(B + WHITE, cmd) : cmd;
115+
process.stdout.write(` ${label.padEnd(24)} ${c(D, desc)}\n`);
116+
}
117+
process.stdout.write('\n');
118+
process.stdout.write(` ${c(D, "Run 'minimax <command> --help' for details.")}\n`);
119+
process.stdout.write(` ${c(D, "Or 'minimax --help' for the full command list.")}\n`);
120+
121+
} else {
122+
// ── Logged-out dashboard ──
123+
process.stdout.write(BANNER);
124+
process.stdout.write(`\n ${c(FG_RED, '✗ Not authenticated yet')}\n`);
125+
process.stdout.write(`\n ${c(D, "You're not logged in. To get started:")}\n`);
126+
process.stdout.write(`\n ${c(MM_CYAN, 'minimax auth login --api-key sk-xxxxx')}\n`);
127+
process.stdout.write(` ${c(D, '← Paste your Token Plan API key (sk-cp-...)')}\n`);
128+
process.stdout.write(`\n ${c(D, 'Or set the environment variable:')}\n`);
129+
process.stdout.write(` ${c(D, 'export MINIMAX_API_KEY=sk-xxxxx')}\n`);
130+
process.stdout.write(`\n ${c(D, 'What you can do:')}\n`);
131+
const capabilities = [
132+
['text chat', 'Chat with any MiniMax model'],
133+
['vision describe', 'Image understanding (VLM)'],
134+
['image generate', 'Image generation (image-01)'],
135+
['video generate', 'Video generation (Hailuo series)'],
136+
];
137+
for (const [cmd, desc] of capabilities) {
138+
const label = useColor ? c(B + WHITE, cmd) : cmd;
139+
process.stdout.write(` ${label.padEnd(24)} ${c(D, desc)}\n`);
140+
}
141+
process.stdout.write('\n');
142+
process.stdout.write(` ${c(D, "Run 'minimax --help' for the full command list.")}\n`);
143+
}
144+
}
145+
146+
function renderMiniBar(pct: number, color: boolean): string {
147+
const W = 10;
148+
const filled = Math.round((Math.max(0, Math.min(100, pct)) / 100) * W);
149+
const empty = W - filled;
150+
if (!color) return `[${'█'.repeat(filled)}${'-'.repeat(empty)}]`;
151+
return `${c(FG_GREEN, '█'.repeat(filled))}${c(D, '-'.repeat(empty))}`;
152+
}
153+
154+
function pctColor(pct: number): string {
155+
if (pct > 50) return FG_GREEN;
156+
if (pct > 20) return FG_YELLOW;
157+
return FG_RED;
158+
}
159+
11160
async function main() {
12161
const args = process.argv.slice(2);
13162

@@ -18,7 +167,14 @@ async function main() {
18167

19168
const { commandPath, flags } = parseArgs(args);
20169

21-
if (flags.help || commandPath.length === 0) {
170+
// ── Dashboard: no subcommand given (distinct from --help) ──
171+
if (commandPath.length === 0 && !flags.help) {
172+
const config = loadConfig(flags);
173+
await printDashboard(config);
174+
process.exit(0);
175+
}
176+
177+
if (flags.help) {
22178
registry.printHelp(commandPath, process.stderr);
23179
process.exit(0);
24180
}

0 commit comments

Comments
 (0)