From 435758c7dfc9eef2b7cf0bd3068bc326dc70afef Mon Sep 17 00:00:00 2001 From: yuanhe Date: Sun, 17 May 2026 15:59:33 +0800 Subject: [PATCH] release: v1.0.14 - fix test isolation: support MMX_CONFIG_DIR env for config path override - remove free-tier music models (music-2.6-free, music-cover-free), default to music-2.6 / music-cover - improve auth login prompt: show region info for API key option --- package.json | 2 +- src/auth/setup.ts | 2 +- src/commands/music/cover.ts | 6 ++--- src/commands/music/generate.ts | 6 ++--- src/commands/music/models.ts | 18 +++------------ src/config/paths.ts | 1 + src/sdk/music/index.ts | 2 +- test/auth/credentials.test.ts | 10 ++++---- test/auth/resolver.test.ts | 17 +++++++++++++- test/commands/music/models.test.ts | 37 +++++++++++------------------- test/utils/model-defaults.test.ts | 2 +- 11 files changed, 49 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 13858aa..a21b614 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mmx-cli", - "version": "1.0.13", + "version": "1.0.14", "description": "CLI for the MiniMax AI Platform", "type": "module", "engines": { diff --git a/src/auth/setup.ts b/src/auth/setup.ts index 0d53bd4..380d080 100644 --- a/src/auth/setup.ts +++ b/src/auth/setup.ts @@ -17,7 +17,7 @@ interface AuthChoice { 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' }, + { value: 'api-key', label: `API key (${stripScheme(REGIONS.global)} or ${stripScheme(REGIONS.cn)})` }, ]; function stripScheme(url: string): string { diff --git a/src/commands/music/cover.ts b/src/commands/music/cover.ts index b7c8460..451d66d 100644 --- a/src/commands/music/cover.ts +++ b/src/commands/music/cover.ts @@ -15,11 +15,11 @@ import { musicCoverModel } from './models'; export default defineCommand({ name: 'music cover', - description: 'Generate a cover version of a song based on reference audio (music-cover / music-cover-free)', + description: 'Generate a cover version of a song based on reference audio (music-cover)', apiDocs: '/docs/api-reference/music-generation', usage: 'mmx music cover --prompt (--audio | --audio-file ) [--lyrics ] [--out ] [flags]', options: [ - { flag: '--model ', description: 'Model: music-cover (Token Plan), music-cover-free (Pay-as-you-go, default). Override only if needed.' }, + { flag: '--model ', description: 'Model: music-cover (default).' }, { flag: '--prompt ', description: 'Target cover style, e.g. "Indie folk, acoustic guitar, warm male vocal"' }, { flag: '--audio ', description: 'URL of the reference audio (mp3, wav, flac, etc. — 6s to 6min, max 50MB)' }, { flag: '--audio-file ', description: 'Local reference audio file (auto base64-encoded)' }, @@ -72,7 +72,7 @@ export default defineCommand({ const format = detectOutputFormat(config.output); const model = (flags.model as string) || musicCoverModel(config); - const VALID_MODELS = ['music-cover', 'music-cover-free']; + const VALID_MODELS = ['music-cover']; if (flags.model && !VALID_MODELS.includes(model)) { throw new CLIError( `Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`, diff --git a/src/commands/music/generate.ts b/src/commands/music/generate.ts index 79d0806..5d6c685 100644 --- a/src/commands/music/generate.ts +++ b/src/commands/music/generate.ts @@ -15,7 +15,7 @@ import { musicGenerateModel } from './models'; export default defineCommand({ name: 'music generate', - description: 'Generate a song (music-2.6 / music-2.6-free / music-2.5+ / music-2.5)', + description: 'Generate a song (music-2.6 / music-2.5+ / music-2.5)', apiDocs: '/docs/api-reference/music-generation', usage: 'mmx music generate --prompt (--lyrics | --instrumental | --lyrics-optimizer) [--out ] [flags]', options: [ @@ -36,7 +36,7 @@ export default defineCommand({ { flag: '--structure ', description: 'Song structure, e.g. "verse-chorus-verse-bridge-chorus"' }, { flag: '--references ', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran"' }, { flag: '--extra ', description: 'Additional fine-grained requirements not covered above' }, - { flag: '--model ', description: 'Model: music-2.6 (recommended), music-2.6-free (default, unlimited), music-2.5+, or music-2.5.' }, + { flag: '--model ', description: 'Model: music-2.6 (default), music-2.5+, or music-2.5.' }, { flag: '--output-format ', description: 'Return format: hex (default, saved to file) or url (24h expiry, download promptly). When --stream, only hex.' }, { flag: '--aigc-watermark', description: 'Embed AI-generated content watermark in audio for content provenance' }, { flag: '--format ', description: `Audio format: ${formatList(MUSIC_FORMATS)} (default: mp3)` }, @@ -127,7 +127,7 @@ export default defineCommand({ const outPath = (flags.out as string | undefined) ?? `music_${ts}.${ext}`; const model = (flags.model as string) || musicGenerateModel(config); - const VALID_MODELS = ['music-2.6', 'music-2.6-free', 'music-2.5+', 'music-2.5']; + const VALID_MODELS = ['music-2.6', 'music-2.5+', 'music-2.5']; if (flags.model && !VALID_MODELS.includes(model)) { throw new CLIError( `Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`, diff --git a/src/commands/music/models.ts b/src/commands/music/models.ts index 30351df..73b25b2 100644 --- a/src/commands/music/models.ts +++ b/src/commands/music/models.ts @@ -1,26 +1,14 @@ import type { Config } from '../../config/schema'; -/** - * sk-cp-xxx = Token Plan (coding plan) → standard models (music-2.6, music-cover) - * sk-api-xxx / other = Pay as you go → free-tier models (music-2.6-free, music-cover-free) - */ -export function isCodingPlan(config: Config): boolean { - const key = config.apiKey ?? config.fileApiKey ?? ''; - return key.startsWith('sk-cp-'); -} - export function musicGenerateModel(config: Config): string { - // Config default > key-type-based default - if (config.defaultMusicModel) return config.defaultMusicModel; - return isCodingPlan(config) ? 'music-2.6' : 'music-2.6-free'; + return config.defaultMusicModel ?? 'music-2.6'; } -const VALID_COVER_MODELS = new Set(['music-cover', 'music-cover-free']); +const VALID_COVER_MODELS = new Set(['music-cover']); export function musicCoverModel(config: Config): string { - // Config default (only if it's a valid cover model) > key-type-based default if (config.defaultMusicModel && VALID_COVER_MODELS.has(config.defaultMusicModel)) { return config.defaultMusicModel; } - return isCodingPlan(config) ? 'music-cover' : 'music-cover-free'; + return 'music-cover'; } diff --git a/src/config/paths.ts b/src/config/paths.ts index bd80a51..de3e6a1 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -4,6 +4,7 @@ import { join } from 'path'; const CONFIG_DIR_NAME = '.mmx'; export function getConfigDir(): string { + if (process.env.MMX_CONFIG_DIR) return process.env.MMX_CONFIG_DIR; return join(homedir(), CONFIG_DIR_NAME); } diff --git a/src/sdk/music/index.ts b/src/sdk/music/index.ts index 090b837..b2f55fe 100644 --- a/src/sdk/music/index.ts +++ b/src/sdk/music/index.ts @@ -180,7 +180,7 @@ export class MusicSDK extends Client { throw new SDKError('lyrics is required', ExitCode.USAGE); } - const VALID_MODELS = ['music-2.6', 'music-2.6-free', 'music-2.5+', 'music-2.5']; + const VALID_MODELS = ['music-2.6', 'music-2.5+', 'music-2.5']; if (model && !VALID_MODELS.includes(model)) { throw new SDKError( `Invalid model: ${model}. Valid models are ${VALID_MODELS.join(', ')}.`, diff --git a/test/auth/credentials.test.ts b/test/auth/credentials.test.ts index 2c3a706..4261358 100644 --- a/test/auth/credentials.test.ts +++ b/test/auth/credentials.test.ts @@ -7,15 +7,17 @@ import { tmpdir } from 'os'; describe('credentials', () => { const testDir = join(tmpdir(), `mmx-test-${Date.now()}`); - const originalHome = process.env.HOME; + const originalConfigDir = process.env.MMX_CONFIG_DIR; beforeEach(() => { - mkdirSync(join(testDir, '.mmx'), { recursive: true }); - process.env.HOME = testDir; + const configDir = join(testDir, '.mmx'); + mkdirSync(configDir, { recursive: true }); + process.env.MMX_CONFIG_DIR = configDir; }); afterEach(() => { - process.env.HOME = originalHome; + if (originalConfigDir) process.env.MMX_CONFIG_DIR = originalConfigDir; + else delete process.env.MMX_CONFIG_DIR; if (existsSync(testDir)) { rmSync(testDir, { recursive: true, force: true }); } diff --git a/test/auth/resolver.test.ts b/test/auth/resolver.test.ts index f967803..a24a304 100644 --- a/test/auth/resolver.test.ts +++ b/test/auth/resolver.test.ts @@ -1,6 +1,9 @@ -import { describe, it, expect, afterEach } from 'bun:test'; +import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; import { resolveCredential } from '../../src/auth/resolver'; import type { Config } from '../../src/config/schema'; +import { mkdirSync, rmSync } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; function makeConfig(overrides: Partial = {}): Config { return { @@ -20,8 +23,20 @@ function makeConfig(overrides: Partial = {}): Config { } describe('resolveCredential', () => { + const testDir = join(tmpdir(), `mmx-resolver-test-${Date.now()}`); + const originalConfigDir = process.env.MMX_CONFIG_DIR; + + beforeEach(() => { + const configDir = join(testDir, '.mmx'); + mkdirSync(configDir, { recursive: true }); + process.env.MMX_CONFIG_DIR = configDir; + }); + afterEach(() => { + if (originalConfigDir) process.env.MMX_CONFIG_DIR = originalConfigDir; + else delete process.env.MMX_CONFIG_DIR; delete process.env.MINIMAX_API_KEY; + rmSync(testDir, { recursive: true, force: true }); }); it('resolves from flag (apiKey in config)', async () => { diff --git a/test/commands/music/models.test.ts b/test/commands/music/models.test.ts index 235ab31..9273ad2 100644 --- a/test/commands/music/models.test.ts +++ b/test/commands/music/models.test.ts @@ -1,39 +1,28 @@ import { describe, it, expect } from 'bun:test'; -import { musicGenerateModel, musicCoverModel, isCodingPlan } from '../../../src/commands/music/models'; +import { musicGenerateModel, musicCoverModel } from '../../../src/commands/music/models'; import type { Config } from '../../../src/config/schema'; describe('music models', () => { - it('isCodingPlan returns true for sk-cp- key', () => { - expect(isCodingPlan({ apiKey: 'sk-cp-abc' } as Config)).toBe(true); - }); - - it('isCodingPlan returns false for sk-api- key', () => { - expect(isCodingPlan({ apiKey: 'sk-api-xyz' } as Config)).toBe(false); - }); - it('musicGenerateModel uses defaultMusicModel when set', () => { - const config = { apiKey: 'sk-api-xyz', defaultMusicModel: 'music-2.6' } as Config; - expect(musicGenerateModel(config)).toBe('music-2.6'); + const config = { defaultMusicModel: 'music-2.5+' } as Config; + expect(musicGenerateModel(config)).toBe('music-2.5+'); }); - it('musicGenerateModel falls back to key-type default when no defaultMusicModel', () => { - const cpConfig = { apiKey: 'sk-cp-abc' } as Config; - expect(musicGenerateModel(cpConfig)).toBe('music-2.6'); - - const apiConfig = { apiKey: 'sk-api-xyz' } as Config; - expect(musicGenerateModel(apiConfig)).toBe('music-2.6-free'); + it('musicGenerateModel defaults to music-2.6', () => { + expect(musicGenerateModel({} as Config)).toBe('music-2.6'); }); it('musicCoverModel ignores defaultMusicModel for non-cover models', () => { - const config = { apiKey: 'sk-api-xyz', defaultMusicModel: 'music-2.6' } as Config; - expect(musicCoverModel(config)).toBe('music-cover-free'); + const config = { defaultMusicModel: 'music-2.6' } as Config; + expect(musicCoverModel(config)).toBe('music-cover'); }); - it('musicCoverModel uses key-type default when no defaultMusicModel', () => { - const cpConfig = { apiKey: 'sk-cp-abc' } as Config; - expect(musicCoverModel(cpConfig)).toBe('music-cover'); + it('musicCoverModel uses defaultMusicModel when it is a cover model', () => { + const config = { defaultMusicModel: 'music-cover' } as Config; + expect(musicCoverModel(config)).toBe('music-cover'); + }); - const apiConfig = { apiKey: 'sk-api-xyz' } as Config; - expect(musicCoverModel(apiConfig)).toBe('music-cover-free'); + it('musicCoverModel defaults to music-cover', () => { + expect(musicCoverModel({} as Config)).toBe('music-cover'); }); }); diff --git a/test/utils/model-defaults.test.ts b/test/utils/model-defaults.test.ts index 6ed34e0..bb6a908 100644 --- a/test/utils/model-defaults.test.ts +++ b/test/utils/model-defaults.test.ts @@ -58,7 +58,7 @@ describe('model resolution (flag > config default > fallback)', () => { }); it('handles music model default', () => { - const model = resolveModel('defaultMusicModel', 'music-2.6-free', { ...baseConfig, defaultMusicModel: 'music-2.6' }, {}); + const model = resolveModel('defaultMusicModel', 'music-2.6', { ...baseConfig, defaultMusicModel: 'music-2.6' }, {}); expect(model).toBe('music-2.6'); }); });