diff --git a/package.json b/package.json index 9ce7b65..8a1c5c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minimax-cli", - "version": "0.1.0", + "version": "0.3.1", "private": true, "description": "CLI for the MiniMax API Platform (Token Plan)", "type": "module", diff --git a/src/client/http.ts b/src/client/http.ts index 76429e6..4cef543 100644 --- a/src/client/http.ts +++ b/src/client/http.ts @@ -16,6 +16,9 @@ export interface RequestOpts { authStyle?: 'bearer' | 'x-api-key'; } +// Printed once per process invocation to avoid repeating on every request. +let statusBarPrinted = false; + export async function request( config: Config, opts: RequestOpts, @@ -23,8 +26,9 @@ export async function request( const isFormData = typeof FormData !== 'undefined' && opts.body instanceof FormData; + const version = process.env.CLI_VERSION ?? '0.3.1'; const headers: Record = { - 'User-Agent': 'minimax-cli/0.1.0', + 'User-Agent': `minimax-cli/${version}`, ...opts.headers, }; @@ -47,7 +51,8 @@ export async function request( } // ANSI 真彩色 (24-bit) 与基础排版 - if (!config.quiet && process.stderr.isTTY) { + if (!config.quiet && !statusBarPrinted && process.stderr.isTTY) { + statusBarPrinted = true; const reset = '\x1b[0m'; const dim = '\x1b[2m'; const bold = '\x1b[1m'; // 新增加粗效果 diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index e94c5b5..0ebd055 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -30,7 +30,6 @@ export default defineCommand({ 'minimax auth login --method api-key --api-key sk-xxxxx', ], async run(config: Config, flags: GlobalFlags) { - // --- Phase 4: env key detection --- const envKey = process.env.MINIMAX_API_KEY; if (envKey) { const maskedEnvKey = envKey.length > 8 ? `${envKey.slice(0, 4)}...${envKey.slice(-4)}` : '***'; @@ -48,7 +47,6 @@ export default defineCommand({ process.stderr.write(`Warning: MINIMAX_API_KEY is already set in environment.\n`); } } - // --- Phase 4 end --- const method = flags.apiKey ? 'api-key' : (flags.method as string) || 'oauth'; diff --git a/src/commands/quota/show.ts b/src/commands/quota/show.ts index af89092..1f98f89 100644 --- a/src/commands/quota/show.ts +++ b/src/commands/quota/show.ts @@ -183,7 +183,9 @@ export default defineCommand({ const url = quotaEndpoint(config.baseUrl); const response = await requestJson(config, { url }); const models = response.model_remains || []; - const format = detectOutputFormat(config.output); + // Only honour explicit --output flag; ignore config-file output setting so the + // rich HUD is shown by default in TTY even when the user has output: json globally. + const format = detectOutputFormat(flags.output as string | undefined); // Step 1: Non-text formats pass through as-is if (format !== 'text') { @@ -208,9 +210,12 @@ export default defineCommand({ const maxNameLen = models.length > 0 ? Math.max(...models.map(m => m.model_name.length)) : 16; - // Layout per row: name + 2 + usage(15) + 2 + bar(BAR_WIDTH) + 1 + pct(4) = name + BAR_WIDTH + 24 - // Box inner W = content + 2 (for "│ " and " │" padding) - const W = Math.max(68, maxNameLen + BAR_WIDTH + 26); + // color bar: BAR_WIDTH spaces + ' ' + pct(4) = BAR_WIDTH + 5 visible cols + // no-color bar: '[' + BAR_WIDTH chars + '] ' + pct(4) = BAR_WIDTH + 7 visible cols + const barVisLen = useColor ? BAR_WIDTH + 5 : BAR_WIDTH + 7; + // line1 content = name(maxNameLen) + ' '(2) + usage(15) + ' '(2) + bar(barVisLen) + // W must be content + 2 so boxRow borders ('| ' + ' |') have room + const W = Math.max(68, maxNameLen + 2 + 15 + 2 + barVisLen + 2); // ── Header row ── const weekRange = models.length > 0 @@ -264,8 +269,9 @@ export default defineCommand({ const usageFrac = `${used.toLocaleString()} / ${limit.toLocaleString()}`; const bar = renderBar(usedPct, useColor); - // Visible columns: name(padded) + gap(2) + usage(15) + gap(2) + bar(BAR_WIDTH) + gap(1) + pct(4) - const line1VisLen = maxNameLen + 2 + 15 + 2 + BAR_WIDTH + 1 + 4; + // color bar: BAR_WIDTH spaces + gap(1) + pct(4) = BAR_WIDTH + 5 + // no-color bar: '[' + BAR_WIDTH chars + '] ' + pct(4) = BAR_WIDTH + 7 + const line1VisLen = maxNameLen + 2 + 15 + 2 + barVisLen; let line1Styled: string; if (useColor) { diff --git a/src/commands/text/chat.ts b/src/commands/text/chat.ts index c80fc06..2d764b9 100644 --- a/src/commands/text/chat.ts +++ b/src/commands/text/chat.ts @@ -79,7 +79,7 @@ function extractText(content: ContentBlock[]): string { export default defineCommand({ name: 'text chat', - description: 'Send a chat completion (Anthropic Messages API)', + description: 'Send a chat completion (MiniMax Messages API)', usage: 'minimax text chat --message [flags]', options: [ { flag: '--model ', description: 'Model ID (default: MiniMax-M2.7)' }, diff --git a/src/errors/handler.ts b/src/errors/handler.ts index 93280cb..cb2074e 100644 --- a/src/errors/handler.ts +++ b/src/errors/handler.ts @@ -1,12 +1,12 @@ import { CLIError } from './base'; import { ExitCode } from './codes'; +import { detectOutputFormat } from '../output/formatter'; export function handleError(err: unknown): never { if (err instanceof CLIError) { - const isJson = process.env.MINIMAX_OUTPUT === 'json' || - (typeof process.stdout?.isTTY !== 'undefined' && !process.stdout.isTTY); + const format = detectOutputFormat(process.env.MINIMAX_OUTPUT); - if (isJson) { + if (format === 'json') { process.stderr.write(JSON.stringify(err.toJSON(), null, 2) + '\n'); } else { process.stderr.write(`\nError: ${err.message}\n`); diff --git a/src/main.ts b/src/main.ts index 8f29483..e72c1e9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,7 @@ import { detectRegion, saveDetectedRegion } from './config/detect-region'; import { REGIONS } from './config/schema'; import { checkForUpdate, getPendingUpdateNotification } from './update/checker'; -const CLI_VERSION = process.env.CLI_VERSION ?? '0.1.0'; +const CLI_VERSION = process.env.CLI_VERSION ?? '0.3.1'; async function main() { const args = process.argv.slice(2); diff --git a/src/registry.ts b/src/registry.ts index 37883b1..2a12685 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -20,7 +20,7 @@ import quotaShow from './commands/quota/show'; import configShow from './commands/config/show'; import configSet from './commands/config/set'; import configExportSchema from './commands/config/export-schema'; -// ❄️ 暂时雪藏 File API (等待接口权限开放) +// File API temporarily disabled (pending permission grant) // import fileUpload from './commands/file/upload'; // import fileList from './commands/file/list'; // import fileDelete from './commands/file/delete'; @@ -205,13 +205,18 @@ Getting Help: } private printChildren(node: CommandNode, prefix: string, out: NodeJS.WriteStream): void { - for (const [name, child] of node.children) { - if (child.command) { - out.write(` ${prefix} ${name.padEnd(12)} ${child.command.description}\n`); - } - if (child.children.size > 0) { - this.printChildren(child, `${prefix} ${name}`, out); + // Collect all leaf entries first so we can align the description column. + const entries: Array<{ fullName: string; description: string }> = []; + const collect = (n: CommandNode, p: string) => { + for (const [name, child] of n.children) { + if (child.command) entries.push({ fullName: `${p} ${name}`, description: child.command.description }); + if (child.children.size > 0) collect(child, `${p} ${name}`); } + }; + collect(node, prefix); + const maxLen = Math.max(...entries.map(e => e.fullName.length)); + for (const { fullName, description } of entries) { + out.write(` ${fullName.padEnd(maxLen)} ${description}\n`); } } } @@ -236,7 +241,7 @@ export const registry = new CommandRegistry({ 'config set': configSet, 'config export-schema': configExportSchema, - // ❄️ 暂时雪藏 File API + // File API temporarily disabled (pending permission grant) // 'file upload': fileUpload, // 'file list': fileList, // 'file delete': fileDelete,