Skip to content

Commit 9ba4cea

Browse files
committed
feat: colorized CLI help output with MiniMax brand gradient
Made-with: Cursor
1 parent 6500642 commit 9ba4cea

File tree

4 files changed

+90
-57
lines changed

4 files changed

+90
-57
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mmx-cli",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "CLI for the MiniMax AI Platform",
55
"type": "module",
66
"engines": {
@@ -15,7 +15,7 @@
1515
"scripts": {
1616
"dev": "bun run src/main.ts",
1717
"build": "bun run build.ts",
18-
"build:dev": "bun build src/main.ts --outfile dist/mmx.mjs --target node --minify --define \"process.env.CLI_VERSION='1.0.2'\" && printf '#!/usr/bin/env node\\n' | cat - dist/mmx.mjs > dist/tmp && mv dist/tmp dist/mmx.mjs && chmod +x dist/mmx.mjs",
18+
"build:dev": "bun build src/main.ts --outfile dist/mmx.mjs --target node --minify --define \"process.env.CLI_VERSION='1.0.3'\" && printf '#!/usr/bin/env node\\n' | cat - dist/mmx.mjs > dist/tmp && mv dist/tmp dist/mmx.mjs && chmod +x dist/mmx.mjs",
1919
"prepublishOnly": "bun run build",
2020
"lint": "eslint src/ test/",
2121
"typecheck": "tsc --noEmit",

src/client/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface RequestOpts {
2020
export async function request(config: Config, opts: RequestOpts): Promise<Response> {
2121
const isFormData = typeof FormData !== 'undefined' && opts.body instanceof FormData;
2222

23-
const version = process.env.CLI_VERSION ?? '1.0.2';
23+
const version = process.env.CLI_VERSION ?? '1.0.3';
2424
const headers: Record<string, string> = {
2525
'User-Agent': `mmx-cli/${version}`,
2626
...opts.headers,

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { checkForUpdate, getPendingUpdateNotification } from './update/checker';
99
import { loadCredentials } from './auth/credentials';
1010
import { ensureApiKey } from './auth/setup';
1111

12-
const CLI_VERSION = process.env.CLI_VERSION ?? '1.0.2';
12+
const CLI_VERSION = process.env.CLI_VERSION ?? '1.0.3';
1313

1414
// Handle Ctrl+C gracefully
1515
process.on('SIGINT', () => {

src/registry.ts

Lines changed: 86 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ class CommandRegistry {
113113
* Defaults to stdout; pass stderr (or a non-TTY stream) to keep stdout
114114
* clean for piped / JSON output.
115115
*/
116+
// Color helpers — no-ops when output is not a TTY
117+
private bold = (s: string, out: NodeJS.WriteStream) => out.isTTY ? `\x1b[1m${s}\x1b[0m` : s;
118+
private accent = (s: string, out: NodeJS.WriteStream) => out.isTTY ? `\x1b[38;2;248;103;58m${s}\x1b[0m` : s;
119+
private dim = (s: string, out: NodeJS.WriteStream) => out.isTTY ? `\x1b[2m${s}\x1b[0m` : s;
120+
116121
printHelp(commandPath: string[], out: NodeJS.WriteStream = process.stdout): void {
117122
if (commandPath.length === 0) {
118123
this.printRootHelp(out);
@@ -134,80 +139,108 @@ class CommandRegistry {
134139
return;
135140
}
136141

137-
// Group help
138-
out.write(`\nUsage: mmx ${commandPath.join(' ')} <command> [flags]\n\n`);
139-
out.write('Commands:\n');
140-
this.printChildren(node, commandPath.join(' '), out);
142+
// Group help (e.g. `mmx auth --help`)
143+
const prefix = commandPath.join(' ');
144+
out.write(`\n${this.bold('Usage:', out)} mmx ${prefix} <command> [flags]\n\n`);
145+
out.write(`${this.bold('Commands:', out)}\n`);
146+
this.printChildren(node, prefix, out);
141147
out.write('\n');
142148
}
143149

144150
private printRootHelp(out: NodeJS.WriteStream): void {
151+
// MiniMax brand gradient: #F0177A (pink) → #FA7B2A (orange), one color per row
152+
const LOGO = [
153+
'███╗ ███╗███╗ ███╗██╗ ██╗',
154+
'████╗ ████║████╗ ████║╚██╗██╔╝',
155+
'██╔████╔██║██╔████╔██║ ╚███╔╝ ',
156+
'██║╚██╔╝██║██║╚██╔╝██║ ██╔██╗ ',
157+
'██║ ╚═╝ ██║██║ ╚═╝ ██║██╔╝ ██╗',
158+
'╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝',
159+
];
160+
const GRADIENT: [number, number, number][] = [
161+
[240, 23, 122],
162+
[242, 43, 106],
163+
[244, 63, 90],
164+
[246, 83, 74],
165+
[248, 103, 58],
166+
[250, 123, 42],
167+
];
168+
169+
out.write('\n');
170+
for (let i = 0; i < LOGO.length; i++) {
171+
if (out.isTTY) {
172+
const [r, g, b] = GRADIENT[i];
173+
out.write(`\x1b[1;38;2;${r};${g};${b}m${LOGO[i]}\x1b[0m\n`);
174+
} else {
175+
out.write(LOGO[i] + '\n');
176+
}
177+
}
178+
179+
const b = (s: string) => this.bold(s, out);
180+
const a = (s: string) => this.accent(s, out);
181+
const d = (s: string) => this.dim(s, out);
182+
145183
out.write(`
146-
__ __ __ ____ __
147-
| \\/ | \\/ \\ \\/ /
148-
| |\\/| | |\\/| |\\ /
149-
| | | | | | |/ \\
150-
|_| |_|_| |_/_/\\_\\
151-
152-
Usage: mmx <resource> <command> [flags]
153-
154-
Resources:
155-
auth Authentication (login, status, refresh, logout)
156-
text Text generation (chat)
157-
speech Speech synthesis (synthesize, voices)
158-
image Image generation (generate)
159-
video Video generation (generate, task get, download)
160-
music Music generation (generate)
161-
search Web search (query)
162-
vision Image understanding (describe)
163-
quota Usage quotas (show)
164-
config CLI configuration (show, set, export-schema)
165-
update Update mmx to a newer version
166-
167-
Global Flags:
168-
--api-key <key> API key (overrides all other auth)
169-
--region <region> API region: global (default), cn
170-
--base-url <url> API base URL (overrides region)
171-
--output <format> Output format: text, json
172-
--quiet Suppress non-essential output
173-
--verbose Print HTTP request/response details
174-
--timeout <seconds> Request timeout (default: 300)
175-
--no-color Disable ANSI colors and spinners
176-
--dry-run Show what would happen without executing
177-
--non-interactive Disable interactive prompts (CI/agent mode)
178-
--version Print version and exit
179-
--help Show help
180-
181-
Getting Help:
182-
Add --help after any command to see its full list of options, defaults,
183-
and usage examples. For example: mmx text chat --help
184+
${b('Usage:')} mmx <resource> <command> [flags]
185+
186+
${b('Resources:')}
187+
${a('auth')} ${d('Authentication (login, status, refresh, logout)')}
188+
${a('text')} ${d('Text generation (chat)')}
189+
${a('speech')} ${d('Speech synthesis (synthesize, voices)')}
190+
${a('image')} ${d('Image generation (generate)')}
191+
${a('video')} ${d('Video generation (generate, task get, download)')}
192+
${a('music')} ${d('Music generation (generate)')}
193+
${a('search')} ${d('Web search (query)')}
194+
${a('vision')} ${d('Image understanding (describe)')}
195+
${a('quota')} ${d('Usage quotas (show)')}
196+
${a('config')} ${d('CLI configuration (show, set, export-schema)')}
197+
${a('update')} ${d('Update mmx to a newer version')}
198+
199+
${b('Global Flags:')}
200+
${a('--api-key <key>')} ${d('API key (overrides all other auth)')}
201+
${a('--region <region>')} ${d('API region: global (default), cn')}
202+
${a('--base-url <url>')} ${d('API base URL (overrides region)')}
203+
${a('--output <format>')} ${d('Output format: text, json')}
204+
${a('--quiet')} ${d('Suppress non-essential output')}
205+
${a('--verbose')} ${d('Print HTTP request/response details')}
206+
${a('--timeout <seconds>')} ${d('Request timeout (default: 300)')}
207+
${a('--no-color')} ${d('Disable ANSI colors and spinners')}
208+
${a('--dry-run')} ${d('Show what would happen without executing')}
209+
${a('--non-interactive')} ${d('Disable interactive prompts (CI/agent mode)')}
210+
${a('--version')} ${d('Print version and exit')}
211+
${a('--help')} ${d('Show help')}
212+
213+
${b('Getting Help:')}
214+
${d('Add --help after any command to see its full list of options, defaults,')}
215+
${d('and usage examples. For example:')} mmx text chat --help
184216
`);
185217
}
186218

187219
private printCommandHelp(cmd: Command, out: NodeJS.WriteStream): void {
220+
const b = (s: string) => this.bold(s, out);
221+
const a = (s: string) => this.accent(s, out);
222+
const d = (s: string) => this.dim(s, out);
223+
188224
out.write(`\n${cmd.description}\n`);
189-
if (cmd.usage) out.write(`Usage: ${cmd.usage}\n`);
225+
if (cmd.usage) out.write(`${b('Usage:')} ${cmd.usage}\n`);
190226
if (cmd.options && cmd.options.length > 0) {
191227
const maxLen = Math.max(...cmd.options.map(o => o.flag.length));
192-
out.write('Options:\n');
228+
out.write(`\n${b('Options:')}\n`);
193229
for (const opt of cmd.options) {
194-
out.write(` ${opt.flag.padEnd(maxLen + 2)} ${opt.description}\n`);
230+
out.write(` ${a(opt.flag.padEnd(maxLen + 2))} ${d(opt.description)}\n`);
195231
}
196-
out.write('\n');
197232
}
198233
if (cmd.examples && cmd.examples.length > 0) {
199-
out.write('Examples:\n');
234+
out.write(`\n${b('Examples:')}\n`);
200235
for (const ex of cmd.examples) {
201-
out.write(` ${ex}\n`);
236+
out.write(` ${d(ex)}\n`);
202237
}
203-
out.write('\n');
204238
}
205-
out.write(`Global flags (--api-key, --output, --quiet, etc.) are always available.\n`);
206-
out.write(`Run 'mmx --help' for the full list.\n`);
239+
out.write(`\n${d('Global flags (--api-key, --output, --quiet, etc.) are always available.')}\n`);
240+
out.write(`${d("Run")} mmx --help ${d('for the full list.')}\n`);
207241
}
208242

209243
private printChildren(node: CommandNode, prefix: string, out: NodeJS.WriteStream): void {
210-
// Collect all leaf entries first so we can align the description column.
211244
const entries: Array<{ fullName: string; description: string }> = [];
212245
const collect = (n: CommandNode, p: string) => {
213246
for (const [name, child] of n.children) {
@@ -218,7 +251,7 @@ Getting Help:
218251
collect(node, prefix);
219252
const maxLen = Math.max(...entries.map(e => e.fullName.length));
220253
for (const { fullName, description } of entries) {
221-
out.write(` ${fullName.padEnd(maxLen)} ${description}\n`);
254+
out.write(` ${this.accent(fullName.padEnd(maxLen), out)} ${this.dim(description, out)}\n`);
222255
}
223256
}
224257
}

0 commit comments

Comments
 (0)