Skip to content

Commit b9a3a23

Browse files
authored
Merge pull request #139 from MiniMax-AI/feat/oauth-device-flow
feat(auth): add OAuth device-code login (Global / CN) into config.json
2 parents 9b523aa + 33116b9 commit b9a3a23

18 files changed

Lines changed: 470 additions & 412 deletions

File tree

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ npm install -g mmx-cli
4545
## Quick Start
4646

4747
```bash
48-
# Authenticate
48+
# Authenticate (interactive — choose MiniMax OAuth or paste an API key)
49+
mmx auth login
50+
51+
# Or non-interactive
4952
mmx auth login --api-key sk-xxxxx
5053

5154
# Start creating
@@ -130,16 +133,21 @@ mmx search query --q "latest news" --output json
130133
### `mmx auth`
131134

132135
```bash
133-
mmx auth login --api-key sk-xxxxx
134-
mmx auth login # OAuth browser flow
136+
mmx auth login # interactive: pick OAuth (Global / China) or paste an API key
137+
mmx auth login --api-key sk-xxxxx # save an API key directly
138+
mmx auth login --recommend # skip the menu, pick OAuth region interactively
139+
mmx auth login --recommend --region=global # OAuth → api.minimax.io
140+
mmx auth login --recommend --region=cn # OAuth → api.minimaxi.com
135141
mmx auth status
136142
mmx auth refresh
137143
mmx auth logout
138144
```
139145

140146
`mmx auth status` is the canonical way to verify active authentication.
141-
`~/.mmx/credentials.json` exists only for OAuth login. API-key login persists to
142-
`~/.mmx/config.json` (and `--api-key` can also be passed per command).
147+
Both OAuth and API-key credentials live in `~/.mmx/config.json` (the two are
148+
mutually exclusive — logging in with one method clears the other). API keys
149+
can also be passed per command via `--api-key`. With an API key, the region
150+
is auto-detected by probing both Global and CN.
143151

144152
### `mmx config` · `mmx quota`
145153

README_CN.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ npm install -g mmx-cli
4545
## 快速开始
4646

4747
```bash
48-
# 认证
48+
# 认证(交互式 — 选 MiniMax OAuth 或粘 API Key)
49+
mmx auth login
50+
51+
# 或者非交互
4952
mmx auth login --api-key sk-xxxxx
5053

5154
# 开始创作
@@ -130,16 +133,20 @@ mmx search query --q "最新动态" --output json
130133
### `mmx auth`
131134

132135
```bash
133-
mmx auth login --api-key sk-xxxxx
134-
mmx auth login # OAuth 浏览器授权
136+
mmx auth login # 交互式:选 OAuth (Global / 中国) 或粘 API Key
137+
mmx auth login --api-key sk-xxxxx # 直接保存 API Key
138+
mmx auth login --recommend # 跳过 3 选 1 菜单,弹出 region 选择器
139+
mmx auth login --recommend --region=global # 直接 OAuth → api.minimax.io
140+
mmx auth login --recommend --region=cn # 直接 OAuth → api.minimaxi.com
135141
mmx auth status
136142
mmx auth refresh
137143
mmx auth logout
138144
```
139145

140-
请使用 `mmx auth status` 作为认证状态的权威检查方式。`~/.mmx/credentials.json`
141-
只在 OAuth 登录时存在;API Key 登录会写入 `~/.mmx/config.json`(也可每次通过
142-
`--api-key` 直接传入)。
146+
请使用 `mmx auth status` 作为认证状态的权威检查方式。OAuth 与 API Key 凭据
147+
都保存在 `~/.mmx/config.json` 里,**两者互斥** —— 用一种登录会清掉另一种。
148+
也可以每次通过 `--api-key` 直接传入。使用 API Key 登录时,会自动同时探测
149+
Global 与中国两个 region,选用能通过的那个。
143150

144151
### `mmx config` · `mmx quota`
145152

src/auth/credentials.ts

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,26 @@
1-
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, statSync } from 'fs';
2-
import { getCredentialsPath, ensureConfigDir } from '../config/paths';
1+
import { readConfigFile, writeConfigFile } from '../config/loader';
32
import type { CredentialFile } from './types';
43

5-
export async function loadCredentials(): Promise<CredentialFile | null> {
6-
const path = getCredentialsPath();
7-
if (!existsSync(path)) return null;
4+
/**
5+
* OAuth credentials live inside the user's main config file
6+
* (`~/.mmx/config.json`) under the `oauth` subobject. This keeps a
7+
* single source of truth for all CLI state.
8+
*/
89

9-
try {
10-
checkPermissions(path);
11-
const raw = readFileSync(path, 'utf-8');
12-
const data = JSON.parse(raw) as CredentialFile;
13-
if (!data.access_token || !data.refresh_token) return null;
14-
return data;
15-
} catch (err) {
16-
const e = err as Error;
17-
if (e instanceof SyntaxError || e.message.includes('JSON')) {
18-
process.stderr.write(`Warning: credentials file is corrupted. Run 'mmx auth logout' to reset.\n`);
19-
}
20-
return null;
21-
}
10+
export async function loadCredentials(): Promise<CredentialFile | null> {
11+
const cfg = readConfigFile();
12+
return cfg.oauth ?? null;
2213
}
2314

2415
export async function saveCredentials(creds: CredentialFile): Promise<void> {
25-
await ensureConfigDir();
26-
const path = getCredentialsPath();
27-
const tmp = path + '.tmp';
28-
writeFileSync(tmp, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
29-
renameSync(tmp, path);
16+
const existing = readConfigFile() as Record<string, unknown>;
17+
existing.oauth = creds;
18+
await writeConfigFile(existing);
3019
}
3120

3221
export async function clearCredentials(): Promise<void> {
33-
const path = getCredentialsPath();
34-
if (existsSync(path)) {
35-
unlinkSync(path);
36-
}
37-
}
38-
39-
function checkPermissions(path: string): void {
40-
try {
41-
const stat = statSync(path);
42-
const mode = stat.mode & 0o777;
43-
if (mode !== 0o600) {
44-
process.stderr.write(
45-
`Warning: ${path} has permissions ${mode.toString(8)}, expected 600.\n`,
46-
);
47-
}
48-
} catch {
49-
// Ignore permission check errors on platforms that don't support it
50-
}
22+
const existing = readConfigFile() as Record<string, unknown>;
23+
if (!('oauth' in existing)) return;
24+
delete existing.oauth;
25+
await writeConfigFile(existing);
5126
}

0 commit comments

Comments
 (0)