Skip to content

Commit 213ca64

Browse files
fix(auth): document MMX_CONFIG_DIR in credential errors
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5f13ef5 commit 213ca64

5 files changed

Lines changed: 65 additions & 2 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.` + 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/resolver.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export async function resolveCredential(config: Config): Promise<ResolvedCredent
2626
throw new CLIError(
2727
'No credentials found.',
2828
ExitCode.AUTH,
29-
'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx',
29+
'Log in: mmx auth login\n' +
30+
'Pass per-call: --api-key sk-xxxxx\n' +
31+
'Or set env var: MMX_CONFIG_DIR=/path/to/.mmx',
3032
);
3133
}

‎src/auth/setup.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export async function ensureAuth(config: Config): Promise<void> {
4646
throw new CLIError(
4747
'No credentials found.',
4848
ExitCode.AUTH,
49-
'Log in: mmx auth login\nPass directly: --api-key sk-xxxxx',
49+
'Log in: mmx auth login\n' +
50+
'Pass per-call: --api-key sk-xxxxx\n' +
51+
'Or set env var: MMX_CONFIG_DIR=/path/to/.mmx',
5052
);
5153
}
5254

‎test/auth/resolver.test.ts‎

Lines changed: 47 additions & 0 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';
@@ -59,9 +61,54 @@ describe('resolveCredential', () => {
5961
await expect(resolveCredential(config)).rejects.toThrow('No credentials found');
6062
});
6163

64+
it('no-credentials hint mentions MMX_CONFIG_DIR and working remediation options', async () => {
65+
const config = makeConfig();
66+
try {
67+
await resolveCredential(config);
68+
throw new Error('expected resolveCredential to throw');
69+
} catch (err) {
70+
expect(err).toBeInstanceOf(CLIError);
71+
const hint = (err as CLIError).hint ?? '';
72+
expect(hint).toContain('mmx auth login');
73+
expect(hint).toContain('--api-key');
74+
expect(hint).toContain('MMX_CONFIG_DIR');
75+
}
76+
});
77+
6278
it('prefers flag over file api key', async () => {
6379
const config = makeConfig({ apiKey: 'sk-flag', fileApiKey: 'sk-file' });
6480
const cred = await resolveCredential(config);
6581
expect(cred.token).toBe('sk-flag');
6682
});
6783
});
84+
85+
describe('ensureAuth (non-interactive, no credentials)', () => {
86+
const testDir = join(tmpdir(), `mmx-setup-test-${Date.now()}`);
87+
const originalConfigDir = process.env.MMX_CONFIG_DIR;
88+
89+
beforeEach(() => {
90+
mkdirSync(join(testDir, '.mmx'), { recursive: true });
91+
process.env.MMX_CONFIG_DIR = join(testDir, '.mmx');
92+
});
93+
94+
afterEach(() => {
95+
if (originalConfigDir) process.env.MMX_CONFIG_DIR = originalConfigDir;
96+
else delete process.env.MMX_CONFIG_DIR;
97+
delete process.env.MINIMAX_API_KEY;
98+
rmSync(testDir, { recursive: true, force: true });
99+
});
100+
101+
it('no-credentials hint mentions MMX_CONFIG_DIR and working remediation options', async () => {
102+
const config = makeConfig({ nonInteractive: true });
103+
try {
104+
await ensureAuth(config);
105+
throw new Error('expected ensureAuth to throw');
106+
} catch (err) {
107+
expect(err).toBeInstanceOf(CLIError);
108+
const hint = (err as CLIError).hint ?? '';
109+
expect(hint).toContain('mmx auth login');
110+
expect(hint).toContain('--api-key');
111+
expect(hint).toContain('MMX_CONFIG_DIR');
112+
}
113+
});
114+
});

0 commit comments

Comments
 (0)