Skip to content

Commit 9ad4f56

Browse files
authored
Merge pull request #189 from kartikkabadi/fix/document-mmx-config-dir
fix(auth): document MMX_CONFIG_DIR in credential errors
2 parents ad37c11 + 2f271f1 commit 9ad4f56

6 files changed

Lines changed: 89 additions & 5 deletions

File tree

ERRORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This document lists all error scenarios and the messages users will see.
1515
| OAuth error in callback | `OAuth error: ${error}` |
1616
| OAuth token exchange failed | `OAuth token exchange failed: ${body}` |
1717
| `MINIMAX_API_KEY` already set (non-interactive) | `Warning: MINIMAX_API_KEY is already set in environment.` |
18+
| No credentials found (non-interactive) | `No credentials found.` + the searched `config.json` path and hint: `Log in: mmx auth login`, `--api-key sk-xxxxx`, or `MMX_CONFIG_DIR=/path/to/.mmx` |
1819

1920
### `mmx auth logout`
2021

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ Useful for CI/CD (`mmx auth login --api-key sk-xxxxx`), or pass per-command via
158158
OAuth and API key are mutually exclusive — logging in with one clears the other.
159159
Credential priority: `--api-key` flag > OAuth (config) > `api_key` (config).
160160

161+
### Environment variables
162+
163+
| Variable | Description |
164+
|---|---|
165+
| `MINIMAX_REGION` | `global` or `cn`. |
166+
| `MINIMAX_BASE_URL` | Override the API base URL. |
167+
| `MINIMAX_OUTPUT` | `text` or `json`. |
168+
| `MINIMAX_TIMEOUT` | Request timeout in seconds. |
169+
| `MINIMAX_VERBOSE` | `1` to enable verbose HTTP logging. |
170+
| `MMX_CONFIG_DIR` | Directory containing the `config.json` file (default: `~/.mmx`). Set this when `mmx` runs from a subprocess, service, or CI job whose home directory differs from where you logged in. |
171+
161172
### `mmx config` · `mmx quota`
162173

