From 88defd38bfba633a3d503f8e40969f9b1aee7600 Mon Sep 17 00:00:00 2001 From: jiemu Date: Sat, 11 Apr 2026 10:47:41 +0800 Subject: [PATCH 01/17] feat: oauth --- src/auth/oauth.ts | 85 ++++++++++++++++++++++++------------ src/auth/refresh.ts | 55 ++++++++++++++++------- src/auth/types.ts | 4 +- src/commands/auth/login.ts | 3 +- src/commands/auth/refresh.ts | 3 +- 5 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 530f03e..b075481 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -2,7 +2,7 @@ import type { OAuthTokens } from './types'; import { CLIError } from '../errors/base'; import { ExitCode } from '../errors/codes'; -// OAuth configuration — exact endpoints TBD pending MiniMax OAuth docs +// OAuth configuration export interface OAuthConfig { clientId: string; authorizationUrl: string; @@ -15,9 +15,9 @@ export interface OAuthConfig { const DEFAULT_OAUTH_CONFIG: OAuthConfig = { clientId: 'mmx-cli', authorizationUrl: 'https://platform.minimax.io/oauth/authorize', - tokenUrl: 'https://api.minimax.io/v1/oauth/token', - deviceCodeUrl: 'https://api.minimax.io/v1/oauth/device/code', - scopes: ['api'], + tokenUrl: 'https://api.minimax.io/oauth/token', + deviceCodeUrl: 'https://api.minimax.io/oauth/code', + scopes: ['openid', 'profile', 'coding_plan'], callbackPort: 18991, }; @@ -139,13 +139,24 @@ async function waitForCallback(port: number, expectedState: string): Promise { - // Request device code + const { randomBytes, createHash } = await import('crypto'); + const codeVerifier = randomBytes(32).toString('base64url'); + const codeChallenge = createHash('sha256') + .update(codeVerifier) + .digest('base64url'); + + const state = randomBytes(16).toString('base64url'); + + // Request device code with PKCE const codeRes = await fetch(config.deviceCodeUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: config.clientId, scope: config.scopes.join(' '), + code_challenge: codeChallenge, + code_challenge_method: 'S256', + state, }), }); @@ -156,22 +167,25 @@ export async function startDeviceCodeFlow( ); } - const { device_code, user_code, verification_uri, interval, expires_in } = - (await codeRes.json()) as { - device_code: string; - user_code: string; - verification_uri: string; - interval: number; - expires_in: number; - }; + const data = (await codeRes.json()) as { + user_code: string; + verification_uri: string; + expired_in: number; // milliseconds + interval: number; // milliseconds + state: string; + }; + + if (data.state !== state) { + throw new CLIError('OAuth state mismatch: possible CSRF attack.', ExitCode.AUTH); + } - process.stderr.write(`\nVisit: ${verification_uri}\n`); - process.stderr.write(`Enter code: ${user_code}\n`); + process.stderr.write(`\nVisit: ${data.verification_uri}\n`); + process.stderr.write(`Enter code: ${data.user_code}\n`); process.stderr.write('Waiting for authorization...\n'); - // Poll for authorization - const deadline = Date.now() + expires_in * 1000; - const pollInterval = (interval || 5) * 1000; + // Poll for authorization (times are already in milliseconds) + const deadline = Date.now() + data.expired_in; + const pollInterval = data.interval || 5000; while (Date.now() < deadline) { await new Promise(r => setTimeout(r, pollInterval)); @@ -181,24 +195,41 @@ export async function startDeviceCodeFlow( headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', - device_code, client_id: config.clientId, + user_code: data.user_code, + code_verifier: codeVerifier, }), }); - if (tokenRes.ok) { - return (await tokenRes.json()) as OAuthTokens; + if (!tokenRes.ok) { + throw new CLIError( + `Device code authorization failed: HTTP ${tokenRes.status}`, + ExitCode.AUTH, + ); } - const err = (await tokenRes.json()) as { error: string }; - if (err.error === 'authorization_pending') continue; - if (err.error === 'slow_down') { - await new Promise(r => setTimeout(r, 5000)); - continue; + const tokenData = (await tokenRes.json()) as { + status: string; + access_token?: string; + refresh_token?: string; + expired_in?: number; + resource_url?: string; + }; + + if (tokenData.status === 'success' && tokenData.access_token) { + return { + access_token: tokenData.access_token, + refresh_token: tokenData.refresh_token ?? '', + expired_in: tokenData.expired_in ?? 0, + token_type: 'Bearer', + resource_url: tokenData.resource_url, + }; } + if (tokenData.status === 'pending') continue; + throw new CLIError( - `Device code authorization failed: ${err.error}`, + `Device code authorization failed: ${tokenData.status}`, ExitCode.AUTH, ); } diff --git a/src/auth/refresh.ts b/src/auth/refresh.ts index e544dea..21bae5f 100644 --- a/src/auth/refresh.ts +++ b/src/auth/refresh.ts @@ -3,31 +3,37 @@ import { saveCredentials } from "./credentials"; import { CLIError } from "../errors/base"; import { ExitCode } from "../errors/codes"; -// OAuth config — endpoints TBD pending MiniMax OAuth documentation -const TOKEN_URL = "https://api.minimax.io/v1/oauth/token"; +const DEFAULT_TOKEN_URL = "https://api.minimax.io/oauth/token"; +const DEFAULT_CLIENT_ID = "minimax-cli"; const MAX_REFRESH_RETRIES = 2; const RETRY_DELAY_MS = 500; export async function refreshAccessToken( refreshToken: string, + tokenUrl: string = DEFAULT_TOKEN_URL, + clientId: string = DEFAULT_CLIENT_ID, ): Promise { + const lane = process.env.BEDROCK_LANE; + const extraHeaders: Record = lane ? { bedrock_lane: lane } : {}; + if (process.env.X_USER_PRE) extraHeaders['X-User-Pre'] = 'true'; + let lastErr: Error | null = null; for (let attempt = 0; attempt <= MAX_REFRESH_RETRIES; attempt++) { if (attempt > 0) { - // Exponential backoff before retry await new Promise(r => setTimeout(r, RETRY_DELAY_MS * attempt)); } let res: Response; try { - res = await fetch(TOKEN_URL, { + res = await fetch(tokenUrl, { method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, + headers: { "Content-Type": "application/x-www-form-urlencoded", ...extraHeaders }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, + client_id: clientId, }), signal: AbortSignal.timeout(10_000), }); @@ -42,11 +48,10 @@ export async function refreshAccessToken( ? "Token refresh timed out — auth server did not respond within 10 s." : `Token refresh failed: ${err instanceof Error ? err.message : String(err)}`, ); - continue; // retry + continue; } if (!res.ok) { - // 4xx errors won't recover with retry if (res.status >= 400 && res.status < 500) { throw new CLIError( "OAuth session expired and could not be refreshed.", @@ -55,14 +60,34 @@ export async function refreshAccessToken( ); } lastErr = new Error(`Token refresh failed: HTTP ${res.status}`); - continue; // retry 5xx errors + continue; + } + + const body = (await res.json()) as { + status: string; + access_token?: string; + refresh_token?: string; + expired_in?: number; + resource_url?: string; + }; + + if (body.status !== 'success' || !body.access_token) { + throw new CLIError( + 'OAuth refresh failed.', + ExitCode.AUTH, + 'Re-authenticate: mmx auth login', + ); } - const data = (await res.json()) as OAuthTokens; - return data; + return { + access_token: body.access_token, + refresh_token: body.refresh_token ?? refreshToken, + expired_in: body.expired_in ?? 0, + token_type: 'Bearer', + resource_url: body.resource_url, + }; } - // All retries exhausted throw new CLIError( `Token refresh failed after ${MAX_REFRESH_RETRIES + 1} attempts: ${lastErr?.message}`, ExitCode.AUTH, @@ -72,20 +97,20 @@ export async function refreshAccessToken( export async function ensureFreshToken(creds: CredentialFile): Promise { const expiresAt = new Date(creds.expires_at).getTime(); - const bufferMs = 5 * 60 * 1000; // 5 minutes + const bufferMs = 5 * 60 * 1000; if (Date.now() < expiresAt - bufferMs) { return creds.access_token; } - // Token expired or about to expire — refresh const tokens = await refreshAccessToken(creds.refresh_token); const updated: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), - token_type: "Bearer", + expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), + token_type: 'Bearer', + resource_url: tokens.resource_url ?? creds.resource_url, account: creds.account, }; diff --git a/src/auth/types.ts b/src/auth/types.ts index 0f39fbe..4b87289 100644 --- a/src/auth/types.ts +++ b/src/auth/types.ts @@ -3,8 +3,9 @@ export type AuthMethod = 'api-key' | 'oauth'; export interface OAuthTokens { access_token: string; refresh_token: string; - expires_in: number; + expired_in: number; // milliseconds token_type: 'Bearer'; + resource_url?: string; } export interface CredentialFile { @@ -12,6 +13,7 @@ export interface CredentialFile { refresh_token: string; expires_at: string; // ISO 8601 token_type: 'Bearer'; + resource_url?: string; account?: string; } diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index 7d1fecb..a040dff 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -122,8 +122,9 @@ export default defineCommand({ const creds: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), + expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), // expired_in is already ms token_type: 'Bearer', + resource_url: tokens.resource_url, }; await saveCredentials(creds); diff --git a/src/commands/auth/refresh.ts b/src/commands/auth/refresh.ts index 2328062..2908f12 100644 --- a/src/commands/auth/refresh.ts +++ b/src/commands/auth/refresh.ts @@ -36,8 +36,9 @@ export default defineCommand({ const updated: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), + expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), // expired_in is already ms token_type: 'Bearer', + resource_url: tokens.resource_url ?? creds.resource_url, account: creds.account, }; From 82dd72cff633603002c1e318d7c0f19d1d70f89e Mon Sep 17 00:00:00 2001 From: jiemu Date: Sat, 11 Apr 2026 13:38:58 +0800 Subject: [PATCH 02/17] fix: expired_in is Unix timestamp, not duration Server returns expired_in as Unix timestamp (ms), matching the old user_management protocol and OpenClaw client expectations. --- src/auth/oauth.ts | 8 ++++---- src/auth/refresh.ts | 4 ++-- src/commands/auth/login.ts | 2 +- src/commands/auth/refresh.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index b075481..6793be6 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -13,7 +13,7 @@ export interface OAuthConfig { } const DEFAULT_OAUTH_CONFIG: OAuthConfig = { - clientId: 'mmx-cli', + clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', authorizationUrl: 'https://platform.minimax.io/oauth/authorize', tokenUrl: 'https://api.minimax.io/oauth/token', deviceCodeUrl: 'https://api.minimax.io/oauth/code', @@ -170,7 +170,7 @@ export async function startDeviceCodeFlow( const data = (await codeRes.json()) as { user_code: string; verification_uri: string; - expired_in: number; // milliseconds + expired_in: number; // Unix timestamp (ms) interval: number; // milliseconds state: string; }; @@ -183,8 +183,8 @@ export async function startDeviceCodeFlow( process.stderr.write(`Enter code: ${data.user_code}\n`); process.stderr.write('Waiting for authorization...\n'); - // Poll for authorization (times are already in milliseconds) - const deadline = Date.now() + data.expired_in; + // Poll for authorization (expired_in is Unix timestamp in ms) + const deadline = data.expired_in; const pollInterval = data.interval || 5000; while (Date.now() < deadline) { diff --git a/src/auth/refresh.ts b/src/auth/refresh.ts index 21bae5f..983a8ca 100644 --- a/src/auth/refresh.ts +++ b/src/auth/refresh.ts @@ -4,7 +4,7 @@ import { CLIError } from "../errors/base"; import { ExitCode } from "../errors/codes"; const DEFAULT_TOKEN_URL = "https://api.minimax.io/oauth/token"; -const DEFAULT_CLIENT_ID = "minimax-cli"; +const DEFAULT_CLIENT_ID = "659cf4c1-615c-45f6-a5f6-4bf15eb476e5"; const MAX_REFRESH_RETRIES = 2; const RETRY_DELAY_MS = 500; @@ -108,7 +108,7 @@ export async function ensureFreshToken(creds: CredentialFile): Promise { const updated: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), + expires_at: new Date(tokens.expired_in).toISOString(), // expired_in is Unix timestamp (ms) token_type: 'Bearer', resource_url: tokens.resource_url ?? creds.resource_url, account: creds.account, diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index a040dff..2920761 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -122,7 +122,7 @@ export default defineCommand({ const creds: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), // expired_in is already ms + expires_at: new Date(tokens.expired_in).toISOString(), // expired_in is Unix timestamp (ms) token_type: 'Bearer', resource_url: tokens.resource_url, }; diff --git a/src/commands/auth/refresh.ts b/src/commands/auth/refresh.ts index 2908f12..dacf07f 100644 --- a/src/commands/auth/refresh.ts +++ b/src/commands/auth/refresh.ts @@ -36,7 +36,7 @@ export default defineCommand({ const updated: CredentialFile = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, - expires_at: new Date(Date.now() + tokens.expired_in).toISOString(), // expired_in is already ms + expires_at: new Date(tokens.expired_in).toISOString(), // expired_in is Unix timestamp (ms) token_type: 'Bearer', resource_url: tokens.resource_url ?? creds.resource_url, account: creds.account, From 083c644426a509ccc59687dee99db95dbe06dfdf Mon Sep 17 00:00:00 2001 From: jiemu Date: Mon, 13 Apr 2026 14:57:34 +0800 Subject: [PATCH 03/17] feat: oauth init --- src/auth/oauth.ts | 37 +++++++++++------- src/auth/refresh.ts | 12 ++++-- src/auth/resolver.ts | 2 +- src/auth/setup.ts | 52 +++++++++++++++++++++++-- src/command.ts | 1 + src/commands/auth/login.ts | 21 +++++----- src/commands/auth/refresh.ts | 5 ++- src/config/loader.ts | 7 +++- src/config/schema.ts | 31 +++++++++++++++ src/main.ts | 7 ++-- src/types/flags.ts | 1 + test/auth/resolver.test.ts | 3 ++ test/client/http.test.ts | 3 ++ test/commands/auth/login.test.ts | 3 ++ test/commands/auth/logout.test.ts | 3 ++ test/commands/auth/refresh.test.ts | 3 ++ test/commands/auth/status.test.ts | 3 ++ test/commands/config/set.test.ts | 6 +++ test/commands/config/show.test.ts | 3 ++ test/commands/file/upload.test.ts | 3 ++ test/commands/image/generate.test.ts | 3 ++ test/commands/music/generate.test.ts | 3 ++ test/commands/quota/show.test.ts | 3 ++ test/commands/search/query.test.ts | 3 ++ test/commands/speech/synthesize.test.ts | 6 +++ test/commands/text/chat.test.ts | 6 +++ test/commands/video/download.test.ts | 6 +++ test/commands/video/generate.test.ts | 3 ++ test/commands/video/task-get.test.ts | 6 +++ test/commands/vision/describe.test.ts | 6 +++ 30 files changed, 214 insertions(+), 37 deletions(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 6793be6..cbeba35 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -5,6 +5,7 @@ import { ExitCode } from '../errors/codes'; // OAuth configuration export interface OAuthConfig { clientId: string; + clientName: string; authorizationUrl: string; tokenUrl: string; deviceCodeUrl: string; @@ -12,17 +13,8 @@ export interface OAuthConfig { callbackPort: number; } -const DEFAULT_OAUTH_CONFIG: OAuthConfig = { - clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', - authorizationUrl: 'https://platform.minimax.io/oauth/authorize', - tokenUrl: 'https://api.minimax.io/oauth/token', - deviceCodeUrl: 'https://api.minimax.io/oauth/code', - scopes: ['openid', 'profile', 'coding_plan'], - callbackPort: 18991, -}; - export async function startBrowserFlow( - config: OAuthConfig = DEFAULT_OAUTH_CONFIG, + config: OAuthConfig, ): Promise { const { randomBytes, createHash } = await import('crypto'); const codeVerifier = randomBytes(32).toString('base64url'); @@ -137,7 +129,7 @@ async function waitForCallback(port: number, expectedState: string): Promise { const { randomBytes, createHash } = await import('crypto'); const codeVerifier = randomBytes(32).toString('base64url'); @@ -147,10 +139,13 @@ export async function startDeviceCodeFlow( const state = randomBytes(16).toString('base64url'); + const lane = process.env.BEDROCK_LANE; + const extraHeaders: Record = lane ? { bedrock_lane: lane } : {}; + // Request device code with PKCE const codeRes = await fetch(config.deviceCodeUrl, { method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...extraHeaders }, body: new URLSearchParams({ client_id: config.clientId, scope: config.scopes.join(' '), @@ -161,9 +156,11 @@ export async function startDeviceCodeFlow( }); if (!codeRes.ok) { + const body = await codeRes.text().catch(() => ''); throw new CLIError( - 'Failed to start device code flow.', + `Failed to start device code flow: HTTP ${codeRes.status} ${body}`, ExitCode.AUTH, + `URL: ${config.deviceCodeUrl}`, ); } @@ -179,7 +176,17 @@ export async function startDeviceCodeFlow( throw new CLIError('OAuth state mismatch: possible CSRF attack.', ExitCode.AUTH); } - process.stderr.write(`\nVisit: ${data.verification_uri}\n`); + const verificationUrl = new URL(data.verification_uri); + verificationUrl.searchParams.set('user_code', data.user_code); + verificationUrl.searchParams.set('client', config.clientName); + const url = verificationUrl.toString(); + + const { exec } = await import('child_process'); + const openCmd = process.platform === 'darwin' ? 'open' : + process.platform === 'win32' ? 'start' : 'xdg-open'; + exec(`${openCmd} "${url}"`); + + process.stderr.write(`\nOpened: ${url}\n`); process.stderr.write(`Enter code: ${data.user_code}\n`); process.stderr.write('Waiting for authorization...\n'); @@ -192,7 +199,7 @@ export async function startDeviceCodeFlow( const tokenRes = await fetch(config.tokenUrl, { method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...extraHeaders }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', client_id: config.clientId, diff --git a/src/auth/refresh.ts b/src/auth/refresh.ts index 983a8ca..b0eb594 100644 --- a/src/auth/refresh.ts +++ b/src/auth/refresh.ts @@ -3,8 +3,8 @@ import { saveCredentials } from "./credentials"; import { CLIError } from "../errors/base"; import { ExitCode } from "../errors/codes"; -const DEFAULT_TOKEN_URL = "https://api.minimax.io/oauth/token"; -const DEFAULT_CLIENT_ID = "659cf4c1-615c-45f6-a5f6-4bf15eb476e5"; +const DEFAULT_TOKEN_URL = 'https://account.minimax.io/oauth2/token'; +const DEFAULT_CLIENT_ID = '659cf4c1-615c-45f6-a5f6-4bf15eb476e5'; const MAX_REFRESH_RETRIES = 2; const RETRY_DELAY_MS = 500; @@ -95,7 +95,11 @@ export async function refreshAccessToken( ); } -export async function ensureFreshToken(creds: CredentialFile): Promise { +export async function ensureFreshToken( + creds: CredentialFile, + tokenUrl?: string, + clientId?: string, +): Promise { const expiresAt = new Date(creds.expires_at).getTime(); const bufferMs = 5 * 60 * 1000; @@ -103,7 +107,7 @@ export async function ensureFreshToken(creds: CredentialFile): Promise { return creds.access_token; } - const tokens = await refreshAccessToken(creds.refresh_token); + const tokens = await refreshAccessToken(creds.refresh_token, tokenUrl, clientId); const updated: CredentialFile = { access_token: tokens.access_token, diff --git a/src/auth/resolver.ts b/src/auth/resolver.ts index eb9a11a..d28cef8 100644 --- a/src/auth/resolver.ts +++ b/src/auth/resolver.ts @@ -14,7 +14,7 @@ export async function resolveCredential(config: Config): Promise { +export async function ensureAuth(config: Config): Promise { if (config.apiKey || config.fileApiKey) return; + // Check existing OAuth credentials + const existingOAuth = await loadCredentials(); + if (existingOAuth) return; + const envKey = process.env.MINIMAX_API_KEY; let key: string | undefined; @@ -26,11 +33,50 @@ export async function ensureApiKey(config: Config): Promise { if (!key) { if (!isInteractive({ nonInteractive: config.nonInteractive })) { throw new CLIError( - 'No API key found.', + 'No credentials found.', ExitCode.AUTH, - 'Set env var: export MINIMAX_API_KEY=sk-xxxxx\nPass directly: --api-key sk-xxxxx', + 'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx', ); } + + const { select } = await import('@clack/prompts'); + const method = await select({ + message: 'How would you like to authenticate?', + options: [ + { value: 'oauth', label: 'Log in with MiniMax account (OAuth)' }, + { value: 'api-key', label: 'Enter API key manually' }, + ], + }); + + if (typeof method === 'symbol') { + // User pressed Ctrl+C + throw new CLIError('Authentication cancelled.', ExitCode.AUTH); + } + + if (method === 'oauth') { + const oauthConfig: OAuthConfig = { + clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', + clientName: 'MiniMax CLI', + authorizationUrl: `${config.platformHost}/oauth-authorize`, + tokenUrl: `${config.oauthApiHost}/oauth2/token`, + deviceCodeUrl: `${config.oauthApiHost}/oauth2/device/code`, + scopes: ['openid', 'profile', 'coding_plan'], + callbackPort: 18991, + }; + const tokens = await startDeviceCodeFlow(oauthConfig); + const creds: CredentialFile = { + access_token: tokens.access_token, + refresh_token: tokens.refresh_token, + expires_at: new Date(tokens.expired_in).toISOString(), + token_type: 'Bearer', + resource_url: tokens.resource_url, + }; + await saveCredentials(creds); + process.stderr.write('Logged in successfully.\n'); + return; + } + + // api-key method const input = await promptText({ message: 'Enter your MiniMax API key:' }); if (!input) throw new CLIError('API key is required.', ExitCode.AUTH); key = input; diff --git a/src/command.ts b/src/command.ts index 8d89da8..4ede2ec 100644 --- a/src/command.ts +++ b/src/command.ts @@ -44,6 +44,7 @@ export function defineCommand(spec: CommandSpec): Command { export const GLOBAL_OPTIONS: OptionDef[] = [ { flag: '--api-key ', description: 'API key' }, { flag: '--region ', description: 'API region: global, cn' }, + { flag: '--env ', description: 'Environment: test, pre, prod (default)' }, { flag: '--base-url ', description: 'API base URL' }, { flag: '--output ', description: 'Output format: text, json' }, { flag: '--timeout ', description: 'Request timeout', type: 'number' }, diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index 2920761..fe6fbb2 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -2,7 +2,7 @@ import { defineCommand } from '../../command'; import { CLIError } from '../../errors/base'; import { ExitCode } from '../../errors/codes'; import { saveCredentials } from '../../auth/credentials'; -import { startBrowserFlow, startDeviceCodeFlow } from '../../auth/oauth'; +import { startDeviceCodeFlow, type OAuthConfig } from '../../auth/oauth'; import { requestJson } from '../../client/http'; import { quotaEndpoint } from '../../client/endpoints'; import { renderQuotaTable } from '../../output/quota-table'; @@ -37,11 +37,9 @@ export default defineCommand({ options: [ { flag: '--method ', description: 'Auth method: oauth (default), api-key' }, { flag: '--api-key ', description: 'API key to store' }, - { flag: '--no-browser', description: 'Use device-code flow instead of browser' }, ], examples: [ 'mmx auth login', - 'mmx auth login --no-browser', 'mmx auth login --api-key sk-xxxxx', 'mmx auth login --method api-key --api-key sk-xxxxx', ], @@ -112,12 +110,17 @@ export default defineCommand({ return; } - let tokens; - if (flags.noBrowser) { - tokens = await startDeviceCodeFlow(); - } else { - tokens = await startBrowserFlow(); - } + const oauthConfig: OAuthConfig = { + clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', + clientName: 'MiniMax CLI', + authorizationUrl: `${config.platformHost}/oauth-authorize`, + tokenUrl: `${config.oauthApiHost}/oauth2/token`, + deviceCodeUrl: `${config.oauthApiHost}/oauth2/device/code`, + scopes: ['openid', 'profile', 'coding_plan'], + callbackPort: 18991, + }; + + const tokens = await startDeviceCodeFlow(oauthConfig); const creds: CredentialFile = { access_token: tokens.access_token, diff --git a/src/commands/auth/refresh.ts b/src/commands/auth/refresh.ts index dacf07f..db8dd18 100644 --- a/src/commands/auth/refresh.ts +++ b/src/commands/auth/refresh.ts @@ -31,7 +31,10 @@ export default defineCommand({ return; } - const tokens = await refreshAccessToken(creds.refresh_token); + const tokens = await refreshAccessToken( + creds.refresh_token, + `${config.oauthApiHost}/oauth2/token`, + ); const updated: CredentialFile = { access_token: tokens.access_token, diff --git a/src/config/loader.ts b/src/config/loader.ts index 5d40ec9..9adaadf 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -1,5 +1,5 @@ import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs'; -import { parseConfigFile, REGIONS, type Config, type ConfigFile, type Region } from './schema'; +import { parseConfigFile, REGIONS, PLATFORM_HOSTS, OAUTH_API_HOSTS, type Config, type ConfigFile, type Region, type Env } from './schema'; import { ensureConfigDir, getConfigPath } from './paths'; import { detectOutputFormat, type OutputFormat } from '../output/formatter'; import { CLIError } from '../errors/base'; @@ -45,6 +45,8 @@ export function loadConfig(flags: GlobalFlags): Config { const cachedRegion = file.region; const region = (explicitRegion || cachedRegion || 'global') as Region; + const env = ((flags.env as string) || process.env.MINIMAX_ENV || 'prod') as Env; + const activeKey = apiKey || fileApiKey; const needsRegionDetection = !explicitRegion && (!cachedRegion || (activeKey !== undefined && activeKey !== file.api_key)); @@ -70,7 +72,10 @@ export function loadConfig(flags: GlobalFlags): Config { fileRegion: file.region, configPath: getConfigPath(), region, + env, baseUrl, + platformHost: PLATFORM_HOSTS[region][env], + oauthApiHost: OAUTH_API_HOSTS[region][env], output, timeout, defaultTextModel: file.default_text_model, diff --git a/src/config/schema.ts b/src/config/schema.ts index 38f3445..9fb8430 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -9,6 +9,33 @@ export const DOCS_HOSTS = { } as const; export type Region = keyof typeof REGIONS; +export type Env = 'test' | 'pre' | 'prod'; + +export const PLATFORM_HOSTS: Record> = { + cn: { + test: 'https://platform-test.xaminim.com', + pre: 'https://platform-pre.xaminim.com', + prod: 'https://platform.minimaxi.com', + }, + global: { + test: 'https://platform-us-test.xaminim.com', + pre: 'https://platform-us-pre.xaminim.com', + prod: 'https://platform.minimax.io', + }, +}; + +export const OAUTH_API_HOSTS: Record> = { + cn: { + test: 'https://account-test.xaminim.com', + pre: 'https://account-pre.xaminim.com', + prod: 'https://account.minimaxi.com', + }, + global: { + test: 'https://account-overseas-test.xaminim.com', + pre: 'https://account-overseas-pre.xaminim.com', + prod: 'https://account.minimax.io', + }, +}; export interface ConfigFile { api_key?: string; @@ -24,6 +51,7 @@ export interface ConfigFile { } const VALID_REGIONS = new Set(['global', 'cn']); +const VALID_ENVS = new Set(['test', 'pre', 'prod']); const VALID_OUTPUTS = new Set(['text', 'json']); export function parseConfigFile(raw: unknown): ConfigFile { @@ -51,7 +79,10 @@ export interface Config { fileRegion?: Region; configPath?: string; region: Region; + env: Env; baseUrl: string; + platformHost: string; + oauthApiHost: string; output: 'text' | 'json'; timeout: number; defaultTextModel?: string; diff --git a/src/main.ts b/src/main.ts index 0ad712f..3eb0f4c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import { detectRegion, saveDetectedRegion } from './config/detect-region'; import { REGIONS, type Region } from './config/schema'; import { checkForUpdate, getPendingUpdateNotification } from './update/checker'; import { loadCredentials } from './auth/credentials'; -import { ensureApiKey } from './auth/setup'; +import { ensureAuth } from './auth/setup'; import { CLI_VERSION } from './version'; import { ProxyAgent, setGlobalDispatcher } from 'undici'; @@ -75,7 +75,8 @@ async function main() { await quotaCmd.execute(config, flags); } else { process.stderr.write(' Not logged in.\n'); - process.stderr.write(' mmx auth login --api-key sk-xxxxx\n\n'); + process.stderr.write(' mmx auth login Log in with MiniMax account\n'); + process.stderr.write(' mmx auth login --api-key Log in with API key\n\n'); } process.exit(0); } @@ -91,7 +92,7 @@ async function main() { (cmd) => cmd.every((c, i) => commandPath[i] === c), ); if (needsAuthSetup) { - await ensureApiKey(config); + await ensureAuth(config); } if (config.needsRegionDetection) { diff --git a/src/types/flags.ts b/src/types/flags.ts index 4f29f85..7893794 100644 --- a/src/types/flags.ts +++ b/src/types/flags.ts @@ -1,6 +1,7 @@ export interface GlobalFlags { apiKey?: string; baseUrl?: string; + env?: string; output?: string; quiet: boolean; verbose: boolean; diff --git a/test/auth/resolver.test.ts b/test/auth/resolver.test.ts index f967803..5f6fe91 100644 --- a/test/auth/resolver.test.ts +++ b/test/auth/resolver.test.ts @@ -5,6 +5,9 @@ import type { Config } from '../../src/config/schema'; function makeConfig(overrides: Partial = {}): Config { return { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text', timeout: 300, diff --git a/test/client/http.test.ts b/test/client/http.test.ts index 07c0aeb..402383c 100644 --- a/test/client/http.test.ts +++ b/test/client/http.test.ts @@ -8,6 +8,9 @@ function makeConfig(baseUrl: string): Config { return { apiKey: 'test-api-key', region: 'global', + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl, output: 'text', timeout: 10, diff --git a/test/commands/auth/login.test.ts b/test/commands/auth/login.test.ts index 8095acc..85f68a5 100644 --- a/test/commands/auth/login.test.ts +++ b/test/commands/auth/login.test.ts @@ -10,6 +10,9 @@ describe('auth login command', () => { it('requires api key when method is api-key', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/auth/logout.test.ts b/test/commands/auth/logout.test.ts index 32bfdbf..7d7742b 100644 --- a/test/commands/auth/logout.test.ts +++ b/test/commands/auth/logout.test.ts @@ -9,6 +9,9 @@ describe('auth logout command', () => { it('handles dry run', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/auth/refresh.test.ts b/test/commands/auth/refresh.test.ts index 56d8fa6..e54a514 100644 --- a/test/commands/auth/refresh.test.ts +++ b/test/commands/auth/refresh.test.ts @@ -9,6 +9,9 @@ describe('auth refresh command', () => { it('errors when not using OAuth', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/auth/status.test.ts b/test/commands/auth/status.test.ts index ae93f2f..aa41136 100644 --- a/test/commands/auth/status.test.ts +++ b/test/commands/auth/status.test.ts @@ -9,6 +9,9 @@ describe('auth status command', () => { it('shows not authenticated when no credentials', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, timeout: 10, diff --git a/test/commands/config/set.test.ts b/test/commands/config/set.test.ts index 53f085e..2c1141d 100644 --- a/test/commands/config/set.test.ts +++ b/test/commands/config/set.test.ts @@ -15,6 +15,9 @@ describe('config set command', () => { it('requires key and value', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, @@ -44,6 +47,9 @@ describe('config set command', () => { it('validates config key', async () => { const config = { region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/config/show.test.ts b/test/commands/config/show.test.ts index e063b7c..b25413c 100644 --- a/test/commands/config/show.test.ts +++ b/test/commands/config/show.test.ts @@ -21,6 +21,9 @@ describe('config show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, timeout: 300, diff --git a/test/commands/file/upload.test.ts b/test/commands/file/upload.test.ts index c48994d..860fcb6 100644 --- a/test/commands/file/upload.test.ts +++ b/test/commands/file/upload.test.ts @@ -10,6 +10,9 @@ describe('file upload command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/image/generate.test.ts b/test/commands/image/generate.test.ts index fb29b6a..305e908 100644 --- a/test/commands/image/generate.test.ts +++ b/test/commands/image/generate.test.ts @@ -10,6 +10,9 @@ describe('image generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/music/generate.test.ts b/test/commands/music/generate.test.ts index e5d0b16..76f456b 100644 --- a/test/commands/music/generate.test.ts +++ b/test/commands/music/generate.test.ts @@ -4,6 +4,9 @@ import { default as generateCommand } from '../../../src/commands/music/generate const baseConfig = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/quota/show.test.ts b/test/commands/quota/show.test.ts index ebb88fd..d4182c3 100644 --- a/test/commands/quota/show.test.ts +++ b/test/commands/quota/show.test.ts @@ -10,6 +10,9 @@ describe('quota show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/search/query.test.ts b/test/commands/search/query.test.ts index 6784be1..8d75ef8 100644 --- a/test/commands/search/query.test.ts +++ b/test/commands/search/query.test.ts @@ -10,6 +10,9 @@ describe('search query command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/speech/synthesize.test.ts b/test/commands/speech/synthesize.test.ts index 8a49063..80c874b 100644 --- a/test/commands/speech/synthesize.test.ts +++ b/test/commands/speech/synthesize.test.ts @@ -10,6 +10,9 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, @@ -40,6 +43,9 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, timeout: 10, diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index 37ad015..5d333f0 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -23,6 +23,9 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, output: 'json', timeout: 10, @@ -66,6 +69,9 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json', timeout: 10, diff --git a/test/commands/video/download.test.ts b/test/commands/video/download.test.ts index dc8b14e..2ff3503 100644 --- a/test/commands/video/download.test.ts +++ b/test/commands/video/download.test.ts @@ -10,6 +10,9 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, @@ -40,6 +43,9 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/video/generate.test.ts b/test/commands/video/generate.test.ts index 9845a09..87a5d24 100644 --- a/test/commands/video/generate.test.ts +++ b/test/commands/video/generate.test.ts @@ -10,6 +10,9 @@ describe('video generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, diff --git a/test/commands/video/task-get.test.ts b/test/commands/video/task-get.test.ts index 29046d8..0bf6471 100644 --- a/test/commands/video/task-get.test.ts +++ b/test/commands/video/task-get.test.ts @@ -18,6 +18,9 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, @@ -54,6 +57,9 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, output: 'json' as const, timeout: 10, diff --git a/test/commands/vision/describe.test.ts b/test/commands/vision/describe.test.ts index 77db40f..68d7758 100644 --- a/test/commands/vision/describe.test.ts +++ b/test/commands/vision/describe.test.ts @@ -10,6 +10,9 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, @@ -40,6 +43,9 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, + env: 'prod' as const, + platformHost: 'https://platform.minimax.io', + oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, timeout: 10, From b175ae2fe7cc999973e5700a8c37e187dcf382ae Mon Sep 17 00:00:00 2001 From: jiemu Date: Mon, 13 Apr 2026 20:02:47 +0800 Subject: [PATCH 04/17] feat: verification_uri --- src/auth/oauth.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index cbeba35..450775b 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -176,10 +176,7 @@ export async function startDeviceCodeFlow( throw new CLIError('OAuth state mismatch: possible CSRF attack.', ExitCode.AUTH); } - const verificationUrl = new URL(data.verification_uri); - verificationUrl.searchParams.set('user_code', data.user_code); - verificationUrl.searchParams.set('client', config.clientName); - const url = verificationUrl.toString(); + const url = data.verification_uri; const { exec } = await import('child_process'); const openCmd = process.platform === 'darwin' ? 'open' : From 28b99d5257b32e71d5123956761325ebd16fc00c Mon Sep 17 00:00:00 2001 From: jiemu Date: Mon, 13 Apr 2026 21:42:23 +0800 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20=E5=88=86=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/loader.ts b/src/config/loader.ts index 9adaadf..a718c3d 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -75,7 +75,7 @@ export function loadConfig(flags: GlobalFlags): Config { env, baseUrl, platformHost: PLATFORM_HOSTS[region][env], - oauthApiHost: OAUTH_API_HOSTS[region][env], + oauthApiHost: process.env.MINIMAX_AUTH_URL || OAUTH_API_HOSTS[region][env], output, timeout, defaultTextModel: file.default_text_model, From f1791663e1f767294c6734acaaaf88e85a4f2881 Mon Sep 17 00:00:00 2001 From: jiemu Date: Wed, 15 Apr 2026 21:10:48 +0800 Subject: [PATCH 06/17] refactor: remove env flag, simplify platform/oauth host config to prod-only --- src/command.ts | 1 - src/config/loader.ts | 23 +++++++++++------ src/config/schema.ts | 33 +++++-------------------- src/main.ts | 2 +- src/types/flags.ts | 1 - test/auth/resolver.test.ts | 1 - test/client/http.test.ts | 1 - test/commands/auth/login.test.ts | 1 - test/commands/auth/logout.test.ts | 1 - test/commands/auth/refresh.test.ts | 1 - test/commands/auth/status.test.ts | 1 - test/commands/config/set.test.ts | 2 -- test/commands/config/show.test.ts | 1 - test/commands/file/upload.test.ts | 1 - test/commands/image/generate.test.ts | 1 - test/commands/music/generate.test.ts | 1 - test/commands/quota/show.test.ts | 1 - test/commands/search/query.test.ts | 1 - test/commands/speech/synthesize.test.ts | 2 -- test/commands/text/chat.test.ts | 2 -- test/commands/video/download.test.ts | 2 -- test/commands/video/generate.test.ts | 1 - test/commands/video/task-get.test.ts | 2 -- test/commands/vision/describe.test.ts | 2 -- 24 files changed, 23 insertions(+), 62 deletions(-) diff --git a/src/command.ts b/src/command.ts index 4ede2ec..8d89da8 100644 --- a/src/command.ts +++ b/src/command.ts @@ -44,7 +44,6 @@ export function defineCommand(spec: CommandSpec): Command { export const GLOBAL_OPTIONS: OptionDef[] = [ { flag: '--api-key ', description: 'API key' }, { flag: '--region ', description: 'API region: global, cn' }, - { flag: '--env ', description: 'Environment: test, pre, prod (default)' }, { flag: '--base-url ', description: 'API base URL' }, { flag: '--output ', description: 'Output format: text, json' }, { flag: '--timeout ', description: 'Request timeout', type: 'number' }, diff --git a/src/config/loader.ts b/src/config/loader.ts index a718c3d..af8e7ec 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -1,6 +1,6 @@ import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs'; -import { parseConfigFile, REGIONS, PLATFORM_HOSTS, OAUTH_API_HOSTS, type Config, type ConfigFile, type Region, type Env } from './schema'; -import { ensureConfigDir, getConfigPath } from './paths'; +import { parseConfigFile, REGIONS, PLATFORM_HOSTS, OAUTH_API_HOSTS, type Config, type ConfigFile, type Region } from './schema'; +import { ensureConfigDir, getConfigPath, getCredentialsPath } from './paths'; import { detectOutputFormat, type OutputFormat } from '../output/formatter'; import { CLIError } from '../errors/base'; import { ExitCode } from '../errors/codes'; @@ -28,6 +28,17 @@ export async function writeConfigFile(data: Record): Promise> = { - cn: { - test: 'https://platform-test.xaminim.com', - pre: 'https://platform-pre.xaminim.com', - prod: 'https://platform.minimaxi.com', - }, - global: { - test: 'https://platform-us-test.xaminim.com', - pre: 'https://platform-us-pre.xaminim.com', - prod: 'https://platform.minimax.io', - }, +export const PLATFORM_HOSTS: Record = { + cn: 'https://platform.minimaxi.com', + global: 'https://platform.minimax.io', }; -export const OAUTH_API_HOSTS: Record> = { - cn: { - test: 'https://account-test.xaminim.com', - pre: 'https://account-pre.xaminim.com', - prod: 'https://account.minimaxi.com', - }, - global: { - test: 'https://account-overseas-test.xaminim.com', - pre: 'https://account-overseas-pre.xaminim.com', - prod: 'https://account.minimax.io', - }, +export const OAUTH_API_HOSTS: Record = { + cn: 'https://account.minimaxi.com', + global: 'https://account.minimax.io', }; - export interface ConfigFile { api_key?: string; region?: Region; @@ -51,7 +32,6 @@ export interface ConfigFile { } const VALID_REGIONS = new Set(['global', 'cn']); -const VALID_ENVS = new Set(['test', 'pre', 'prod']); const VALID_OUTPUTS = new Set(['text', 'json']); export function parseConfigFile(raw: unknown): ConfigFile { @@ -79,7 +59,6 @@ export interface Config { fileRegion?: Region; configPath?: string; region: Region; - env: Env; baseUrl: string; platformHost: string; oauthApiHost: string; diff --git a/src/main.ts b/src/main.ts index 3eb0f4c..b1dff49 100644 --- a/src/main.ts +++ b/src/main.ts @@ -75,7 +75,7 @@ async function main() { await quotaCmd.execute(config, flags); } else { process.stderr.write(' Not logged in.\n'); - process.stderr.write(' mmx auth login Log in with MiniMax account\n'); + process.stderr.write(' mmx auth login Log in with MiniMax account by OAuth\n'); process.stderr.write(' mmx auth login --api-key Log in with API key\n\n'); } process.exit(0); diff --git a/src/types/flags.ts b/src/types/flags.ts index 7893794..4f29f85 100644 --- a/src/types/flags.ts +++ b/src/types/flags.ts @@ -1,7 +1,6 @@ export interface GlobalFlags { apiKey?: string; baseUrl?: string; - env?: string; output?: string; quiet: boolean; verbose: boolean; diff --git a/test/auth/resolver.test.ts b/test/auth/resolver.test.ts index 5f6fe91..aec310b 100644 --- a/test/auth/resolver.test.ts +++ b/test/auth/resolver.test.ts @@ -5,7 +5,6 @@ import type { Config } from '../../src/config/schema'; function makeConfig(overrides: Partial = {}): Config { return { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/client/http.test.ts b/test/client/http.test.ts index 402383c..a293652 100644 --- a/test/client/http.test.ts +++ b/test/client/http.test.ts @@ -8,7 +8,6 @@ function makeConfig(baseUrl: string): Config { return { apiKey: 'test-api-key', region: 'global', - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl, diff --git a/test/commands/auth/login.test.ts b/test/commands/auth/login.test.ts index 85f68a5..df3c81a 100644 --- a/test/commands/auth/login.test.ts +++ b/test/commands/auth/login.test.ts @@ -10,7 +10,6 @@ describe('auth login command', () => { it('requires api key when method is api-key', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/auth/logout.test.ts b/test/commands/auth/logout.test.ts index 7d7742b..cf95db9 100644 --- a/test/commands/auth/logout.test.ts +++ b/test/commands/auth/logout.test.ts @@ -9,7 +9,6 @@ describe('auth logout command', () => { it('handles dry run', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/auth/refresh.test.ts b/test/commands/auth/refresh.test.ts index e54a514..ee849d5 100644 --- a/test/commands/auth/refresh.test.ts +++ b/test/commands/auth/refresh.test.ts @@ -9,7 +9,6 @@ describe('auth refresh command', () => { it('errors when not using OAuth', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/auth/status.test.ts b/test/commands/auth/status.test.ts index aa41136..724af22 100644 --- a/test/commands/auth/status.test.ts +++ b/test/commands/auth/status.test.ts @@ -9,7 +9,6 @@ describe('auth status command', () => { it('shows not authenticated when no credentials', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/config/set.test.ts b/test/commands/config/set.test.ts index 2c1141d..f6cb294 100644 --- a/test/commands/config/set.test.ts +++ b/test/commands/config/set.test.ts @@ -15,7 +15,6 @@ describe('config set command', () => { it('requires key and value', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', @@ -47,7 +46,6 @@ describe('config set command', () => { it('validates config key', async () => { const config = { region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/config/show.test.ts b/test/commands/config/show.test.ts index b25413c..46ce90c 100644 --- a/test/commands/config/show.test.ts +++ b/test/commands/config/show.test.ts @@ -21,7 +21,6 @@ describe('config show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/file/upload.test.ts b/test/commands/file/upload.test.ts index 860fcb6..daa562c 100644 --- a/test/commands/file/upload.test.ts +++ b/test/commands/file/upload.test.ts @@ -10,7 +10,6 @@ describe('file upload command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/image/generate.test.ts b/test/commands/image/generate.test.ts index 305e908..a07ffd5 100644 --- a/test/commands/image/generate.test.ts +++ b/test/commands/image/generate.test.ts @@ -10,7 +10,6 @@ describe('image generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/music/generate.test.ts b/test/commands/music/generate.test.ts index 76f456b..8af773f 100644 --- a/test/commands/music/generate.test.ts +++ b/test/commands/music/generate.test.ts @@ -4,7 +4,6 @@ import { default as generateCommand } from '../../../src/commands/music/generate const baseConfig = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/quota/show.test.ts b/test/commands/quota/show.test.ts index d4182c3..a836d73 100644 --- a/test/commands/quota/show.test.ts +++ b/test/commands/quota/show.test.ts @@ -10,7 +10,6 @@ describe('quota show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/search/query.test.ts b/test/commands/search/query.test.ts index 8d75ef8..94c477e 100644 --- a/test/commands/search/query.test.ts +++ b/test/commands/search/query.test.ts @@ -10,7 +10,6 @@ describe('search query command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/speech/synthesize.test.ts b/test/commands/speech/synthesize.test.ts index 80c874b..c1818be 100644 --- a/test/commands/speech/synthesize.test.ts +++ b/test/commands/speech/synthesize.test.ts @@ -10,7 +10,6 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', @@ -43,7 +42,6 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index 5d333f0..6e6d795 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -23,7 +23,6 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, @@ -69,7 +68,6 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/video/download.test.ts b/test/commands/video/download.test.ts index 2ff3503..04ee162 100644 --- a/test/commands/video/download.test.ts +++ b/test/commands/video/download.test.ts @@ -10,7 +10,6 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', @@ -43,7 +42,6 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/video/generate.test.ts b/test/commands/video/generate.test.ts index 87a5d24..c654a35 100644 --- a/test/commands/video/generate.test.ts +++ b/test/commands/video/generate.test.ts @@ -10,7 +10,6 @@ describe('video generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', diff --git a/test/commands/video/task-get.test.ts b/test/commands/video/task-get.test.ts index 0bf6471..246dcad 100644 --- a/test/commands/video/task-get.test.ts +++ b/test/commands/video/task-get.test.ts @@ -18,7 +18,6 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', @@ -57,7 +56,6 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, diff --git a/test/commands/vision/describe.test.ts b/test/commands/vision/describe.test.ts index 68d7758..c7db879 100644 --- a/test/commands/vision/describe.test.ts +++ b/test/commands/vision/describe.test.ts @@ -10,7 +10,6 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', @@ -43,7 +42,6 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - env: 'prod' as const, platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', From b56d7fe6d0729afde5119244484cf7e8e4f9cc2d Mon Sep 17 00:00:00 2001 From: jiemu Date: Fri, 17 Apr 2026 13:26:50 +0800 Subject: [PATCH 07/17] feat: region select --- src/commands/auth/login.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index fe6fbb2..45f97bb 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -11,6 +11,7 @@ import { getConfigPath } from '../../config/paths'; import { readConfigFile, writeConfigFile } from '../../config/loader'; import { isInteractive } from '../../utils/env'; import { maskToken } from '../../utils/token'; +import { PLATFORM_HOSTS, OAUTH_API_HOSTS, type Region } from '../../config/schema'; import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; import type { CredentialFile } from '../../auth/types'; @@ -110,12 +111,36 @@ export default defineCommand({ return; } + // If no region was explicitly specified via flag or env, let the user choose interactively + let region = config.region; + if (!flags.region && !process.env.MINIMAX_REGION) { + if (isInteractive({ nonInteractive: config.nonInteractive })) { + const { select } = await import('@clack/prompts'); + const chosen = await select({ + message: 'Select your region', + options: [ + { value: 'cn', label: 'minimax.com (China)' }, + { value: 'global', label: 'minimax.io (Global)' }, + ], + }); + if (typeof chosen === 'string') { + region = chosen as Region; + const existing = readConfigFile() as Record; + existing.region = region; + await writeConfigFile(existing); + } + } + } + + const platformHost = process.env.MINIMAX_PLATFORM_URL || PLATFORM_HOSTS[region]; + const oauthApiHost = process.env.MINIMAX_AUTH_URL || OAUTH_API_HOSTS[region]; + const oauthConfig: OAuthConfig = { clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', clientName: 'MiniMax CLI', - authorizationUrl: `${config.platformHost}/oauth-authorize`, - tokenUrl: `${config.oauthApiHost}/oauth2/token`, - deviceCodeUrl: `${config.oauthApiHost}/oauth2/device/code`, + authorizationUrl: `${platformHost}/oauth-authorize`, + tokenUrl: `${oauthApiHost}/oauth2/token`, + deviceCodeUrl: `${oauthApiHost}/oauth2/device/code`, scopes: ['openid', 'profile', 'coding_plan'], callbackPort: 18991, }; From a33bb5f22492101c3ca5761097f146fe3859625a Mon Sep 17 00:00:00 2001 From: jiemu Date: Sat, 18 Apr 2026 13:12:59 +0800 Subject: [PATCH 08/17] feat: propagate BEDROCK_LANE header to all API requests --- src/client/http.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/http.ts b/src/client/http.ts index c77ba79..e01e50b 100644 --- a/src/client/http.ts +++ b/src/client/http.ts @@ -26,6 +26,11 @@ export async function request(config: Config, opts: RequestOpts): Promise Date: Tue, 21 Apr 2026 17:29:13 +0800 Subject: [PATCH 09/17] fix: add BEDROCK_LANE header to token refresh and fix duplicate /anthropic in endpoint URL --- src/client/endpoints.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/endpoints.ts b/src/client/endpoints.ts index b8337a0..95f164c 100644 --- a/src/client/endpoints.ts +++ b/src/client/endpoints.ts @@ -1,5 +1,6 @@ export function chatEndpoint(baseUrl: string): string { - return `${baseUrl}/anthropic/v1/messages`; + const base = baseUrl.replace(/\/anthropic\/?$/, ''); + return `${base}/anthropic/v1/messages`; } export function speechEndpoint(baseUrl: string): string { From ff44e394f0e9f506e18f813c4e93307654932ad5 Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 21 Apr 2026 17:31:21 +0800 Subject: [PATCH 10/17] feat: propagate X-User-Pre header for pre-environment ys-user routing --- src/auth/oauth.ts | 1 + src/client/http.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 450775b..4dab870 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -141,6 +141,7 @@ export async function startDeviceCodeFlow( const lane = process.env.BEDROCK_LANE; const extraHeaders: Record = lane ? { bedrock_lane: lane } : {}; + if (process.env.X_USER_PRE) extraHeaders['X-User-Pre'] = 'true'; // Request device code with PKCE const codeRes = await fetch(config.deviceCodeUrl, { diff --git a/src/client/http.ts b/src/client/http.ts index e01e50b..eb2f2a2 100644 --- a/src/client/http.ts +++ b/src/client/http.ts @@ -30,6 +30,9 @@ export async function request(config: Config, opts: RequestOpts): Promise Date: Fri, 8 May 2026 14:50:23 +0800 Subject: [PATCH 11/17] feat: lock --- package-lock.json | 410 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 390 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 092190a..bae9a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,27 @@ { "name": "mmx-cli", - "version": "1.0.5", + "version": "1.0.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mmx-cli", - "version": "1.0.5", + "version": "1.0.12", "dependencies": { - "@clack/prompts": "^0.7.0", - "yaml": "^2.7.1" + "@clack/prompts": "^0.7.0" }, "bin": { "mmx": "dist/mmx.mjs" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@types/bun": "latest", "eslint": "^9.24.0", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "typescript-eslint": "^8.58.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@clack/core": { @@ -47,6 +51,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -286,6 +291,288 @@ "undici-types": "~7.18.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://npmmirror.xaminim.com/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://npmmirror.xaminim.com/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://npmmirror.xaminim.com/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://npmmirror.xaminim.com/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://npmmirror.xaminim.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", @@ -659,6 +946,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://npmmirror.xaminim.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -1010,6 +1315,19 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://npmmirror.xaminim.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1040,6 +1358,19 @@ "node": ">=4" } }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://npmmirror.xaminim.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1095,6 +1426,36 @@ "node": ">=8" } }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://npmmirror.xaminim.com/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://npmmirror.xaminim.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -1122,6 +1483,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.2", + "resolved": "https://npmmirror.xaminim.com/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", @@ -1165,21 +1550,6 @@ "node": ">=0.10.0" } }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", From 89548d88f1f489f284f3beb4c50d3319232b090a Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 11:48:24 +0800 Subject: [PATCH 12/17] refactor: remove unused browser-based Authorization Code Flow The CLI only uses Device Code Flow. Remove dead code: startBrowserFlow, waitForCallback, PLATFORM_HOSTS, platformHost config, authorizationUrl and callbackPort from OAuthConfig. --- src/auth/oauth.ts | 117 ------------------------------------- src/auth/setup.ts | 2 - src/commands/auth/login.ts | 5 +- src/config/loader.ts | 3 +- src/config/schema.ts | 5 -- 5 files changed, 2 insertions(+), 130 deletions(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 4dab870..842e469 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -6,126 +6,9 @@ import { ExitCode } from '../errors/codes'; export interface OAuthConfig { clientId: string; clientName: string; - authorizationUrl: string; tokenUrl: string; deviceCodeUrl: string; scopes: string[]; - callbackPort: number; -} - -export async function startBrowserFlow( - config: OAuthConfig, -): Promise { - const { randomBytes, createHash } = await import('crypto'); - const codeVerifier = randomBytes(32).toString('base64url'); - const codeChallenge = createHash('sha256') - .update(codeVerifier) - .digest('base64url'); - - const state = randomBytes(16).toString('hex'); - - const params = new URLSearchParams({ - client_id: config.clientId, - response_type: 'code', - redirect_uri: `http://localhost:${config.callbackPort}/callback`, - scope: config.scopes.join(' '), - state, - code_challenge: codeChallenge, - code_challenge_method: 'S256', - }); - - const authUrl = `${config.authorizationUrl}?${params}`; - - // Open browser using execFile/spawn instead of exec to prevent shell injection. - // exec() passes the string to a shell, so a crafted authUrl containing shell - // metacharacters (e.g. from a malicious authorization server redirect) could - // execute arbitrary commands. execFile/spawn bypass the shell entirely. (#79) - const { execFile, spawn } = await import('child_process'); - const platform = process.platform; - - if (platform === 'darwin') { - execFile('open', [authUrl]); - } else if (platform === 'win32') { - // On Windows, 'start' is a shell built-in — use cmd.exe /c start explicitly. - spawn('cmd.exe', ['/c', 'start', '', authUrl], { shell: false, detached: true }); - } else { - execFile('xdg-open', [authUrl]); - } - process.stderr.write('Opening browser to authenticate with MiniMax...\n'); - - // Start local server to receive callback - const code = await waitForCallback(config.callbackPort, state); - - // Exchange code for tokens - const tokenRes = await fetch(config.tokenUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - code, - client_id: config.clientId, - redirect_uri: `http://localhost:${config.callbackPort}/callback`, - code_verifier: codeVerifier, - }), - }); - - if (!tokenRes.ok) { - const body = await tokenRes.text(); - throw new CLIError( - `OAuth token exchange failed: ${body}`, - ExitCode.AUTH, - ); - } - - return (await tokenRes.json()) as OAuthTokens; -} - -async function waitForCallback(port: number, expectedState: string): Promise { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - server.stop(); - reject(new CLIError('OAuth callback timed out.', ExitCode.TIMEOUT)); - }, 120_000); - - const server = Bun.serve({ - port, - fetch(req) { - const url = new URL(req.url); - if (url.pathname !== '/callback') { - return new Response('Not found', { status: 404 }); - } - - const code = url.searchParams.get('code'); - const state = url.searchParams.get('state'); - const error = url.searchParams.get('error'); - - if (error) { - clearTimeout(timeout); - server.stop(); - reject(new CLIError(`OAuth error: ${error}`, ExitCode.AUTH)); - return new Response( - '

