diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index e52bef8..e94c5b5 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -8,6 +8,7 @@ import { quotaEndpoint } from '../../client/endpoints'; import { formatOutput } from '../../output/formatter'; import { getConfigPath } from '../../config/paths'; import { readConfigFile, writeConfigFile } from '../../config/loader'; +import { isInteractive } from '../../utils/env'; import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; import type { CredentialFile } from '../../auth/types'; @@ -29,6 +30,26 @@ 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)}` : '***'; + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const { confirm } = await import('@clack/prompts'); + const proceed = await confirm({ + message: `Detected MINIMAX_API_KEY in environment (${maskedEnvKey}).\nYou are already authenticated via env.\nDo you still want to configure local persistent credentials?`, + initialValue: false, + }); + if (!proceed) { + process.stdout.write('Login skipped. Using environment variables.\n'); + process.exit(0); + } + } else { + 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'; if (method === 'api-key') { diff --git a/src/commands/auth/status.ts b/src/commands/auth/status.ts index 2ae5a3f..06349db 100644 --- a/src/commands/auth/status.ts +++ b/src/commands/auth/status.ts @@ -2,12 +2,15 @@ import { defineCommand } from '../../command'; import { resolveCredential } from '../../auth/resolver'; import { loadCredentials } from '../../auth/credentials'; import { formatOutput, detectOutputFormat } from '../../output/formatter'; +import { requestJson } from '../../client/http'; +import { quotaEndpoint } from '../../client/endpoints'; import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; +import type { QuotaResponse } from '../../types/api'; export default defineCommand({ name: 'auth status', - description: 'Show current authentication state', + description: 'Show current authentication state and quota snapshot', usage: 'minimax auth status', examples: [ 'minimax auth status', @@ -18,26 +21,71 @@ export default defineCommand({ const credential = await resolveCredential(config); const format = detectOutputFormat(config.output); - const result: Record = { - method: credential.method, - source: credential.source, - }; + if (format !== 'text') { + const result: Record = { + method: credential.method, + source: credential.source, + }; + if (credential.method === 'oauth') { + const creds = await loadCredentials(); + if (creds) { + result.token_expires = creds.expires_at; + if (creds.account) result.account = creds.account; + } + } else { + result.key = credential.token.slice(0, 6) + '...' + credential.token.slice(-4); + } + console.log(formatOutput(result, format)); + return; + } + + // Text format — rich output + console.log('Authentication Status:'); + console.log(` Method: ${credential.method}`); + console.log(` Source: ${credential.source}`); + + const token = credential.token; + const maskedToken = token.length > 8 ? `${token.slice(0, 4)}...${token.slice(-4)}` : '***'; + console.log(` Key: ${maskedToken}`); if (credential.method === 'oauth') { const creds = await loadCredentials(); if (creds) { - result.token_expires = creds.expires_at; - if (creds.account) result.account = creds.account; + if (creds.account) console.log(` Account: ${creds.account}`); const expiresAt = new Date(creds.expires_at); const minutesLeft = Math.round((expiresAt.getTime() - Date.now()) / 60000); - result.expires_in = `${minutesLeft} minutes`; + console.log(` Expires in: ${minutesLeft} minutes`); } - result.credentials_path = '~/.minimax/credentials.json'; - } else { - result.key = credential.token.slice(0, 6) + '...' + credential.token.slice(-4); } - console.log(formatOutput(result, format)); + // Fetch quota snapshot + console.log(''); + process.stderr.write('Fetching quota snapshot...\n'); + try { + const url = quotaEndpoint(config.baseUrl); + const quota = await requestJson(config, { url, method: 'GET' }); + const models = quota.model_remains || []; + if (models.length > 0) { + console.log('Available Quotas:'); + for (const m of models.slice(0, 5)) { + const remaining = m.current_interval_total_count - m.current_interval_usage_count; + const pct = m.current_interval_total_count > 0 + ? Math.round((remaining / m.current_interval_total_count) * 100) + : 0; + console.log(` ${m.model_name.padEnd(24)} ${String(remaining).padStart(6)} / ${m.current_interval_total_count} (${pct}%)`); + } + if (models.length > 5) { + console.log(` ... and ${models.length - 5} more (run 'minimax quota show' for full details)`); + } + } else { + console.log(' No quota data available.'); + } + } catch (e) { + console.log(` Quota fetch failed: ${(e as Error).message}`); + } + console.log(''); + console.log("Run 'minimax quota show' for full details."); + } catch { const format = detectOutputFormat(config.output); const result = { diff --git a/src/commands/image/generate.ts b/src/commands/image/generate.ts index 92db6d8..912d312 100644 --- a/src/commands/image/generate.ts +++ b/src/commands/image/generate.ts @@ -94,6 +94,10 @@ export default defineCommand({ const imageUrls = response.data.image_urls || []; + if (!config.quiet) { + process.stderr.write('[Model: image-01]\n'); + } + // Download if --out-dir specified if (flags.outDir) { const outDir = flags.outDir as string; diff --git a/src/commands/music/generate.ts b/src/commands/music/generate.ts index 37587d3..3ab2edf 100644 --- a/src/commands/music/generate.ts +++ b/src/commands/music/generate.ts @@ -94,6 +94,10 @@ export default defineCommand({ body, }); + if (!config.quiet) { + process.stderr.write('[Model: music-2.5]\n'); + } + if (outPath && response.data.audio) { const audioBuffer = Buffer.from(response.data.audio, 'hex'); writeFileSync(outPath, audioBuffer); diff --git a/src/commands/speech/synthesize.ts b/src/commands/speech/synthesize.ts index 7fe5a53..efcc564 100644 --- a/src/commands/speech/synthesize.ts +++ b/src/commands/speech/synthesize.ts @@ -117,6 +117,10 @@ export default defineCommand({ body, }); + if (!config.quiet) { + process.stderr.write(`[Model: ${model}]\n`); + } + if (outPath && response.data.audio) { // --out given: decode hex and save to file const audioBuffer = Buffer.from(response.data.audio, 'hex'); diff --git a/src/commands/text/chat.ts b/src/commands/text/chat.ts index c80fc06..797f4c0 100644 --- a/src/commands/text/chat.ts +++ b/src/commands/text/chat.ts @@ -152,6 +152,9 @@ export default defineCommand({ const url = chatEndpoint(config.baseUrl); if (shouldStream) { + if (!config.quiet) { + process.stderr.write(`[Model: ${model}]\n`); + } const res = await request(config, { url, method: 'POST', @@ -211,6 +214,10 @@ export default defineCommand({ const text = extractText(response.content); + if (!config.quiet && format === 'text') { + process.stderr.write(`[Model: ${response.model || model}]\n`); + } + if (config.quiet || format === 'text') { console.log(text); } else { diff --git a/src/commands/video/generate.ts b/src/commands/video/generate.ts index 2c67fbe..1bacb2a 100644 --- a/src/commands/video/generate.ts +++ b/src/commands/video/generate.ts @@ -85,6 +85,12 @@ export default defineCommand({ const taskId = response.task_id; + if (!config.quiet && !flags.noWait && !config.async) { + process.stderr.write(`[Model: ${model}]\n`); + } else if (!config.quiet) { + process.stderr.write(`[Model: ${model}]\n`); + } + // --no-wait or --async: return task ID immediately if (flags.noWait || config.async) { // Always pure JSON — Agent/CI mode needs predictable stdout