-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathsetup.ts
More file actions
111 lines (98 loc) · 4.06 KB
/
Copy pathsetup.ts
File metadata and controls
111 lines (98 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import type { Config, Region } from '../config/schema';
import { REGIONS } from '../config/schema';
import { readConfigFile, writeConfigFile } from '../config/loader';
import { promptText, promptConfirm } from '../utils/prompt';
import { isInteractive } from '../utils/env';
import { maskToken } from '../utils/token';
import { CLIError } from '../errors/base';
import { ExitCode } from '../errors/codes';
import { deviceCodeLogin } from './oauth';
import { loadCredentials } from './credentials';
interface AuthChoice {
value: 'oauth-global' | 'oauth-cn' | 'api-key';
label: string;
}
const AUTH_CHOICES: AuthChoice[] = [
{ value: 'oauth-global', label: `MiniMax (OAuth login → ${stripScheme(REGIONS.global)})` },
{ value: 'oauth-cn', label: `MiniMax (OAuth login → ${stripScheme(REGIONS.cn)})` },
{ value: 'api-key', label: `API key (${stripScheme(REGIONS.global)} or ${stripScheme(REGIONS.cn)})` },
];
function stripScheme(url: string): string {
return url.replace(/^https?:\/\//, '');
}
export async function ensureAuth(config: Config): Promise<void> {
if (config.apiKey || config.fileApiKey) return;
if (await loadCredentials()) return;
const envKey = process.env.MINIMAX_API_KEY;
if (envKey) {
if (!isInteractive({ nonInteractive: config.nonInteractive })) {
return; // env key is enough; no prompt
}
const save = await promptConfirm({
message: `Found MINIMAX_API_KEY in environment (${maskToken(envKey)}). Save to config file?`,
});
if (save) {
await persistApiKey(config, envKey);
}
return;
}
if (!isInteractive({ nonInteractive: config.nonInteractive })) {
throw new CLIError(
'No credentials found.',
ExitCode.AUTH,
'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx',
);
}
const choice = await pickAuthMethod();
if (choice === 'api-key') {
const input = await promptText({ message: 'Enter your MiniMax API key:' });
if (!input) throw new CLIError('API key is required.', ExitCode.AUTH);
await persistApiKey(config, input);
return;
}
await runOAuthLogin(choice === 'oauth-cn' ? 'cn' : 'global');
}
export async function pickAuthMethod(): Promise<AuthChoice['value']> {
const { select, isCancel } = await import('@clack/prompts');
const value = await select({
message: 'How would you like to authenticate?',
options: AUTH_CHOICES.map(c => ({ value: c.value, label: c.label })),
});
if (isCancel(value)) throw new CLIError('Authentication cancelled.', ExitCode.AUTH);
return value as AuthChoice['value'];
}
/**
* Region-only picker used by `mmx auth login --recommend` (no API-key option).
*/
export async function pickOAuthRegion(): Promise<Region> {
const { select, isCancel } = await import('@clack/prompts');
const value = await select({
message: 'Select an OAuth region:',
options: [
{ value: 'global', label: `Global → ${stripScheme(REGIONS.global)}` },
{ value: 'cn', label: `China → ${stripScheme(REGIONS.cn)}` },
],
});
if (isCancel(value)) throw new CLIError('Authentication cancelled.', ExitCode.AUTH);
return value as Region;
}
export async function runOAuthLogin(region: Region): Promise<void> {
const creds = await deviceCodeLogin(region);
// OAuth and api_key are mutually exclusive — drop any stale api_key
// so `mmx auth status` and the resolver see a single source of truth.
const existing = readConfigFile() as Record<string, unknown>;
delete existing.api_key;
existing.oauth = creds;
existing.region = region;
await writeConfigFile(existing);
process.stderr.write('Logged in successfully.\n');
process.stderr.write('Credentials saved to ~/.mmx/config.json\n');
}
async function persistApiKey(config: Config, key: string): Promise<void> {
const data = { ...(readConfigFile() as Record<string, unknown>), api_key: key };
await writeConfigFile(data);
config.fileApiKey = key;
process.stderr.write(`API key saved to ${config.configPath ?? '~/.mmx/config.json'}\n`);
}
// Legacy alias kept so main.ts keeps working without churn.
export const ensureApiKey = ensureAuth;