163174
```bash

src/auth/hints.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getConfigPath } from '../config/paths';
2+
3+
const NO_CREDENTIALS_REMEDIATION = [
4+
'Log in: mmx auth login',
5+
'Pass per-call: --api-key sk-xxxxx',
6+
'Or set env var: MMX_CONFIG_DIR=/path/to/.mmx',
7+
].join('\n');
8+
9+
export function buildNoCredentialsHint(configPath?: string): string {
10+
return `Looked for credentials in: ${configPath ?? getConfigPath()}\n${NO_CREDENTIALS_REMEDIATION}`;
11+
}

src/auth/resolver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { loadCredentials } from './credentials';
44
import { ensureFreshToken } from './refresh';
55
import { CLIError } from '../errors/base';
66
import { ExitCode } from '../errors/codes';
7+
import { buildNoCredentialsHint } from './hints';
78

89
export async function resolveCredential(config: Config): Promise<ResolvedCredential> {
910
// 1. --api-key flag
@@ -26,6 +27,6 @@ export async function resolveCredential(config: Config): Promise<ResolvedCredent
2627
throw new CLIError(
2728
'No credentials found.',
2829
ExitCode.AUTH,
29-
'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx',
30+
buildNoCredentialsHint(config.configPath),
3031
);
3132
}

src/auth/setup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CLIError } from '../errors/base';
88
import { ExitCode } from '../errors/codes';
99
import { deviceCodeLogin } from './oauth';
1010
import { loadCredentials } from './credentials';
11+
import { buildNoCredentialsHint } from './hints';
1112

1213
interface AuthChoice {
1314
value: 'oauth-global' | 'oauth-cn' | 'api-key';
@@ -46,7 +47,7 @@ export async function ensureAuth(config: Config): Promise<void> {
4647
throw new CLIError(
4748
'No credentials found.',
4849
ExitCode.AUTH,
49-
'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx',
50+
buildNoCredentialsHint(config.configPath),
5051
);
5152
}
5253

test/auth/resolver.test.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
22
import { resolveCredential } from '../../src/auth/resolver';
3+
import { ensureAuth } from '../../src/auth/setup';
4+
import { CLIError } from '../../src/errors/base';
35
import type { Config } from '../../src/config/schema';
46
import { mkdirSync, rmSync } from 'fs';
57
import { join } from 'path';
@@ -24,18 +26,23 @@ function makeConfig(overrides: Partial<Config> = {}): Config {
2426

2527
describe('resolveCredential', () => {
2628
const testDir = join(tmpdir(), `mmx-resolver-test-${Date.now()}`);
27-
const originalConfigDir = process.env.MMX_CONFIG_DIR;
29+
let originalConfigDir: string | undefined;
30+
let originalApiKey: string | undefined;
2831

2932
beforeEach(() => {
33+
originalConfigDir = process.env.MMX_CONFIG_DIR;
34+
originalApiKey = process.env.MINIMAX_API_KEY;
3035
const configDir = join(testDir, '.mmx');
3136
mkdirSync(configDir, { recursive: true });
3237
process.env.MMX_CONFIG_DIR = configDir;
38+
delete process.env.MINIMAX_API_KEY;
3339
});
3440

3541
afterEach(() => {
36-
if (originalConfigDir) process.env.MMX_CONFIG_DIR = originalConfigDir;
42+
if (originalConfigDir !== undefined) process.env.MMX_CONFIG_DIR = originalConfigDir;
3743
else delete process.env.MMX_CONFIG_DIR;
38-
delete process.env.MINIMAX_API_KEY;
44+
if (originalApiKey !== undefined) process.env.MINIMAX_API_KEY = originalApiKey;
45+
else delete process.env.MINIMAX_API_KEY;
3946
rmSync(testDir, { recursive: true, force: true });
4047
});
4148

@@ -59,9 +66,61 @@ describe('resolveCredential', () => {
5966
await expect(resolveCredential(config)).rejects.toThrow('No credentials found');
6067
});
6168

69+
it('no-credentials hint mentions MMX_CONFIG_DIR and working remediation options', async () => {
70+
const config = makeConfig();
71+
try {
72+
await resolveCredential(config);
73+
throw new Error('expected resolveCredential to throw');
74+
} catch (err) {
75+
expect(err).toBeInstanceOf(CLIError);
76+
const hint = (err as CLIError).hint ?? '';
77+
expect(hint).toContain('mmx auth login');
78+
expect(hint).toContain('--api-key');
79+
expect(hint).toContain('MMX_CONFIG_DIR');
80+
expect(hint).toContain(join(testDir, '.mmx', 'config.json'));
81+
}
82+
});
83+
6284
it('prefers flag over file api key', async () => {
6385
const config = makeConfig({ apiKey: 'sk-flag', fileApiKey: 'sk-file' });
6486
const cred = await resolveCredential(config);
6587
expect(cred.token).toBe('sk-flag');
6688
});
6789
});
90+
91+
describe('ensureAuth (non-interactive, no credentials)', () => {
92+
const testDir = join(tmpdir(), `mmx-setup-test-${Date.now()}`);
93+
let originalConfigDir: string | undefined;
94+
let originalApiKey: string | undefined;
95+
96+
beforeEach(() => {
97+
originalConfigDir = process.env.MMX_CONFIG_DIR;
98+
originalApiKey = process.env.MINIMAX_API_KEY;
99+
mkdirSync(join(testDir, '.mmx'), { recursive: true });
100+
process.env.MMX_CONFIG_DIR = join(testDir, '.mmx');
101+
delete process.env.MINIMAX_API_KEY;
102+
});
103+
104+
afterEach(() => {
105+
if (originalConfigDir !== undefined) process.env.MMX_CONFIG_DIR = originalConfigDir;
106+
else delete process.env.MMX_CONFIG_DIR;
107+
if (originalApiKey !== undefined) process.env.MINIMAX_API_KEY = originalApiKey;
108+
else delete process.env.MINIMAX_API_KEY;
109+
rmSync(testDir, { recursive: true, force: true });
110+
});
111+
112+
it('no-credentials hint mentions MMX_CONFIG_DIR and working remediation options', async () => {
113+
const config = makeConfig({ nonInteractive: true });
114+
try {
115+
await ensureAuth(config);
116+
throw new Error('expected ensureAuth to throw');
117+
} catch (err) {
118+
expect(err).toBeInstanceOf(CLIError);
119+
const hint = (err as CLIError).hint ?? '';
120+
expect(hint).toContain('mmx auth login');
121+
expect(hint).toContain('--api-key');
122+
expect(hint).toContain('MMX_CONFIG_DIR');
123+
expect(hint).toContain(join(testDir, '.mmx', 'config.json'));
124+
}
125+
});
126+
});

0 commit comments

Comments
 (0)