11import { defineCommand } from '../../command' ;
22import { CLIError } from '../../errors/base' ;
33import { ExitCode } from '../../errors/codes' ;
4- import { pickAuthMethod , runOAuthLogin } from '../../auth/setup' ;
4+ import { pickAuthMethod , pickOAuthRegion , runOAuthLogin } from '../../auth/setup' ;
55import { requestJson } from '../../client/http' ;
66import { quotaEndpoint } from '../../client/endpoints' ;
77import { renderQuotaTable } from '../../output/quota-table' ;
8+ import { detectRegion } from '../../config/detect-region' ;
89
910import { getConfigPath } from '../../config/paths' ;
1011import { readConfigFile , writeConfigFile } from '../../config/loader' ;
@@ -13,7 +14,7 @@ import { maskToken } from '../../utils/token';
1314import type { Config , Region } from '../../config/schema' ;
1415import { REGIONS } from '../../config/schema' ;
1516import type { GlobalFlags } from '../../types/flags' ;
16- import type { QuotaResponse , QuotaModelRemain } from '../../types/api' ;
17+ import type { QuotaModelRemain } from '../../types/api' ;
1718
1819interface QuotaApiResponse {
1920 model_remains : QuotaModelRemain [ ] ;
@@ -43,14 +44,17 @@ async function showDashboardAfterLogin(config: Config): Promise<void> {
4344export default defineCommand ( {
4445 name : 'auth login' ,
4546 description : 'Authenticate via OAuth or API key' ,
46- usage : 'mmx auth login [--api-key <key>] [--region global|cn]' ,
47+ usage : 'mmx auth login [--api-key <key>] [--recommend] [--region= global|cn]' ,
4748 options : [
4849 { flag : '--api-key <key>' , description : 'Skip the menu and save this API key directly' } ,
50+ { flag : '--recommend' , description : 'Skip the menu, go straight to OAuth (combine with --region= to skip the region picker)' } ,
4951 ] ,
5052 examples : [
5153 'mmx auth login' ,
5254 'mmx auth login --api-key sk-cp-xxxxx' ,
53- 'mmx auth login --region cn' ,
55+ 'mmx auth login --recommend' ,
56+ 'mmx auth login --recommend --region=global' ,
57+ 'mmx auth login --recommend --region=cn' ,
5458 ] ,
5559 async run ( config : Config , flags : GlobalFlags ) {
5660 const envKey = process . env . MINIMAX_API_KEY ;
@@ -89,6 +93,14 @@ export default defineCommand({
8993 ) ;
9094 }
9195
96+ // --recommend: skip the 3-option menu, go straight to OAuth.
97+ // With --region, skip the region picker too.
98+ if ( flags . recommend ) {
99+ const region = ( flags . region as Region ) || await pickOAuthRegion ( ) ;
100+ await completeOAuthLogin ( config , region ) ;
101+ return ;
102+ }
103+
92104 const choice = await pickAuthMethod ( ) ;
93105 if ( choice === 'api-key' ) {
94106 const { text, isCancel } = await import ( '@clack/prompts' ) ;
@@ -102,30 +114,38 @@ export default defineCommand({
102114 }
103115
104116 const region : Region = ( flags . region as Region ) || ( choice === 'oauth-cn' ? 'cn' : 'global' ) ;
105- await runOAuthLogin ( region ) ;
106-
107- // Reload config so the OAuth subobject (and its resource_url) are picked up.
108- const fresh = readConfigFile ( ) ;
109- const cfg : Config = {
110- ...config ,
111- region,
112- baseUrl : fresh . oauth ?. resource_url || REGIONS [ region ] ,
113- } ;
114- await showDashboardAfterLogin ( cfg ) ;
117+ await completeOAuthLogin ( config , region ) ;
115118 } ,
116119} ) ;
117120
121+ async function completeOAuthLogin ( config : Config , region : Region ) : Promise < void > {
122+ await runOAuthLogin ( region ) ;
123+
124+ // Reload so the OAuth subobject (and any resource_url override) is picked up.
125+ const fresh = readConfigFile ( ) ;
126+ const cfg : Config = {
127+ ...config ,
128+ region,
129+ baseUrl : fresh . oauth ?. resource_url || REGIONS [ region ] ,
130+ } ;
131+ await showDashboardAfterLogin ( cfg ) ;
132+ }
133+
118134async function loginWithApiKey ( config : Config , key : string ) : Promise < void > {
119135 if ( config . dryRun ) {
120136 console . log ( 'Would validate and save API key.' ) ;
121137 return ;
122138 }
123139
124- process . stderr . write ( 'Testing key... ' ) ;
140+ // Probe both regions and pick the one the key actually authenticates against.
141+ // This doubles as key validation — if neither region accepts it, the key is bad.
142+ const detected = await detectRegion ( key ) ;
143+ const cfg : Config = { ...config , region : detected , baseUrl : REGIONS [ detected ] , apiKey : key } ;
144+
145+ // Verify the detection actually authorizes the quota endpoint (defends against
146+ // detectRegion's graceful 'global' fallback when the network is unreachable).
125147 try {
126- const test = { ...config , apiKey : key } ;
127- await requestJson < QuotaResponse > ( test , { url : quotaEndpoint ( test . baseUrl ) } ) ;
128- process . stderr . write ( 'Valid\n' ) ;
148+ await requestJson < QuotaApiResponse > ( cfg , { url : quotaEndpoint ( cfg . baseUrl ) } ) ;
129149 } catch {
130150 throw new CLIError (
131151 'API key validation failed.' ,
@@ -134,10 +154,13 @@ async function loginWithApiKey(config: Config, key: string): Promise<void> {
134154 ) ;
135155 }
136156
157+ // OAuth and api_key are mutually exclusive — drop any stale oauth block.
137158 const existing = readConfigFile ( ) as Record < string , unknown > ;
159+ delete existing . oauth ;
138160 existing . api_key = key ;
161+ existing . region = detected ;
139162 await writeConfigFile ( existing ) ;
140163 process . stderr . write ( `API key saved to ${ getConfigPath ( ) } \n` ) ;
141164
142- await showDashboardAfterLogin ( { ... config , apiKey : key } ) ;
165+ await showDashboardAfterLogin ( cfg ) ;
143166}
0 commit comments