Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions src/commands/music/cover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <text> (--audio <url> | --audio-file <path>) [--lyrics <text>] [--out <path>] [flags]',
options: [
{ flag: '--model <model>', description: 'Model: music-cover (Token Plan), music-cover-free (Pay-as-you-go, default). Override only if needed.' },
{ flag: '--model <model>', description: 'Model: music-cover (default).' },
{ flag: '--prompt <text>', description: 'Target cover style, e.g. "Indie folk, acoustic guitar, warm male vocal"' },
{ flag: '--audio <url>', description: 'URL of the reference audio (mp3, wav, flac, etc. — 6s to 6min, max 50MB)' },
{ flag: '--audio-file <path>', description: 'Local reference audio file (auto base64-encoded)' },
Expand Down Expand Up @@ -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(', ')}`,
Expand Down
6 changes: 3 additions & 3 deletions src/commands/music/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <text> (--lyrics <text> | --instrumental | --lyrics-optimizer) [--out <path>] [flags]',
options: [
Expand All @@ -36,7 +36,7 @@ export default defineCommand({
{ flag: '--structure <text>', description: 'Song structure, e.g. "verse-chorus-verse-bridge-chorus"' },
{ flag: '--references <text>', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran"' },
{ flag: '--extra <text>', description: 'Additional fine-grained requirements not covered above' },
{ flag: '--model <model>', description: 'Model: music-2.6 (recommended), music-2.6-free (default, unlimited), music-2.5+, or music-2.5.' },
{ flag: '--model <model>', description: 'Model: music-2.6 (default), music-2.5+, or music-2.5.' },
{ flag: '--output-format <fmt>', 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 <fmt>', description: `Audio format: ${formatList(MUSIC_FORMATS)} (default: mp3)` },
Expand Down Expand Up @@ -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(', ')}`,
Expand Down
18 changes: 3 additions & 15 deletions src/commands/music/models.ts
Original file line number Diff line number Diff line change
@@ -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';
}
1 change: 1 addition & 0 deletions src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/sdk/music/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(', ')}.`,
Expand Down
10 changes: 6 additions & 4 deletions test/auth/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
Expand Down
17 changes: 16 additions & 1 deletion test/auth/resolver.test.ts
Original file line number Diff line number Diff line change
@@ -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> = {}): Config {
return {
Expand All @@ -20,8 +23,20 @@ function makeConfig(overrides: Partial<Config> = {}): 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 () => {
Expand Down
37 changes: 13 additions & 24 deletions test/commands/music/models.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
2 changes: 1 addition & 1 deletion test/utils/model-defaults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Loading