Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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') {
Expand Down
72 changes: 60 additions & 12 deletions src/commands/auth/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -18,26 +21,71 @@ export default defineCommand({
const credential = await resolveCredential(config);
const format = detectOutputFormat(config.output);

const result: Record<string, unknown> = {
method: credential.method,
source: credential.source,
};
if (format !== 'text') {
const result: Record<string, unknown> = {
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<QuotaResponse>(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 = {
Expand Down
4 changes: 4 additions & 0 deletions src/commands/image/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/commands/music/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/commands/speech/synthesize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
7 changes: 7 additions & 0 deletions src/commands/text/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/commands/video/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down