Authentication Failed

You can close this tab.

', - { headers: { 'Content-Type': 'text/html' } }, - ); - } - - if (!code || state !== expectedState) { - clearTimeout(timeout); - server.stop(); - reject(new CLIError('Invalid OAuth callback.', ExitCode.AUTH)); - return new Response('Invalid callback', { status: 400 }); - } - - clearTimeout(timeout); - server.stop(); - resolve(code); - return new Response( - '

Authentication Successful

You can close this tab.

', - { headers: { 'Content-Type': 'text/html' } }, - ); - }, - }); - }); } export async function startDeviceCodeFlow( diff --git a/src/auth/setup.ts b/src/auth/setup.ts index 0844ac9..85f1d43 100644 --- a/src/auth/setup.ts +++ b/src/auth/setup.ts @@ -57,11 +57,9 @@ export async function ensureAuth(config: Config): Promise { const oauthConfig: OAuthConfig = { clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', clientName: 'MiniMax CLI', - authorizationUrl: `${config.platformHost}/oauth-authorize`, tokenUrl: `${config.oauthApiHost}/oauth2/token`, deviceCodeUrl: `${config.oauthApiHost}/oauth2/device/code`, scopes: ['openid', 'profile', 'coding_plan'], - callbackPort: 18991, }; const tokens = await startDeviceCodeFlow(oauthConfig); const creds: CredentialFile = { diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index 45f97bb..e24c4e3 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -11,7 +11,7 @@ import { getConfigPath } from '../../config/paths'; import { readConfigFile, writeConfigFile } from '../../config/loader'; import { isInteractive } from '../../utils/env'; import { maskToken } from '../../utils/token'; -import { PLATFORM_HOSTS, OAUTH_API_HOSTS, type Region } from '../../config/schema'; +import { OAUTH_API_HOSTS, type Region } from '../../config/schema'; import type { Config } from '../../config/schema'; import type { GlobalFlags } from '../../types/flags'; import type { CredentialFile } from '../../auth/types'; @@ -132,17 +132,14 @@ export default defineCommand({ } } - const platformHost = process.env.MINIMAX_PLATFORM_URL || PLATFORM_HOSTS[region]; const oauthApiHost = process.env.MINIMAX_AUTH_URL || OAUTH_API_HOSTS[region]; const oauthConfig: OAuthConfig = { clientId: '659cf4c1-615c-45f6-a5f6-4bf15eb476e5', clientName: 'MiniMax CLI', - authorizationUrl: `${platformHost}/oauth-authorize`, tokenUrl: `${oauthApiHost}/oauth2/token`, deviceCodeUrl: `${oauthApiHost}/oauth2/device/code`, scopes: ['openid', 'profile', 'coding_plan'], - callbackPort: 18991, }; const tokens = await startDeviceCodeFlow(oauthConfig); diff --git a/src/config/loader.ts b/src/config/loader.ts index af8e7ec..74aedd8 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -1,5 +1,5 @@ import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs'; -import { parseConfigFile, REGIONS, PLATFORM_HOSTS, OAUTH_API_HOSTS, type Config, type ConfigFile, type Region } from './schema'; +import { parseConfigFile, REGIONS, OAUTH_API_HOSTS, type Config, type ConfigFile, type Region } from './schema'; import { ensureConfigDir, getConfigPath, getCredentialsPath } from './paths'; import { detectOutputFormat, type OutputFormat } from '../output/formatter'; import { CLIError } from '../errors/base'; @@ -83,7 +83,6 @@ export function loadConfig(flags: GlobalFlags): Config { configPath: getConfigPath(), region, baseUrl, - platformHost: process.env.MINIMAX_PLATFORM_URL || PLATFORM_HOSTS[region], oauthApiHost: process.env.MINIMAX_AUTH_URL || OAUTH_API_HOSTS[region], output, timeout, diff --git a/src/config/schema.ts b/src/config/schema.ts index 541a9a7..2c1e9b6 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -9,10 +9,6 @@ export const DOCS_HOSTS = { } as const; export type Region = keyof typeof REGIONS; -export const PLATFORM_HOSTS: Record = { - cn: 'https://platform.minimaxi.com', - global: 'https://platform.minimax.io', -}; export const OAUTH_API_HOSTS: Record = { cn: 'https://account.minimaxi.com', @@ -60,7 +56,6 @@ export interface Config { configPath?: string; region: Region; baseUrl: string; - platformHost: string; oauthApiHost: string; output: 'text' | 'json'; timeout: number; From afb193af40de1a86d9ecd8e09f71054a9465ba17 Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 11:49:04 +0800 Subject: [PATCH 13/17] chore: update package-lock.json for new upstream dependencies --- package-lock.json | 231 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 223 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index bae9a23..a2efb7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,17 @@ { "name": "mmx-cli", - "version": "1.0.12", + "version": "1.0.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mmx-cli", - "version": "1.0.12", + "version": "1.0.13", "dependencies": { - "@clack/prompts": "^0.7.0" + "@clack/prompts": "^0.7.0", + "bun-plugin-dts": "^0.4.0", + "es-toolkit": "^1.46.1", + "undici": "^6.21.1" }, "bin": { "mmx": "dist/mmx.mjs" @@ -51,7 +54,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -613,11 +615,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://npmmirror.xaminim.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -654,6 +664,17 @@ "concat-map": "0.0.1" } }, + "node_modules/bun-plugin-dts": { + "version": "0.4.0", + "resolved": "https://npmmirror.xaminim.com/bun-plugin-dts/-/bun-plugin-dts-0.4.0.tgz", + "integrity": "sha512-g/pHy9SuhnUw+E+bHnJvADOnnZlEIci3nvZY8EuQEMwkpC4V4Kmoa2nG9nfda4jmjj+0POlCRCjdqXrL9gjYtA==", + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "dts-bundle-generator": "^9.5.1", + "get-tsconfig": "^4.13.6" + } + }, "node_modules/bun-types": { "version": "1.3.11", "resolved": "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.11.tgz", @@ -691,11 +712,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://npmmirror.xaminim.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -708,9 +742,14 @@ "version": "1.1.4", "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://npmmirror.xaminim.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -758,6 +797,47 @@ "dev": true, "license": "MIT" }, + "node_modules/dts-bundle-generator": { + "version": "9.5.1", + "resolved": "https://npmmirror.xaminim.com/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz", + "integrity": "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA==", + "license": "MIT", + "dependencies": { + "typescript": ">=5.0.2", + "yargs": "^17.6.0" + }, + "bin": { + "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://npmmirror.xaminim.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://npmmirror.xaminim.com/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://npmmirror.xaminim.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1015,6 +1095,27 @@ "dev": true, "license": "ISC" }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://npmmirror.xaminim.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://npmmirror.xaminim.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1098,6 +1199,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://npmmirror.xaminim.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", @@ -1348,6 +1458,15 @@ "node": ">=6" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://npmmirror.xaminim.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1358,6 +1477,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://npmmirror.xaminim.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://npmmirror.xaminim.com/semver/-/semver-7.7.4.tgz", @@ -1400,6 +1528,32 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://npmmirror.xaminim.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://npmmirror.xaminim.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1473,7 +1627,6 @@ "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -1507,6 +1660,15 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://npmmirror.xaminim.com/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", @@ -1550,6 +1712,59 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://npmmirror.xaminim.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://npmmirror.xaminim.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://npmmirror.xaminim.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://npmmirror.xaminim.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", From 30eb34cf3b9effd26aa0e1d144f7c57f0f668358 Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 12:01:18 +0800 Subject: [PATCH 14/17] feat: v1 --- src/client/endpoints.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/endpoints.ts b/src/client/endpoints.ts index 95f164c..291792a 100644 --- a/src/client/endpoints.ts +++ b/src/client/endpoints.ts @@ -1,6 +1,5 @@ export function chatEndpoint(baseUrl: string): string { - const base = baseUrl.replace(/\/anthropic\/?$/, ''); - return `${base}/anthropic/v1/messages`; + return `${baseUrl}/v1/messages`; } export function speechEndpoint(baseUrl: string): string { From 8545a71da965df046e4f3635f63c2c1271f6b8bb Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 12:21:59 +0800 Subject: [PATCH 15/17] fix: normalize baseUrl in chatEndpoint to prevent duplicate /anthropic --- src/client/endpoints.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/endpoints.ts b/src/client/endpoints.ts index 291792a..95f164c 100644 --- a/src/client/endpoints.ts +++ b/src/client/endpoints.ts @@ -1,5 +1,6 @@ export function chatEndpoint(baseUrl: string): string { - return `${baseUrl}/v1/messages`; + const base = baseUrl.replace(/\/anthropic\/?$/, ''); + return `${base}/anthropic/v1/messages`; } export function speechEndpoint(baseUrl: string): string { From 56b82603fd6b8e72ade3556e8b651d50368b6b26 Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 12:34:38 +0800 Subject: [PATCH 16/17] revert: restore upstream chatEndpoint without baseUrl normalization resource_url from OAuth will not include /anthropic suffix. --- src/client/endpoints.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/endpoints.ts b/src/client/endpoints.ts index 95f164c..b8337a0 100644 --- a/src/client/endpoints.ts +++ b/src/client/endpoints.ts @@ -1,6 +1,5 @@ export function chatEndpoint(baseUrl: string): string { - const base = baseUrl.replace(/\/anthropic\/?$/, ''); - return `${base}/anthropic/v1/messages`; + return `${baseUrl}/anthropic/v1/messages`; } export function speechEndpoint(baseUrl: string): string { From 9f21e358c77598acb051a576b82315ae2e62dc13 Mon Sep 17 00:00:00 2001 From: jiemu Date: Tue, 12 May 2026 12:41:31 +0800 Subject: [PATCH 17/17] chore: remove platformHost from test mock configs --- test/auth/resolver.test.ts | 1 - test/client/http.test.ts | 1 - test/commands/auth/login.test.ts | 1 - test/commands/auth/logout.test.ts | 1 - test/commands/auth/refresh.test.ts | 1 - test/commands/auth/status.test.ts | 1 - test/commands/config/set.test.ts | 2 -- test/commands/config/show.test.ts | 1 - test/commands/file/upload.test.ts | 1 - test/commands/image/generate.test.ts | 1 - test/commands/music/generate.test.ts | 1 - test/commands/quota/show.test.ts | 1 - test/commands/search/query.test.ts | 1 - test/commands/speech/synthesize.test.ts | 2 -- test/commands/text/chat.test.ts | 2 -- test/commands/video/download.test.ts | 2 -- test/commands/video/generate.test.ts | 1 - test/commands/video/task-get.test.ts | 2 -- test/commands/vision/describe.test.ts | 2 -- 19 files changed, 25 deletions(-) diff --git a/test/auth/resolver.test.ts b/test/auth/resolver.test.ts index aec310b..8187498 100644 --- a/test/auth/resolver.test.ts +++ b/test/auth/resolver.test.ts @@ -5,7 +5,6 @@ import type { Config } from '../../src/config/schema'; function makeConfig(overrides: Partial = {}): Config { return { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text', diff --git a/test/client/http.test.ts b/test/client/http.test.ts index a293652..31c948d 100644 --- a/test/client/http.test.ts +++ b/test/client/http.test.ts @@ -8,7 +8,6 @@ function makeConfig(baseUrl: string): Config { return { apiKey: 'test-api-key', region: 'global', - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl, output: 'text', diff --git a/test/commands/auth/login.test.ts b/test/commands/auth/login.test.ts index df3c81a..4b4ce77 100644 --- a/test/commands/auth/login.test.ts +++ b/test/commands/auth/login.test.ts @@ -10,7 +10,6 @@ describe('auth login command', () => { it('requires api key when method is api-key', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/auth/logout.test.ts b/test/commands/auth/logout.test.ts index cf95db9..d577025 100644 --- a/test/commands/auth/logout.test.ts +++ b/test/commands/auth/logout.test.ts @@ -9,7 +9,6 @@ describe('auth logout command', () => { it('handles dry run', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/auth/refresh.test.ts b/test/commands/auth/refresh.test.ts index ee849d5..4a6890e 100644 --- a/test/commands/auth/refresh.test.ts +++ b/test/commands/auth/refresh.test.ts @@ -9,7 +9,6 @@ describe('auth refresh command', () => { it('errors when not using OAuth', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/auth/status.test.ts b/test/commands/auth/status.test.ts index 724af22..bcc41e2 100644 --- a/test/commands/auth/status.test.ts +++ b/test/commands/auth/status.test.ts @@ -9,7 +9,6 @@ describe('auth status command', () => { it('shows not authenticated when no credentials', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, diff --git a/test/commands/config/set.test.ts b/test/commands/config/set.test.ts index f6cb294..a5439f9 100644 --- a/test/commands/config/set.test.ts +++ b/test/commands/config/set.test.ts @@ -15,7 +15,6 @@ describe('config set command', () => { it('requires key and value', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, @@ -46,7 +45,6 @@ describe('config set command', () => { it('validates config key', async () => { const config = { region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/config/show.test.ts b/test/commands/config/show.test.ts index 46ce90c..6576883 100644 --- a/test/commands/config/show.test.ts +++ b/test/commands/config/show.test.ts @@ -21,7 +21,6 @@ describe('config show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, diff --git a/test/commands/file/upload.test.ts b/test/commands/file/upload.test.ts index daa562c..d51e344 100644 --- a/test/commands/file/upload.test.ts +++ b/test/commands/file/upload.test.ts @@ -10,7 +10,6 @@ describe('file upload command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/image/generate.test.ts b/test/commands/image/generate.test.ts index a07ffd5..f776df9 100644 --- a/test/commands/image/generate.test.ts +++ b/test/commands/image/generate.test.ts @@ -10,7 +10,6 @@ describe('image generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/music/generate.test.ts b/test/commands/music/generate.test.ts index 8af773f..de3825c 100644 --- a/test/commands/music/generate.test.ts +++ b/test/commands/music/generate.test.ts @@ -4,7 +4,6 @@ import { default as generateCommand } from '../../../src/commands/music/generate const baseConfig = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/quota/show.test.ts b/test/commands/quota/show.test.ts index a836d73..9f7fdec 100644 --- a/test/commands/quota/show.test.ts +++ b/test/commands/quota/show.test.ts @@ -10,7 +10,6 @@ describe('quota show command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/search/query.test.ts b/test/commands/search/query.test.ts index 94c477e..44fa942 100644 --- a/test/commands/search/query.test.ts +++ b/test/commands/search/query.test.ts @@ -10,7 +10,6 @@ describe('search query command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/speech/synthesize.test.ts b/test/commands/speech/synthesize.test.ts index c1818be..304a11d 100644 --- a/test/commands/speech/synthesize.test.ts +++ b/test/commands/speech/synthesize.test.ts @@ -10,7 +10,6 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, @@ -42,7 +41,6 @@ describe('speech synthesize command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json' as const, diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index 6e6d795..e5751ca 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -23,7 +23,6 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, output: 'json', @@ -68,7 +67,6 @@ describe('text chat command', () => { const config: Config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'json', diff --git a/test/commands/video/download.test.ts b/test/commands/video/download.test.ts index 04ee162..aad6464 100644 --- a/test/commands/video/download.test.ts +++ b/test/commands/video/download.test.ts @@ -10,7 +10,6 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, @@ -42,7 +41,6 @@ describe('video download command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/video/generate.test.ts b/test/commands/video/generate.test.ts index c654a35..7c564d8 100644 --- a/test/commands/video/generate.test.ts +++ b/test/commands/video/generate.test.ts @@ -10,7 +10,6 @@ describe('video generate command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, diff --git a/test/commands/video/task-get.test.ts b/test/commands/video/task-get.test.ts index 246dcad..114a58f 100644 --- a/test/commands/video/task-get.test.ts +++ b/test/commands/video/task-get.test.ts @@ -18,7 +18,6 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, @@ -56,7 +55,6 @@ describe('video task get command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: server.url, output: 'json' as const, diff --git a/test/commands/vision/describe.test.ts b/test/commands/vision/describe.test.ts index c7db879..1dfb2da 100644 --- a/test/commands/vision/describe.test.ts +++ b/test/commands/vision/describe.test.ts @@ -10,7 +10,6 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const, @@ -42,7 +41,6 @@ describe('vision describe command', () => { const config = { apiKey: 'test-key', region: 'global' as const, - platformHost: 'https://platform.minimax.io', oauthApiHost: 'https://account.minimax.io', baseUrl: 'https://api.mmx.io', output: 'text' as const,