@hasna/connectors 1.3.19 → 1.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +8 -1
- package/bin/mcp.js +8 -1
- package/bin/serve.js +8 -1
- package/connectors/connect-minimax/CLAUDE.md +47 -0
- package/connectors/connect-minimax/package.json +41 -0
- package/connectors/connect-minimax/src/api/client.ts +123 -0
- package/connectors/connect-minimax/src/api/image.ts +65 -0
- package/connectors/connect-minimax/src/api/index.ts +53 -0
- package/connectors/connect-minimax/src/api/music.ts +78 -0
- package/connectors/connect-minimax/src/api/sound-effects.ts +59 -0
- package/connectors/connect-minimax/src/api/tts.ts +56 -0
- package/connectors/connect-minimax/src/api/video.ts +78 -0
- package/connectors/connect-minimax/src/cli/index.ts +246 -0
- package/connectors/connect-minimax/src/index.ts +19 -0
- package/connectors/connect-minimax/src/types/index.ts +182 -0
- package/connectors/connect-minimax/src/utils/config.ts +125 -0
- package/connectors/connect-minimax/src/utils/output.ts +40 -0
- package/connectors/connect-minimax/tsconfig.json +8 -0
- package/dist/index.js +8 -1
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1909,7 +1909,7 @@ var package_default;
|
|
|
1909
1909
|
var init_package = __esm(() => {
|
|
1910
1910
|
package_default = {
|
|
1911
1911
|
name: "@hasna/connectors",
|
|
1912
|
-
version: "1.3.
|
|
1912
|
+
version: "1.3.20",
|
|
1913
1913
|
description: "Open source connector library - Install API connectors with a single command",
|
|
1914
1914
|
type: "module",
|
|
1915
1915
|
bin: {
|
|
@@ -20262,6 +20262,13 @@ var init_ai_ml = __esm(() => {
|
|
|
20262
20262
|
category: "AI & ML",
|
|
20263
20263
|
tags: ["ai", "llm", "grok"]
|
|
20264
20264
|
},
|
|
20265
|
+
{
|
|
20266
|
+
name: "minimax",
|
|
20267
|
+
displayName: "Minimax",
|
|
20268
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
20269
|
+
category: "AI & ML",
|
|
20270
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
20271
|
+
},
|
|
20265
20272
|
{
|
|
20266
20273
|
name: "mistral",
|
|
20267
20274
|
displayName: "Mistral",
|
package/bin/mcp.js
CHANGED
|
@@ -11443,7 +11443,7 @@ var package_default;
|
|
|
11443
11443
|
var init_package = __esm(() => {
|
|
11444
11444
|
package_default = {
|
|
11445
11445
|
name: "@hasna/connectors",
|
|
11446
|
-
version: "1.3.
|
|
11446
|
+
version: "1.3.20",
|
|
11447
11447
|
description: "Open source connector library - Install API connectors with a single command",
|
|
11448
11448
|
type: "module",
|
|
11449
11449
|
bin: {
|
|
@@ -33493,6 +33493,13 @@ var connectors = [
|
|
|
33493
33493
|
category: "AI & ML",
|
|
33494
33494
|
tags: ["ai", "llm", "grok"]
|
|
33495
33495
|
},
|
|
33496
|
+
{
|
|
33497
|
+
name: "minimax",
|
|
33498
|
+
displayName: "Minimax",
|
|
33499
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
33500
|
+
category: "AI & ML",
|
|
33501
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
33502
|
+
},
|
|
33496
33503
|
{
|
|
33497
33504
|
name: "mistral",
|
|
33498
33505
|
displayName: "Mistral",
|
package/bin/serve.js
CHANGED
|
@@ -15160,7 +15160,7 @@ var githubConnector = defineConnector({
|
|
|
15160
15160
|
// package.json
|
|
15161
15161
|
var package_default = {
|
|
15162
15162
|
name: "@hasna/connectors",
|
|
15163
|
-
version: "1.3.
|
|
15163
|
+
version: "1.3.20",
|
|
15164
15164
|
description: "Open source connector library - Install API connectors with a single command",
|
|
15165
15165
|
type: "module",
|
|
15166
15166
|
bin: {
|
|
@@ -17221,6 +17221,13 @@ var connectors = [
|
|
|
17221
17221
|
category: "AI & ML",
|
|
17222
17222
|
tags: ["ai", "llm", "grok"]
|
|
17223
17223
|
},
|
|
17224
|
+
{
|
|
17225
|
+
name: "minimax",
|
|
17226
|
+
displayName: "Minimax",
|
|
17227
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
17228
|
+
category: "AI & ML",
|
|
17229
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
17230
|
+
},
|
|
17224
17231
|
{
|
|
17225
17232
|
name: "mistral",
|
|
17226
17233
|
displayName: "Mistral",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# connect-minimax
|
|
2
|
+
|
|
3
|
+
Minimax API connector for video, music, image, TTS, and sound effects generation.
|
|
4
|
+
|
|
5
|
+
## API Modules
|
|
6
|
+
|
|
7
|
+
- `VideoApi` — Text-to-video (T2V-01) and image-to-video (I2V-01) generation with async polling
|
|
8
|
+
- `MusicApi` — AI music generation (music-01) with lyrics, genre, mood, tempo control
|
|
9
|
+
- `TTSApi` — Text-to-speech (speech-02-hd) with voice selection, speed, emotion
|
|
10
|
+
- `ImageApi` — Image generation (image-01) with aspect ratio control
|
|
11
|
+
- `SoundEffectsApi` — Sound effect generation from text prompts
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Minimax } from '@hasna/connect-minimax';
|
|
17
|
+
|
|
18
|
+
const client = Minimax.fromEnv(); // uses MINIMAX_API_KEY
|
|
19
|
+
|
|
20
|
+
// Generate video
|
|
21
|
+
const video = await client.video.generateAndWait('A cat playing piano');
|
|
22
|
+
|
|
23
|
+
// Generate music
|
|
24
|
+
const music = await client.music.generateAndWait('Upbeat jazz track');
|
|
25
|
+
|
|
26
|
+
// Generate speech
|
|
27
|
+
const audioBuffer = await client.tts.generateToBuffer('Hello world');
|
|
28
|
+
|
|
29
|
+
// Generate image
|
|
30
|
+
const image = await client.image.generateAndWait('A sunset over mountains');
|
|
31
|
+
|
|
32
|
+
// Generate sound effect
|
|
33
|
+
const sfx = await client.soundEffects.generateAndWait('Thunder rolling');
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Environment Variables
|
|
37
|
+
|
|
38
|
+
- `MINIMAX_API_KEY` — API key (required)
|
|
39
|
+
- `MINIMAX_GROUP_ID` — Group ID (optional, some endpoints)
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bun run dev # Run CLI
|
|
45
|
+
bun run build # Build dist + bin
|
|
46
|
+
bun run typecheck # Type check
|
|
47
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/connect-minimax",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimax API connector - Video, music, image, TTS, and sound effects generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"connect-minimax": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli/index.ts --outdir ./bin --target bun",
|
|
19
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.3.0",
|
|
24
|
+
"commander": "^12.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/bun": "latest",
|
|
28
|
+
"typescript": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"dist",
|
|
37
|
+
"bin",
|
|
38
|
+
"tsconfig.json"
|
|
39
|
+
],
|
|
40
|
+
"license": "Apache-2.0"
|
|
41
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { MinimaxConfig, OutputFormat } from '../types';
|
|
2
|
+
import { MinimaxApiError } from '../types';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://api.minimax.chat/v1';
|
|
5
|
+
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
8
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
9
|
+
body?: Record<string, unknown> | unknown[] | string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
format?: OutputFormat;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class MinimaxClient {
|
|
15
|
+
private readonly apiKey: string;
|
|
16
|
+
private readonly baseUrl: string;
|
|
17
|
+
private readonly groupId?: string;
|
|
18
|
+
|
|
19
|
+
constructor(config: MinimaxConfig) {
|
|
20
|
+
if (!config.apiKey) {
|
|
21
|
+
throw new Error('API key is required');
|
|
22
|
+
}
|
|
23
|
+
this.apiKey = config.apiKey;
|
|
24
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
25
|
+
this.groupId = config.groupId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
|
|
29
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
30
|
+
|
|
31
|
+
if (params) {
|
|
32
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
33
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
34
|
+
url.searchParams.append(key, String(value));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return url.toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
43
|
+
const { method = 'GET', params, body, headers = {} } = options;
|
|
44
|
+
|
|
45
|
+
const url = this.buildUrl(path, params);
|
|
46
|
+
|
|
47
|
+
const requestHeaders: Record<string, string> = {
|
|
48
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
49
|
+
'Accept': 'application/json',
|
|
50
|
+
...headers,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (this.groupId) {
|
|
54
|
+
requestHeaders['X-Group-Id'] = this.groupId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (body && ['POST', 'PUT'].includes(method)) {
|
|
58
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fetchOptions: RequestInit = {
|
|
62
|
+
method,
|
|
63
|
+
headers: requestHeaders,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (body && ['POST', 'PUT'].includes(method)) {
|
|
67
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const response = await fetch(url, fetchOptions);
|
|
71
|
+
|
|
72
|
+
if (response.status === 204) {
|
|
73
|
+
return {} as T;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let data: unknown;
|
|
77
|
+
const contentType = response.headers.get('content-type') || '';
|
|
78
|
+
|
|
79
|
+
if (contentType.includes('application/json')) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
if (text) {
|
|
82
|
+
try {
|
|
83
|
+
data = JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
data = text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
data = await response.text();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const errorData = data as { base_resp?: { status_code: number; status_msg: string } } | undefined;
|
|
94
|
+
const errorMessage = errorData?.base_resp?.status_msg || response.statusText;
|
|
95
|
+
throw new MinimaxApiError(errorMessage, response.status, errorData?.base_resp);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data as T;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
102
|
+
return this.request<T>(path, { method: 'GET', params });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async post<T>(path: string, body?: Record<string, unknown> | unknown[] | string | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
106
|
+
return this.request<T>(path, { method: 'POST', body: body as Record<string, unknown>, params });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async downloadFile(url: string): Promise<Buffer> {
|
|
110
|
+
const response = await fetch(url);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new MinimaxApiError(`Download failed: ${response.statusText}`, response.status);
|
|
113
|
+
}
|
|
114
|
+
return Buffer.from(await response.arrayBuffer());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getApiKeyPreview(): string {
|
|
118
|
+
if (this.apiKey.length > 10) {
|
|
119
|
+
return `${this.apiKey.substring(0, 6)}...${this.apiKey.substring(this.apiKey.length - 4)}`;
|
|
120
|
+
}
|
|
121
|
+
return '***';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
ImageModel,
|
|
4
|
+
ImageGenerateRequest,
|
|
5
|
+
ImageGenerateResponse,
|
|
6
|
+
ImageStatusResponse,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export interface ImageOptions {
|
|
10
|
+
model?: ImageModel;
|
|
11
|
+
aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
|
|
12
|
+
n?: number;
|
|
13
|
+
promptOptimizer?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ImageApi {
|
|
17
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
18
|
+
|
|
19
|
+
async generate(prompt: string, options: ImageOptions = {}): Promise<ImageGenerateResponse> {
|
|
20
|
+
const request: ImageGenerateRequest = {
|
|
21
|
+
model: options.model || 'image-01',
|
|
22
|
+
prompt,
|
|
23
|
+
aspect_ratio: options.aspectRatio || '1:1',
|
|
24
|
+
n: options.n || 1,
|
|
25
|
+
prompt_optimizer: options.promptOptimizer ?? true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return this.client.post<ImageGenerateResponse>('/image_generation', request);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getStatus(taskId: string): Promise<ImageStatusResponse> {
|
|
32
|
+
return this.client.get<ImageStatusResponse>('/query/image_generation', { task_id: taskId });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async download(fileId: string): Promise<Buffer> {
|
|
36
|
+
const fileResponse = await this.client.get<{ file: { download_url: string } }>('/files/retrieve', { file_id: fileId });
|
|
37
|
+
return this.client.downloadFile(fileResponse.file.download_url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async generateAndWait(
|
|
41
|
+
prompt: string,
|
|
42
|
+
options: ImageOptions = {},
|
|
43
|
+
pollIntervalMs = 3000,
|
|
44
|
+
maxAttempts = 60
|
|
45
|
+
): Promise<{ fileId: string; downloadUrl: string }> {
|
|
46
|
+
const job = await this.generate(prompt, options);
|
|
47
|
+
const taskId = job.task_id;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
51
|
+
const status = await this.getStatus(taskId);
|
|
52
|
+
|
|
53
|
+
if (status.status === 'Success' && status.file_id) {
|
|
54
|
+
const fileResponse = await this.client.get<{ file: { download_url: string } }>('/files/retrieve', { file_id: status.file_id });
|
|
55
|
+
return { fileId: status.file_id, downloadUrl: fileResponse.file.download_url };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (status.status === 'Fail') {
|
|
59
|
+
throw new Error(`Image generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error('Image generation timed out');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MinimaxConfig } from '../types';
|
|
2
|
+
import { MinimaxClient } from './client';
|
|
3
|
+
import { VideoApi } from './video';
|
|
4
|
+
import { MusicApi } from './music';
|
|
5
|
+
import { TTSApi } from './tts';
|
|
6
|
+
import { ImageApi } from './image';
|
|
7
|
+
import { SoundEffectsApi } from './sound-effects';
|
|
8
|
+
|
|
9
|
+
export class Minimax {
|
|
10
|
+
private readonly client: MinimaxClient;
|
|
11
|
+
|
|
12
|
+
public readonly video: VideoApi;
|
|
13
|
+
public readonly music: MusicApi;
|
|
14
|
+
public readonly tts: TTSApi;
|
|
15
|
+
public readonly image: ImageApi;
|
|
16
|
+
public readonly soundEffects: SoundEffectsApi;
|
|
17
|
+
|
|
18
|
+
constructor(config: MinimaxConfig) {
|
|
19
|
+
this.client = new MinimaxClient(config);
|
|
20
|
+
this.video = new VideoApi(this.client);
|
|
21
|
+
this.music = new MusicApi(this.client);
|
|
22
|
+
this.tts = new TTSApi(this.client);
|
|
23
|
+
this.image = new ImageApi(this.client);
|
|
24
|
+
this.soundEffects = new SoundEffectsApi(this.client);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static fromEnv(): Minimax {
|
|
28
|
+
const apiKey = process.env.MINIMAX_API_KEY;
|
|
29
|
+
const groupId = process.env.MINIMAX_GROUP_ID;
|
|
30
|
+
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error('MINIMAX_API_KEY environment variable is required');
|
|
33
|
+
}
|
|
34
|
+
return new Minimax({ apiKey, groupId });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getApiKeyPreview(): string {
|
|
38
|
+
return this.client.getApiKeyPreview();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getClient(): MinimaxClient {
|
|
42
|
+
return this.client;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Connector = Minimax;
|
|
47
|
+
|
|
48
|
+
export { MinimaxClient } from './client';
|
|
49
|
+
export { VideoApi } from './video';
|
|
50
|
+
export { MusicApi } from './music';
|
|
51
|
+
export { TTSApi } from './tts';
|
|
52
|
+
export { ImageApi } from './image';
|
|
53
|
+
export { SoundEffectsApi } from './sound-effects';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
MusicModel,
|
|
4
|
+
MusicGenerateRequest,
|
|
5
|
+
MusicGenerateResponse,
|
|
6
|
+
MusicStatusResponse,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export interface MusicOptions {
|
|
10
|
+
model?: MusicModel;
|
|
11
|
+
lyrics?: string;
|
|
12
|
+
referVoice?: string;
|
|
13
|
+
referInstrumental?: string;
|
|
14
|
+
genre?: string;
|
|
15
|
+
mood?: string;
|
|
16
|
+
tempo?: number;
|
|
17
|
+
duration?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MusicApi {
|
|
21
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
22
|
+
|
|
23
|
+
async generate(prompt: string, options: MusicOptions = {}): Promise<MusicGenerateResponse> {
|
|
24
|
+
const request: MusicGenerateRequest = {
|
|
25
|
+
model: options.model || 'music-01',
|
|
26
|
+
prompt,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (options.lyrics) request.lyrics = options.lyrics;
|
|
30
|
+
if (options.referVoice) request.refer_voice = options.referVoice;
|
|
31
|
+
if (options.referInstrumental) request.refer_instrumental = options.referInstrumental;
|
|
32
|
+
if (options.genre) request.genre = options.genre;
|
|
33
|
+
if (options.mood) request.mood = options.mood;
|
|
34
|
+
if (options.tempo) request.tempo = options.tempo;
|
|
35
|
+
if (options.duration) request.duration = options.duration;
|
|
36
|
+
|
|
37
|
+
return this.client.post<MusicGenerateResponse>('/music_generation', request);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getStatus(taskId: string): Promise<MusicStatusResponse> {
|
|
41
|
+
return this.client.get<MusicStatusResponse>('/query/music_generation', { task_id: taskId });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async download(audioUrl: string): Promise<Buffer> {
|
|
45
|
+
return this.client.downloadFile(audioUrl);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async generateAndWait(
|
|
49
|
+
prompt: string,
|
|
50
|
+
options: MusicOptions = {},
|
|
51
|
+
pollIntervalMs = 5000,
|
|
52
|
+
maxAttempts = 120
|
|
53
|
+
): Promise<{ audioUrl: string; lyrics?: string; instrumentalUrl?: string }> {
|
|
54
|
+
const job = await this.generate(prompt, options);
|
|
55
|
+
const taskId = job.task_id;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
59
|
+
const status = await this.getStatus(taskId);
|
|
60
|
+
|
|
61
|
+
if (status.status === 'Success') {
|
|
62
|
+
const audioUrl = status.extra_info?.audio_url || status.audio_file;
|
|
63
|
+
if (!audioUrl) throw new Error('No audio URL in completed response');
|
|
64
|
+
return {
|
|
65
|
+
audioUrl,
|
|
66
|
+
lyrics: status.extra_info?.lyrics,
|
|
67
|
+
instrumentalUrl: status.extra_info?.instrumental_url,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status.status === 'Fail') {
|
|
72
|
+
throw new Error(`Music generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('Music generation timed out');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
SoundEffectRequest,
|
|
4
|
+
SoundEffectResponse,
|
|
5
|
+
SoundEffectStatusResponse,
|
|
6
|
+
} from '../types';
|
|
7
|
+
|
|
8
|
+
export interface SoundEffectOptions {
|
|
9
|
+
duration?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SoundEffectsApi {
|
|
13
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
14
|
+
|
|
15
|
+
async generate(prompt: string, options: SoundEffectOptions = {}): Promise<SoundEffectResponse> {
|
|
16
|
+
const request: SoundEffectRequest = {
|
|
17
|
+
model: 'sound-effects-01',
|
|
18
|
+
prompt,
|
|
19
|
+
duration: options.duration,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return this.client.post<SoundEffectResponse>('/sound_generation', request);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getStatus(taskId: string): Promise<SoundEffectStatusResponse> {
|
|
26
|
+
return this.client.get<SoundEffectStatusResponse>('/query/sound_generation', { task_id: taskId });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async download(audioUrl: string): Promise<Buffer> {
|
|
30
|
+
return this.client.downloadFile(audioUrl);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async generateAndWait(
|
|
34
|
+
prompt: string,
|
|
35
|
+
options: SoundEffectOptions = {},
|
|
36
|
+
pollIntervalMs = 3000,
|
|
37
|
+
maxAttempts = 60
|
|
38
|
+
): Promise<{ audioUrl: string }> {
|
|
39
|
+
const job = await this.generate(prompt, options);
|
|
40
|
+
const taskId = job.task_id;
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
43
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
44
|
+
const status = await this.getStatus(taskId);
|
|
45
|
+
|
|
46
|
+
if (status.status === 'Success') {
|
|
47
|
+
const audioUrl = status.extra_info?.audio_url || status.audio_file;
|
|
48
|
+
if (!audioUrl) throw new Error('No audio URL in completed response');
|
|
49
|
+
return { audioUrl };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (status.status === 'Fail') {
|
|
53
|
+
throw new Error(`Sound effect generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new Error('Sound effect generation timed out');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type { TTSModel, TTSRequest, TTSResponse } from '../types';
|
|
3
|
+
|
|
4
|
+
export interface TTSOptions {
|
|
5
|
+
model?: TTSModel;
|
|
6
|
+
voiceId?: string;
|
|
7
|
+
speed?: number;
|
|
8
|
+
volume?: number;
|
|
9
|
+
pitch?: number;
|
|
10
|
+
emotion?: string;
|
|
11
|
+
format?: 'mp3' | 'wav' | 'pcm' | 'flac';
|
|
12
|
+
sampleRate?: number;
|
|
13
|
+
languageBoost?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class TTSApi {
|
|
17
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
18
|
+
|
|
19
|
+
async generate(text: string, options: TTSOptions = {}): Promise<TTSResponse> {
|
|
20
|
+
const request: TTSRequest = {
|
|
21
|
+
model: options.model || 'speech-02-hd',
|
|
22
|
+
text,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (options.voiceId || options.speed || options.volume || options.pitch || options.emotion) {
|
|
26
|
+
request.voice_setting = {};
|
|
27
|
+
if (options.voiceId) request.voice_setting.voice_id = options.voiceId;
|
|
28
|
+
if (options.speed) request.voice_setting.speed = options.speed;
|
|
29
|
+
if (options.volume) request.voice_setting.vol = options.volume;
|
|
30
|
+
if (options.pitch) request.voice_setting.pitch = options.pitch;
|
|
31
|
+
if (options.emotion) request.voice_setting.emotion = options.emotion;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options.format || options.sampleRate) {
|
|
35
|
+
request.audio_setting = {};
|
|
36
|
+
if (options.format) request.audio_setting.format = options.format;
|
|
37
|
+
if (options.sampleRate) request.audio_setting.sample_rate = options.sampleRate;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options.languageBoost) {
|
|
41
|
+
request.language_boost = options.languageBoost;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this.client.post<TTSResponse>('/t2a_v2', request);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async generateToBuffer(text: string, options: TTSOptions = {}): Promise<Buffer> {
|
|
48
|
+
const response = await this.generate(text, options);
|
|
49
|
+
|
|
50
|
+
if (!response.data?.audio) {
|
|
51
|
+
throw new Error('No audio data in TTS response');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Buffer.from(response.data.audio, 'hex');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
VideoModel,
|
|
4
|
+
VideoGenerateRequest,
|
|
5
|
+
VideoGenerateResponse,
|
|
6
|
+
VideoStatusResponse,
|
|
7
|
+
VideoFileResponse,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
export interface VideoOptions {
|
|
11
|
+
model?: VideoModel;
|
|
12
|
+
firstFrameImage?: string;
|
|
13
|
+
subjectReference?: string[];
|
|
14
|
+
promptOptimizer?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class VideoApi {
|
|
18
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
19
|
+
|
|
20
|
+
async generate(prompt: string, options: VideoOptions = {}): Promise<VideoGenerateResponse> {
|
|
21
|
+
const request: VideoGenerateRequest = {
|
|
22
|
+
model: options.model || 'T2V-01',
|
|
23
|
+
prompt,
|
|
24
|
+
prompt_optimizer: options.promptOptimizer ?? true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (options.firstFrameImage) {
|
|
28
|
+
request.first_frame_image = options.firstFrameImage;
|
|
29
|
+
request.model = options.model || 'I2V-01';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (options.subjectReference) {
|
|
33
|
+
request.subject_reference = options.subjectReference;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this.client.post<VideoGenerateResponse>('/video_generation', request);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getStatus(taskId: string): Promise<VideoStatusResponse> {
|
|
40
|
+
return this.client.get<VideoStatusResponse>('/query/video_generation', { task_id: taskId });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getFileUrl(fileId: string): Promise<string> {
|
|
44
|
+
const response = await this.client.get<VideoFileResponse>('/files/retrieve', { file_id: fileId });
|
|
45
|
+
return response.file.download_url;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async download(fileId: string): Promise<Buffer> {
|
|
49
|
+
const url = await this.getFileUrl(fileId);
|
|
50
|
+
return this.client.downloadFile(url);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async generateAndWait(
|
|
54
|
+
prompt: string,
|
|
55
|
+
options: VideoOptions = {},
|
|
56
|
+
pollIntervalMs = 10000,
|
|
57
|
+
maxAttempts = 120
|
|
58
|
+
): Promise<{ fileId: string; downloadUrl: string }> {
|
|
59
|
+
const job = await this.generate(prompt, options);
|
|
60
|
+
const taskId = job.task_id;
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
63
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
64
|
+
const status = await this.getStatus(taskId);
|
|
65
|
+
|
|
66
|
+
if (status.status === 'Success' && status.file_id) {
|
|
67
|
+
const url = await this.getFileUrl(status.file_id);
|
|
68
|
+
return { fileId: status.file_id, downloadUrl: url };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status.status === 'Fail') {
|
|
72
|
+
throw new Error(`Video generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('Video generation timed out');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { writeFile } from 'fs/promises';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { Minimax } from '../api';
|
|
6
|
+
import {
|
|
7
|
+
getApiKey,
|
|
8
|
+
setApiKey,
|
|
9
|
+
getGroupId,
|
|
10
|
+
setGroupId,
|
|
11
|
+
clearConfig,
|
|
12
|
+
getConfigDir,
|
|
13
|
+
setProfileOverride,
|
|
14
|
+
getCurrentProfile,
|
|
15
|
+
setCurrentProfile,
|
|
16
|
+
listProfiles,
|
|
17
|
+
createProfile,
|
|
18
|
+
deleteProfile,
|
|
19
|
+
profileExists,
|
|
20
|
+
} from '../utils/config';
|
|
21
|
+
import { success, error, info, print } from '../utils/output';
|
|
22
|
+
import type { OutputFormat } from '../utils/output';
|
|
23
|
+
|
|
24
|
+
const CONNECTOR_NAME = 'connect-minimax';
|
|
25
|
+
const VERSION = '0.1.0';
|
|
26
|
+
|
|
27
|
+
const program = new Command();
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.name(CONNECTOR_NAME)
|
|
31
|
+
.description('Minimax API connector - Video, music, image, TTS, and sound effects')
|
|
32
|
+
.version(VERSION)
|
|
33
|
+
.option('-k, --api-key <key>', 'API key (overrides config)')
|
|
34
|
+
.option('-f, --format <format>', 'Output format (json, pretty)', 'pretty')
|
|
35
|
+
.option('-p, --profile <profile>', 'Use a specific profile')
|
|
36
|
+
.hook('preAction', (thisCommand) => {
|
|
37
|
+
const opts = thisCommand.opts();
|
|
38
|
+
if (opts.profile) {
|
|
39
|
+
if (!profileExists(opts.profile)) {
|
|
40
|
+
error(`Profile "${opts.profile}" does not exist.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
setProfileOverride(opts.profile);
|
|
44
|
+
}
|
|
45
|
+
if (opts.apiKey) process.env.MINIMAX_API_KEY = opts.apiKey;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function getFormat(cmd: Command): OutputFormat {
|
|
49
|
+
return (cmd.parent?.opts().format || 'pretty') as OutputFormat;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getClient(): Minimax {
|
|
53
|
+
const apiKey = getApiKey();
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
error(`No API key configured. Run "${CONNECTOR_NAME} config set-key <key>" or set MINIMAX_API_KEY.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const groupId = getGroupId();
|
|
59
|
+
return new Minimax({ apiKey, groupId });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Config
|
|
63
|
+
const configCmd = program.command('config').description('Manage configuration');
|
|
64
|
+
|
|
65
|
+
configCmd.command('set-key <key>').description('Set API key').action((key) => {
|
|
66
|
+
setApiKey(key);
|
|
67
|
+
success('API key saved');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
configCmd.command('set-group <id>').description('Set group ID').action((id) => {
|
|
71
|
+
setGroupId(id);
|
|
72
|
+
success('Group ID saved');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
configCmd.command('show').description('Show current config').action(() => {
|
|
76
|
+
const key = getApiKey();
|
|
77
|
+
const group = getGroupId();
|
|
78
|
+
info(`Profile: ${getCurrentProfile()}`);
|
|
79
|
+
info(`API Key: ${key ? key.substring(0, 6) + '...' : 'not set'}`);
|
|
80
|
+
info(`Group ID: ${group || 'not set'}`);
|
|
81
|
+
info(`Config dir: ${getConfigDir()}`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
configCmd.command('clear').description('Clear config').action(() => {
|
|
85
|
+
clearConfig();
|
|
86
|
+
success('Config cleared');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Profile
|
|
90
|
+
const profileCmd = program.command('profile').description('Manage profiles');
|
|
91
|
+
profileCmd.command('list').description('List profiles').action(() => {
|
|
92
|
+
const profiles = listProfiles();
|
|
93
|
+
const current = getCurrentProfile();
|
|
94
|
+
if (profiles.length === 0) { info('No profiles'); return; }
|
|
95
|
+
profiles.forEach(p => console.log(p === current ? `* ${p}` : ` ${p}`));
|
|
96
|
+
});
|
|
97
|
+
profileCmd.command('create <name>').description('Create profile').action((name) => {
|
|
98
|
+
createProfile(name) ? success(`Profile "${name}" created`) : error(`Profile "${name}" already exists`);
|
|
99
|
+
});
|
|
100
|
+
profileCmd.command('use <name>').description('Switch profile').action((name) => {
|
|
101
|
+
setCurrentProfile(name);
|
|
102
|
+
success(`Switched to profile "${name}"`);
|
|
103
|
+
});
|
|
104
|
+
profileCmd.command('delete <name>').description('Delete profile').action((name) => {
|
|
105
|
+
deleteProfile(name) ? success(`Profile "${name}" deleted`) : error(`Cannot delete "${name}"`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Video
|
|
109
|
+
const videoCmd = program.command('video').description('Video generation');
|
|
110
|
+
videoCmd
|
|
111
|
+
.command('generate <prompt>')
|
|
112
|
+
.description('Generate a video from a text prompt')
|
|
113
|
+
.option('-m, --model <model>', 'Model (T2V-01, I2V-01)', 'T2V-01')
|
|
114
|
+
.option('-o, --output <path>', 'Save video to file')
|
|
115
|
+
.option('--image <url>', 'First frame image (switches to I2V)')
|
|
116
|
+
.option('--no-optimize', 'Disable prompt optimizer')
|
|
117
|
+
.action(async (prompt, opts, cmd) => {
|
|
118
|
+
const client = getClient();
|
|
119
|
+
info('Starting video generation...');
|
|
120
|
+
try {
|
|
121
|
+
const result = await client.video.generateAndWait(prompt, {
|
|
122
|
+
model: opts.model,
|
|
123
|
+
firstFrameImage: opts.image,
|
|
124
|
+
promptOptimizer: opts.optimize !== false,
|
|
125
|
+
});
|
|
126
|
+
if (opts.output) {
|
|
127
|
+
const buffer = await client.video.download(result.fileId);
|
|
128
|
+
await writeFile(resolve(opts.output), buffer);
|
|
129
|
+
success(`Video saved to: ${opts.output}`);
|
|
130
|
+
} else {
|
|
131
|
+
print(result, getFormat(cmd));
|
|
132
|
+
}
|
|
133
|
+
} catch (e: any) { error(e.message); process.exit(1); }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Music
|
|
137
|
+
const musicCmd = program.command('music').description('Music generation');
|
|
138
|
+
musicCmd
|
|
139
|
+
.command('generate <prompt>')
|
|
140
|
+
.description('Generate music from a prompt')
|
|
141
|
+
.option('-o, --output <path>', 'Save audio to file')
|
|
142
|
+
.option('--lyrics <text>', 'Lyrics for the song')
|
|
143
|
+
.option('--genre <genre>', 'Music genre')
|
|
144
|
+
.option('--mood <mood>', 'Desired mood')
|
|
145
|
+
.option('--tempo <bpm>', 'Tempo in BPM', parseInt)
|
|
146
|
+
.option('--duration <seconds>', 'Duration in seconds', parseInt)
|
|
147
|
+
.action(async (prompt, opts, cmd) => {
|
|
148
|
+
const client = getClient();
|
|
149
|
+
info('Starting music generation...');
|
|
150
|
+
try {
|
|
151
|
+
const result = await client.music.generateAndWait(prompt, {
|
|
152
|
+
lyrics: opts.lyrics,
|
|
153
|
+
genre: opts.genre,
|
|
154
|
+
mood: opts.mood,
|
|
155
|
+
tempo: opts.tempo,
|
|
156
|
+
duration: opts.duration,
|
|
157
|
+
});
|
|
158
|
+
if (opts.output) {
|
|
159
|
+
const buffer = await client.music.download(result.audioUrl);
|
|
160
|
+
await writeFile(resolve(opts.output), buffer);
|
|
161
|
+
success(`Music saved to: ${opts.output}`);
|
|
162
|
+
} else {
|
|
163
|
+
print(result, getFormat(cmd));
|
|
164
|
+
}
|
|
165
|
+
} catch (e: any) { error(e.message); process.exit(1); }
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// TTS
|
|
169
|
+
const ttsCmd = program.command('tts').description('Text-to-speech');
|
|
170
|
+
ttsCmd
|
|
171
|
+
.command('generate <text>')
|
|
172
|
+
.description('Generate speech from text')
|
|
173
|
+
.option('-o, --output <path>', 'Save audio to file (required)')
|
|
174
|
+
.option('-m, --model <model>', 'Model', 'speech-02-hd')
|
|
175
|
+
.option('--voice <id>', 'Voice ID')
|
|
176
|
+
.option('--speed <n>', 'Speed (0.5-2.0)', parseFloat)
|
|
177
|
+
.option('--format <fmt>', 'Audio format (mp3, wav, flac)', 'mp3')
|
|
178
|
+
.option('--language <code>', 'Language boost code')
|
|
179
|
+
.action(async (text, opts, cmd) => {
|
|
180
|
+
const client = getClient();
|
|
181
|
+
if (!opts.output) { error('--output is required'); process.exit(1); }
|
|
182
|
+
info('Generating speech...');
|
|
183
|
+
try {
|
|
184
|
+
const buffer = await client.tts.generateToBuffer(text, {
|
|
185
|
+
model: opts.model,
|
|
186
|
+
voiceId: opts.voice,
|
|
187
|
+
speed: opts.speed,
|
|
188
|
+
format: opts.format,
|
|
189
|
+
languageBoost: opts.language,
|
|
190
|
+
});
|
|
191
|
+
await writeFile(resolve(opts.output), buffer);
|
|
192
|
+
success(`Audio saved to: ${opts.output}`);
|
|
193
|
+
} catch (e: any) { error(e.message); process.exit(1); }
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Image
|
|
197
|
+
const imageCmd = program.command('image').description('Image generation');
|
|
198
|
+
imageCmd
|
|
199
|
+
.command('generate <prompt>')
|
|
200
|
+
.description('Generate an image from a prompt')
|
|
201
|
+
.option('-o, --output <path>', 'Save image to file')
|
|
202
|
+
.option('--aspect <ratio>', 'Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)', '1:1')
|
|
203
|
+
.option('-n, --count <n>', 'Number of images', parseInt, 1)
|
|
204
|
+
.action(async (prompt, opts, cmd) => {
|
|
205
|
+
const client = getClient();
|
|
206
|
+
info('Starting image generation...');
|
|
207
|
+
try {
|
|
208
|
+
const result = await client.image.generateAndWait(prompt, {
|
|
209
|
+
aspectRatio: opts.aspect,
|
|
210
|
+
n: opts.count,
|
|
211
|
+
});
|
|
212
|
+
if (opts.output) {
|
|
213
|
+
const buffer = await client.image.download(result.fileId);
|
|
214
|
+
await writeFile(resolve(opts.output), buffer);
|
|
215
|
+
success(`Image saved to: ${opts.output}`);
|
|
216
|
+
} else {
|
|
217
|
+
print(result, getFormat(cmd));
|
|
218
|
+
}
|
|
219
|
+
} catch (e: any) { error(e.message); process.exit(1); }
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Sound Effects
|
|
223
|
+
const sfxCmd = program.command('sfx').description('Sound effects generation');
|
|
224
|
+
sfxCmd
|
|
225
|
+
.command('generate <prompt>')
|
|
226
|
+
.description('Generate a sound effect from a prompt')
|
|
227
|
+
.option('-o, --output <path>', 'Save audio to file')
|
|
228
|
+
.option('--duration <seconds>', 'Duration in seconds', parseInt)
|
|
229
|
+
.action(async (prompt, opts, cmd) => {
|
|
230
|
+
const client = getClient();
|
|
231
|
+
info('Generating sound effect...');
|
|
232
|
+
try {
|
|
233
|
+
const result = await client.soundEffects.generateAndWait(prompt, {
|
|
234
|
+
duration: opts.duration,
|
|
235
|
+
});
|
|
236
|
+
if (opts.output) {
|
|
237
|
+
const buffer = await client.soundEffects.download(result.audioUrl);
|
|
238
|
+
await writeFile(resolve(opts.output), buffer);
|
|
239
|
+
success(`Sound effect saved to: ${opts.output}`);
|
|
240
|
+
} else {
|
|
241
|
+
print(result, getFormat(cmd));
|
|
242
|
+
}
|
|
243
|
+
} catch (e: any) { error(e.message); process.exit(1); }
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
program.parse();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { Minimax, Connector } from './api';
|
|
2
|
+
export * from './types';
|
|
3
|
+
|
|
4
|
+
export { MinimaxClient, VideoApi, MusicApi, TTSApi, ImageApi, SoundEffectsApi } from './api';
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
getApiKey,
|
|
8
|
+
setApiKey,
|
|
9
|
+
getGroupId,
|
|
10
|
+
setGroupId,
|
|
11
|
+
getCurrentProfile,
|
|
12
|
+
setCurrentProfile,
|
|
13
|
+
listProfiles,
|
|
14
|
+
createProfile,
|
|
15
|
+
deleteProfile,
|
|
16
|
+
loadProfile,
|
|
17
|
+
saveProfile,
|
|
18
|
+
clearConfig,
|
|
19
|
+
} from './utils/config';
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export interface MinimaxConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
groupId?: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Models
|
|
8
|
+
export type VideoModel = 'T2V-01' | 'T2V-01-Director' | 'I2V-01' | 'I2V-01-Director' | 'S2V-01';
|
|
9
|
+
export type MusicModel = 'music-01';
|
|
10
|
+
export type TTSModel = 'speech-02' | 'speech-02-hd' | 'speech-02-turbo';
|
|
11
|
+
export type ImageModel = 'image-01';
|
|
12
|
+
|
|
13
|
+
// Video Generation
|
|
14
|
+
export interface VideoGenerateRequest {
|
|
15
|
+
model: VideoModel;
|
|
16
|
+
prompt?: string;
|
|
17
|
+
first_frame_image?: string;
|
|
18
|
+
subject_reference?: string[];
|
|
19
|
+
prompt_optimizer?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VideoGenerateResponse {
|
|
23
|
+
task_id: string;
|
|
24
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface VideoStatusResponse {
|
|
28
|
+
task_id: string;
|
|
29
|
+
status: 'Queueing' | 'Processing' | 'Success' | 'Fail';
|
|
30
|
+
file_id?: string;
|
|
31
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface VideoFileResponse {
|
|
35
|
+
file: {
|
|
36
|
+
file_id: string;
|
|
37
|
+
bytes: number;
|
|
38
|
+
created_at: number;
|
|
39
|
+
filename: string;
|
|
40
|
+
purpose: string;
|
|
41
|
+
download_url: string;
|
|
42
|
+
};
|
|
43
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Music Generation
|
|
47
|
+
export interface MusicGenerateRequest {
|
|
48
|
+
model: MusicModel;
|
|
49
|
+
lyrics?: string;
|
|
50
|
+
refer_voice?: string;
|
|
51
|
+
refer_instrumental?: string;
|
|
52
|
+
prompt?: string;
|
|
53
|
+
genre?: string;
|
|
54
|
+
mood?: string;
|
|
55
|
+
tempo?: number;
|
|
56
|
+
duration?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface MusicGenerateResponse {
|
|
60
|
+
task_id: string;
|
|
61
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MusicStatusResponse {
|
|
65
|
+
task_id: string;
|
|
66
|
+
status: 'Queueing' | 'Processing' | 'Success' | 'Fail';
|
|
67
|
+
audio_file?: string;
|
|
68
|
+
extra_info?: {
|
|
69
|
+
audio_url?: string;
|
|
70
|
+
lyrics?: string;
|
|
71
|
+
instrumental_url?: string;
|
|
72
|
+
};
|
|
73
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// TTS (Text to Audio)
|
|
77
|
+
export interface TTSRequest {
|
|
78
|
+
model: TTSModel;
|
|
79
|
+
text: string;
|
|
80
|
+
voice_setting?: {
|
|
81
|
+
voice_id?: string;
|
|
82
|
+
speed?: number;
|
|
83
|
+
vol?: number;
|
|
84
|
+
pitch?: number;
|
|
85
|
+
emotion?: string;
|
|
86
|
+
};
|
|
87
|
+
audio_setting?: {
|
|
88
|
+
sample_rate?: number;
|
|
89
|
+
bitrate?: number;
|
|
90
|
+
format?: 'mp3' | 'wav' | 'pcm' | 'flac';
|
|
91
|
+
channel?: number;
|
|
92
|
+
};
|
|
93
|
+
language_boost?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TTSResponse {
|
|
97
|
+
data?: {
|
|
98
|
+
audio?: string;
|
|
99
|
+
};
|
|
100
|
+
extra_info?: {
|
|
101
|
+
audio_length?: number;
|
|
102
|
+
audio_sample_rate?: number;
|
|
103
|
+
audio_size?: number;
|
|
104
|
+
bitrate?: number;
|
|
105
|
+
word_count?: number;
|
|
106
|
+
invisible_character_ratio?: number;
|
|
107
|
+
};
|
|
108
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Image Generation
|
|
112
|
+
export interface ImageGenerateRequest {
|
|
113
|
+
model: ImageModel;
|
|
114
|
+
prompt: string;
|
|
115
|
+
aspect_ratio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
|
|
116
|
+
n?: number;
|
|
117
|
+
prompt_optimizer?: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ImageGenerateResponse {
|
|
121
|
+
task_id: string;
|
|
122
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ImageStatusResponse {
|
|
126
|
+
task_id: string;
|
|
127
|
+
status: 'Queueing' | 'Processing' | 'Success' | 'Fail';
|
|
128
|
+
file_id?: string;
|
|
129
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Sound Effects
|
|
133
|
+
export interface SoundEffectRequest {
|
|
134
|
+
model: string;
|
|
135
|
+
prompt: string;
|
|
136
|
+
duration?: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface SoundEffectResponse {
|
|
140
|
+
task_id: string;
|
|
141
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface SoundEffectStatusResponse {
|
|
145
|
+
task_id: string;
|
|
146
|
+
status: 'Queueing' | 'Processing' | 'Success' | 'Fail';
|
|
147
|
+
audio_file?: string;
|
|
148
|
+
extra_info?: {
|
|
149
|
+
audio_url?: string;
|
|
150
|
+
};
|
|
151
|
+
base_resp?: { status_code: number; status_msg: string };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Voice Clone
|
|
155
|
+
export interface VoiceCloneRequest {
|
|
156
|
+
file: Buffer;
|
|
157
|
+
voice_id?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface VoiceListResponse {
|
|
161
|
+
voices: Array<{
|
|
162
|
+
voice_id: string;
|
|
163
|
+
name: string;
|
|
164
|
+
language?: string;
|
|
165
|
+
description?: string;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Shared
|
|
170
|
+
export type OutputFormat = 'json' | 'pretty';
|
|
171
|
+
|
|
172
|
+
export class MinimaxApiError extends Error {
|
|
173
|
+
public readonly statusCode: number;
|
|
174
|
+
public readonly error?: { status_code: number; status_msg: string };
|
|
175
|
+
|
|
176
|
+
constructor(message: string, statusCode: number, error?: { status_code: number; status_msg: string }) {
|
|
177
|
+
super(message);
|
|
178
|
+
this.name = 'MinimaxApiError';
|
|
179
|
+
this.statusCode = statusCode;
|
|
180
|
+
this.error = error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
const CONNECTOR_NAME = 'connect-minimax';
|
|
6
|
+
const DEFAULT_PROFILE = 'default';
|
|
7
|
+
|
|
8
|
+
export interface ProfileConfig {
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
groupId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let profileOverride: string | undefined;
|
|
14
|
+
|
|
15
|
+
const CONFIG_DIR = join(homedir(), '.hasna', 'connectors', CONNECTOR_NAME);
|
|
16
|
+
const PROFILES_DIR = join(CONFIG_DIR, 'profiles');
|
|
17
|
+
const CURRENT_PROFILE_FILE = join(CONFIG_DIR, 'current_profile');
|
|
18
|
+
|
|
19
|
+
export function setProfileOverride(profile: string | undefined): void {
|
|
20
|
+
profileOverride = profile;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ensureConfigDir(): void {
|
|
24
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
if (!existsSync(PROFILES_DIR)) mkdirSync(PROFILES_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getProfilePath(profile: string): string {
|
|
29
|
+
return join(PROFILES_DIR, `${profile}.json`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getCurrentProfile(): string {
|
|
33
|
+
if (profileOverride) return profileOverride;
|
|
34
|
+
ensureConfigDir();
|
|
35
|
+
if (existsSync(CURRENT_PROFILE_FILE)) {
|
|
36
|
+
try {
|
|
37
|
+
const profile = readFileSync(CURRENT_PROFILE_FILE, 'utf-8').trim();
|
|
38
|
+
if (profile && profileExists(profile)) return profile;
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_PROFILE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setCurrentProfile(profile: string): void {
|
|
45
|
+
ensureConfigDir();
|
|
46
|
+
if (!profileExists(profile) && profile !== DEFAULT_PROFILE) {
|
|
47
|
+
throw new Error(`Profile "${profile}" does not exist`);
|
|
48
|
+
}
|
|
49
|
+
writeFileSync(CURRENT_PROFILE_FILE, profile);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function profileExists(profile: string): boolean {
|
|
53
|
+
return existsSync(getProfilePath(profile));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function listProfiles(): string[] {
|
|
57
|
+
ensureConfigDir();
|
|
58
|
+
if (!existsSync(PROFILES_DIR)) return [];
|
|
59
|
+
return readdirSync(PROFILES_DIR)
|
|
60
|
+
.filter(f => f.endsWith('.json'))
|
|
61
|
+
.map(f => f.replace('.json', ''))
|
|
62
|
+
.sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createProfile(profile: string, config: ProfileConfig = {}): boolean {
|
|
66
|
+
ensureConfigDir();
|
|
67
|
+
if (profileExists(profile)) return false;
|
|
68
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
|
|
69
|
+
throw new Error('Profile name can only contain letters, numbers, hyphens, and underscores');
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(getProfilePath(profile), JSON.stringify(config, null, 2));
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function deleteProfile(profile: string): boolean {
|
|
76
|
+
if (profile === DEFAULT_PROFILE) return false;
|
|
77
|
+
if (!profileExists(profile)) return false;
|
|
78
|
+
if (getCurrentProfile() === profile) setCurrentProfile(DEFAULT_PROFILE);
|
|
79
|
+
rmSync(getProfilePath(profile));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function loadProfile(profile?: string): ProfileConfig {
|
|
84
|
+
ensureConfigDir();
|
|
85
|
+
const profilePath = getProfilePath(profile || getCurrentProfile());
|
|
86
|
+
if (!existsSync(profilePath)) return {};
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(readFileSync(profilePath, 'utf-8'));
|
|
89
|
+
} catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function saveProfile(config: ProfileConfig, profile?: string): void {
|
|
95
|
+
ensureConfigDir();
|
|
96
|
+
writeFileSync(getProfilePath(profile || getCurrentProfile()), JSON.stringify(config, null, 2));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getApiKey(): string | undefined {
|
|
100
|
+
return process.env.MINIMAX_API_KEY || loadProfile().apiKey;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function setApiKey(apiKey: string): void {
|
|
104
|
+
const config = loadProfile();
|
|
105
|
+
config.apiKey = apiKey;
|
|
106
|
+
saveProfile(config);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getGroupId(): string | undefined {
|
|
110
|
+
return process.env.MINIMAX_GROUP_ID || loadProfile().groupId;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function setGroupId(groupId: string): void {
|
|
114
|
+
const config = loadProfile();
|
|
115
|
+
config.groupId = groupId;
|
|
116
|
+
saveProfile(config);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function clearConfig(): void {
|
|
120
|
+
saveProfile({});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getConfigDir(): string {
|
|
124
|
+
return CONFIG_DIR;
|
|
125
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export type OutputFormat = 'json' | 'pretty';
|
|
4
|
+
|
|
5
|
+
export function formatOutput(data: unknown, format: OutputFormat = 'pretty'): string {
|
|
6
|
+
if (format === 'json') return JSON.stringify(data, null, 2);
|
|
7
|
+
return formatPretty(data);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatPretty(data: unknown, indent = 0): string {
|
|
11
|
+
if (data === null || data === undefined) return chalk.gray('null');
|
|
12
|
+
if (typeof data !== 'object') return String(data);
|
|
13
|
+
|
|
14
|
+
const spaces = ' '.repeat(indent);
|
|
15
|
+
const entries = Object.entries(data as Record<string, unknown>);
|
|
16
|
+
|
|
17
|
+
return entries
|
|
18
|
+
.map(([key, value]) => {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
if (value.length === 0) return `${spaces}${chalk.blue(key)}: ${chalk.gray('[]')}`;
|
|
21
|
+
if (typeof value[0] === 'object') {
|
|
22
|
+
return `${spaces}${chalk.blue(key)}:\n${value.map(v => formatPretty(v, indent + 1)).join('\n')}`;
|
|
23
|
+
}
|
|
24
|
+
return `${spaces}${chalk.blue(key)}: ${value.join(', ')}`;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'object' && value !== null) {
|
|
27
|
+
return `${spaces}${chalk.blue(key)}:\n${formatPretty(value, indent + 1)}`;
|
|
28
|
+
}
|
|
29
|
+
return `${spaces}${chalk.blue(key)}: ${chalk.white(String(value))}`;
|
|
30
|
+
})
|
|
31
|
+
.join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function success(message: string): void { console.log(chalk.green('✓'), message); }
|
|
35
|
+
export function error(message: string): void { console.error(chalk.red('✗'), message); }
|
|
36
|
+
export function warn(message: string): void { console.warn(chalk.yellow('⚠'), message); }
|
|
37
|
+
export function info(message: string): void { console.log(chalk.blue('ℹ'), message); }
|
|
38
|
+
export function print(data: unknown, format: OutputFormat = 'pretty'): void {
|
|
39
|
+
console.log(formatOutput(data, format));
|
|
40
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4969,7 +4969,7 @@ var githubConnector = defineConnector({
|
|
|
4969
4969
|
// package.json
|
|
4970
4970
|
var package_default = {
|
|
4971
4971
|
name: "@hasna/connectors",
|
|
4972
|
-
version: "1.3.
|
|
4972
|
+
version: "1.3.20",
|
|
4973
4973
|
description: "Open source connector library - Install API connectors with a single command",
|
|
4974
4974
|
type: "module",
|
|
4975
4975
|
bin: {
|
|
@@ -16684,6 +16684,13 @@ var connectors = [
|
|
|
16684
16684
|
category: "AI & ML",
|
|
16685
16685
|
tags: ["ai", "llm", "grok"]
|
|
16686
16686
|
},
|
|
16687
|
+
{
|
|
16688
|
+
name: "minimax",
|
|
16689
|
+
displayName: "Minimax",
|
|
16690
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
16691
|
+
category: "AI & ML",
|
|
16692
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
16693
|
+
},
|
|
16687
16694
|
{
|
|
16688
16695
|
name: "mistral",
|
|
16689
16696
|
displayName: "Mistral",
|