@cocoviral/cli 1.5.4

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/dist/client.js ADDED
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CocoViralClient = exports.IMAGE_MODEL_ALIASES = exports.IMAGE_MODEL_ALIAS_MAP = exports.CINEMA_FRAME_MODELS = exports.CINEMA_SHOT_MODELS = exports.VIDEO_MODEL_ID_MAP = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ /** Enough of a mime map for what the upload endpoint accepts. */
11
+ function mimeTypeFor(filePath) {
12
+ const extension = node_path_1.default.extname(filePath).slice(1).toLowerCase();
13
+ const map = {
14
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif',
15
+ mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
16
+ mp3: 'audio/mpeg', wav: 'audio/wav', m4a: 'audio/mp4', ogg: 'audio/ogg',
17
+ };
18
+ return map[extension] || 'application/octet-stream';
19
+ }
20
+ exports.VIDEO_MODEL_ID_MAP = {
21
+ // Veo 3.1
22
+ 'veo3.1': 'google/veo-3.1',
23
+ 'veo3.1-fast': 'google/veo-3.1-fast',
24
+ // Kling 3.0
25
+ kling3: 'fal-ai/kling-video/v3/standard/text-to-video',
26
+ 'kling3-standard': 'fal-ai/kling-video/v3/standard/text-to-video',
27
+ 'kling3-standard-i2v': 'fal-ai/kling-video/v3/standard/image-to-video',
28
+ 'kling3-pro': 'fal-ai/kling-video/v3/pro/text-to-video',
29
+ 'kling3-pro-i2v': 'fal-ai/kling-video/v3/pro/image-to-video',
30
+ 'kling3-4k': 'fal-ai/kling-video/v3/4k/text-to-video',
31
+ 'kling3-4k-i2v': 'fal-ai/kling-video/v3/4k/image-to-video',
32
+ // Seedance 2.0 — standard, fast and mini tiers
33
+ seedance2: 'bytedance/seedance-2.0/text-to-video',
34
+ 'seedance2-i2v': 'bytedance/seedance-2.0/image-to-video',
35
+ 'seedance2-omni': 'bytedance/seedance-2.0/reference-to-video',
36
+ seedance2_fast: 'bytedance/seedance-2.0/fast/text-to-video',
37
+ 'seedance2-fast': 'bytedance/seedance-2.0/fast/text-to-video',
38
+ 'seedance2-i2v-fast': 'bytedance/seedance-2.0/fast/image-to-video',
39
+ 'seedance2-omni-fast': 'bytedance/seedance-2.0/fast/reference-to-video',
40
+ 'seedance2-mini': 'bytedance/seedance-2.0/mini/text-to-video',
41
+ 'seedance2-i2v-mini': 'bytedance/seedance-2.0/mini/image-to-video',
42
+ 'seedance2-omni-mini': 'bytedance/seedance-2.0/mini/reference-to-video',
43
+ // Seedance 2.5 — 30s single pass, up to 50 multimodal references
44
+ 'seedance2.5': 'bytedance/seedance-2.5/text-to-video',
45
+ 'seedance2.5-i2v': 'bytedance/seedance-2.5/image-to-video',
46
+ 'seedance2.5-omni': 'bytedance/seedance-2.5/reference-to-video',
47
+ // FLUX 3 (Black Forest Labs) — native audio. An end frame on -i2v routes to
48
+ // first-last-frame-to-video upstream.
49
+ flux3: 'black-forest-labs/flux-3/text-to-video',
50
+ 'flux3-i2v': 'black-forest-labs/flux-3/image-to-video',
51
+ 'flux3-keyframes': 'black-forest-labs/flux-3/keyframes-to-video',
52
+ 'flux3-extend': 'black-forest-labs/flux-3/extend-video',
53
+ // MiniMax H3 (Hailuo 3.0) — native 2K, native audio
54
+ 'minimax-h3': 'minimax/hailuo-3.0/text-to-video',
55
+ 'minimax-h3-i2v': 'minimax/hailuo-3.0/image-to-video',
56
+ 'minimax-h3-ref': 'minimax/hailuo-3.0/reference-to-video',
57
+ h3: 'minimax/hailuo-3.0/text-to-video',
58
+ 'h3-i2v': 'minimax/hailuo-3.0/image-to-video',
59
+ 'h3-ref': 'minimax/hailuo-3.0/reference-to-video',
60
+ // MiniMax H3 Max — max 768p, native audio, ~half H3's per-second cost
61
+ 'minimax-h3-max': 'minimax/h3-max/text-to-video',
62
+ 'minimax-h3-max-i2v': 'minimax/h3-max/image-to-video',
63
+ 'minimax-h3-max-ref': 'minimax/h3-max/reference-to-video',
64
+ 'h3-max': 'minimax/h3-max/text-to-video',
65
+ 'h3-max-i2v': 'minimax/h3-max/image-to-video',
66
+ 'h3-max-ref': 'minimax/h3-max/reference-to-video',
67
+ };
68
+ /** Image-to-video models offered for `cviral shot` / the cinema MCP tools. */
69
+ exports.CINEMA_SHOT_MODELS = [
70
+ 'kling3-pro-i2v',
71
+ 'kling3-standard-i2v',
72
+ 'seedance2-i2v',
73
+ 'seedance2.5-i2v',
74
+ 'minimax-h3-i2v',
75
+ 'minimax-h3-max-i2v',
76
+ 'flux3-i2v',
77
+ ];
78
+ /** Image models that render a cinematic still frame well. */
79
+ exports.CINEMA_FRAME_MODELS = [
80
+ 'nano-banana-pro',
81
+ 'nano-banana-2',
82
+ 'seedream-v5-pro',
83
+ 'flux-2-pro',
84
+ 'gpt-image-2',
85
+ 'gpt-image-2.5',
86
+ 'midjourney-v8',
87
+ ];
88
+ // Alias → real model ID used by the image API
89
+ exports.IMAGE_MODEL_ALIAS_MAP = {
90
+ 'flux-2-pro': 'black-forest-labs/flux-2-pro',
91
+ 'gpt-image-2': 'openai/gpt-image-2',
92
+ 'nano-banana-2': 'fal-ai/nano-banana-2',
93
+ 'nano-banana-pro': 'google/nano-banana-pro',
94
+ 'midjourney-v8': 'midjourney/v8.2',
95
+ 'gpt-image-2.5': 'openai/gpt-image-2.5-flare/text-to-image',
96
+ 'gpt-image-2.5-sunburst': 'openai/gpt-image-2.5-sunburst/text-to-image',
97
+ 'seedream-v5-lite': 'fal-ai/bytedance/seedream/v5/lite/text-to-image',
98
+ 'seedream-v5-pro': 'fal-ai/bytedance/seedream/v5/pro/text-to-image',
99
+ };
100
+ exports.IMAGE_MODEL_ALIASES = Object.keys(exports.IMAGE_MODEL_ALIAS_MAP);
101
+ class CocoViralClient {
102
+ constructor({ apiKey, apiUrl = 'https://www.cocoviral.ai' }) {
103
+ this.apiKey = apiKey;
104
+ this.http = axios_1.default.create({
105
+ baseURL: `${apiUrl}/api`,
106
+ headers: {
107
+ 'x-api-key': apiKey,
108
+ 'Content-Type': 'application/json',
109
+ },
110
+ timeout: 60000,
111
+ });
112
+ }
113
+ async validate() {
114
+ try {
115
+ const res = await this.http.post('/api-keys/validate', { apiKey: this.apiKey });
116
+ return res.data?.valid === true;
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
122
+ async listVideos(params = {}) {
123
+ const res = await this.http.get('/videos', { params: { limit: 10, page: 1, ...params } });
124
+ return res.data?.data || [];
125
+ }
126
+ async getCredits() {
127
+ const res = await this.http.get('/credits/balance');
128
+ return res.data;
129
+ }
130
+ resolveQuality(modelId, quality) {
131
+ const isGptModel = CocoViralClient.GPT_IMAGE_MODEL_IDS.has(modelId);
132
+ if (quality)
133
+ return quality;
134
+ return isGptModel ? 'low' : 'hd';
135
+ }
136
+ async generateImage(opts) {
137
+ const alias = opts.model || 'flux-2-pro';
138
+ const modelId = exports.IMAGE_MODEL_ALIAS_MAP[alias] ?? exports.IMAGE_MODEL_ALIAS_MAP['flux-2-pro'];
139
+ const quality = this.resolveQuality(modelId, opts.quality);
140
+ const res = await this.http.post('/ai-generation/image', {
141
+ model: modelId,
142
+ quality,
143
+ input: {
144
+ prompt: opts.prompt,
145
+ aspect_ratio: opts.aspectRatio || '1:1',
146
+ ...(opts.outputFormat ? { output_format: opts.outputFormat } : {}),
147
+ ...(opts.referenceImages?.length ? { input_images: opts.referenceImages } : {}),
148
+ },
149
+ });
150
+ return res.data;
151
+ }
152
+ async editImage(opts) {
153
+ const alias = opts.model || 'nano-banana-2';
154
+ const modelId = exports.IMAGE_MODEL_ALIAS_MAP[alias] ?? exports.IMAGE_MODEL_ALIAS_MAP['nano-banana-2'];
155
+ const quality = this.resolveQuality(modelId, opts.quality);
156
+ const res = await this.http.post('/ai-generation/image', {
157
+ model: modelId,
158
+ quality,
159
+ input: {
160
+ prompt: opts.prompt,
161
+ aspect_ratio: opts.aspectRatio || '1:1',
162
+ input_images: [opts.imageUrl],
163
+ },
164
+ });
165
+ return res.data;
166
+ }
167
+ async generateVideo(opts) {
168
+ const modelId = exports.VIDEO_MODEL_ID_MAP[opts.model || 'veo3.1'] ?? exports.VIDEO_MODEL_ID_MAP['veo3.1'];
169
+ const res = await this.http.post('/ai-generation/video', {
170
+ prompt: opts.prompt,
171
+ model: modelId,
172
+ resolution: opts.resolution || 'hd',
173
+ aspect_ratio: opts.aspectRatio || '9:16',
174
+ duration: opts.duration || 5,
175
+ source: 'cli',
176
+ ...(opts.startImageUrl ? { start_image_url: opts.startImageUrl } : {}),
177
+ ...(opts.endImageUrl ? { end_image_url: opts.endImageUrl } : {}),
178
+ ...(opts.referenceImages?.length ? { reference_images: opts.referenceImages } : {}),
179
+ ...(opts.referenceVideos?.length ? { reference_videos: opts.referenceVideos } : {}),
180
+ ...(opts.referenceAudios?.length ? { reference_audios: opts.referenceAudios } : {}),
181
+ });
182
+ return res.data;
183
+ }
184
+ async checkVideoStatus(predictionId) {
185
+ const res = await this.http.get(`/ai-generation/video/${predictionId}/status`);
186
+ return res.data;
187
+ }
188
+ /** @deprecated use checkVideoStatus */
189
+ async checkStatus(predictionId) {
190
+ return this.checkVideoStatus(predictionId);
191
+ }
192
+ async checkImageStatus(predictionId) {
193
+ const res = await this.http.get('/ai-generation/image', { params: { id: predictionId } });
194
+ return res.data;
195
+ }
196
+ async analyzeVideo(videoUrl, prompt) {
197
+ const analysisPrompt = prompt ||
198
+ 'Analyze this video: 1) Concept & hook, 2) Visual style & pacing, 3) Audio/music, 4) Text overlays, 5) Key scenes.';
199
+ const res = await this.http.post('/tools/gemini-video-vision', {
200
+ prompt: analysisPrompt,
201
+ videoUrl,
202
+ limitCharacters: true,
203
+ });
204
+ return res.data?.content || res.data?.result || '';
205
+ }
206
+ async buildRemixVariants(opts) {
207
+ const res = await this.http.post('/video-clone-bulk/build-variants', {
208
+ basePrompt: opts.basePrompt,
209
+ modificationInstructions: opts.modificationInstructions || '',
210
+ productDescription: opts.productDescription || '',
211
+ count: Math.min(10, Math.max(1, opts.count || 3)),
212
+ });
213
+ return res.data?.variants || res.data || [];
214
+ }
215
+ async translateVideo(opts) {
216
+ const res = await this.http.post('/video-translate/jobs', {
217
+ sourceUrl: opts.sourceUrl,
218
+ targetLanguage: opts.targetLanguage,
219
+ tone: opts.tone || 'casual',
220
+ voiceStyle: opts.voiceStyle || '',
221
+ translationMode: 'auto',
222
+ });
223
+ return res.data;
224
+ }
225
+ async getTranslateJobStatus(jobId) {
226
+ const res = await this.http.get(`/video-translate/jobs/${jobId}`);
227
+ return res.data;
228
+ }
229
+ // ── Director Studio ────────────────────────────────────────────────────────
230
+ // Presets and prompt composition live in the API so the CLI, the MCP server
231
+ // and the web studio can never drift apart on what a preset means.
232
+ async listCinemaPresets(params = {}) {
233
+ const res = await this.http.get('/director-studio/presets', { params });
234
+ return res.data;
235
+ }
236
+ async composeCinemaPrompt(opts) {
237
+ const res = await this.http.post('/director-studio/compose', opts);
238
+ return res.data;
239
+ }
240
+ /** Compose the frame prompt from the presets, then generate the still. */
241
+ async generateCinemaFrame(opts) {
242
+ const composed = await this.composeCinemaPrompt({
243
+ mode: 'frame',
244
+ scene: opts.scene,
245
+ lookIds: [opts.look, opts.lighting, opts.palette].filter(Boolean),
246
+ shotSizeId: opts.shotSize,
247
+ cameraBodyId: opts.cameraBody,
248
+ lensId: opts.lens,
249
+ apertureId: opts.aperture,
250
+ });
251
+ const data = await this.generateImage({
252
+ prompt: composed.prompt,
253
+ model: opts.model || 'nano-banana-pro',
254
+ aspectRatio: opts.aspectRatio || '16:9',
255
+ // A cinema frame is a master to animate from, so keep it lossless.
256
+ outputFormat: 'png',
257
+ });
258
+ return { ...data, composed };
259
+ }
260
+ /** Compose the motion prompt from the camera move, then animate the frame. */
261
+ async animateCinemaShot(opts) {
262
+ const composed = await this.composeCinemaPrompt({
263
+ mode: 'shot',
264
+ scene: opts.scene,
265
+ motionId: opts.motion,
266
+ direction: opts.direction,
267
+ });
268
+ const duration = opts.duration ?? composed.recommendedDuration ?? 5;
269
+ const data = await this.generateVideo({
270
+ prompt: composed.prompt,
271
+ model: opts.model || 'kling3-pro-i2v',
272
+ aspectRatio: opts.aspectRatio || '16:9',
273
+ duration,
274
+ resolution: opts.resolution || 'fhd',
275
+ startImageUrl: opts.startImageUrl,
276
+ });
277
+ return { ...data, composed, duration };
278
+ }
279
+ async getCharacters(limit = 20) {
280
+ const res = await this.http.get('/characters', { params: { limit } });
281
+ return res.data?.characters || res.data || [];
282
+ }
283
+ // ── Viral Flow ────────────────────────────────────────────────────────────
284
+ async listFlows() {
285
+ const res = await this.http.get('/viral-flow/sessions');
286
+ return res.data?.sessions || [];
287
+ }
288
+ async getFlow(flowId) {
289
+ const res = await this.http.get(`/viral-flow/sessions/${encodeURIComponent(flowId)}/graph`);
290
+ return res.data?.flow;
291
+ }
292
+ /**
293
+ * Upload a file from disk.
294
+ *
295
+ * This is the CLI's reason to exist: a hosted tool call carries JSON, so the
296
+ * remote server can only take a URL that is already public. Here the bytes
297
+ * are right there on the same machine.
298
+ */
299
+ async uploadFile(filePath) {
300
+ const form = new FormData();
301
+ const buffer = await node_fs_1.default.promises.readFile(filePath);
302
+ const type = mimeTypeFor(filePath);
303
+ form.append('file', new Blob([buffer], { type }), node_path_1.default.basename(filePath));
304
+ const res = await this.http.post('/viral-flow/upload', form, {
305
+ // Let the runtime set the multipart boundary; the client default of
306
+ // application/json would make the server reject this as a URL import.
307
+ headers: { 'Content-Type': undefined },
308
+ maxBodyLength: Infinity,
309
+ maxContentLength: Infinity,
310
+ timeout: 300000,
311
+ });
312
+ return res.data;
313
+ }
314
+ async runFlowNode(flowId, nodeId) {
315
+ const res = await this.http.post(`/viral-flow/sessions/${encodeURIComponent(flowId)}/nodes/${encodeURIComponent(nodeId)}/run`, {});
316
+ return res.data;
317
+ }
318
+ async flowNodeStatus(flowId, nodeId) {
319
+ const res = await this.http.get(`/viral-flow/sessions/${encodeURIComponent(flowId)}/nodes/${encodeURIComponent(nodeId)}/status`);
320
+ return res.data;
321
+ }
322
+ async listFlowAssets(flowId) {
323
+ const res = await this.http.get(`/viral-flow/sessions/${encodeURIComponent(flowId)}/assets`);
324
+ return res.data?.assets || [];
325
+ }
326
+ /** Stream an asset to disk. Assets are public, so this needs no auth header. */
327
+ async downloadTo(url, filePath) {
328
+ const res = await axios_1.default.get(url, { responseType: 'arraybuffer', timeout: 300000 });
329
+ await node_fs_1.default.promises.writeFile(filePath, Buffer.from(res.data));
330
+ }
331
+ }
332
+ exports.CocoViralClient = CocoViralClient;
333
+ // GPT Image model IDs that require quality values: auto | low | medium | high
334
+ CocoViralClient.GPT_IMAGE_MODEL_IDS = new Set([
335
+ 'openai/gpt-image-2',
336
+ 'openai/gpt-image-2/edit',
337
+ 'fal-ai/gpt-image-1/text-to-image',
338
+ 'fal-ai/gpt-image-1.5',
339
+ ]);
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@cocoviral/cli",
3
+ "version": "1.5.4",
4
+ "description": "CocoViral CLI - generate AI images and video, drive Director Studio, and build, run and deliver Viral Flow graphs from your terminal.",
5
+ "bin": {
6
+ "cviral": "dist/cli.js"
7
+ },
8
+ "files": [
9
+ "dist/**/*",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "prepack": "bash ./sync-dist.sh"
14
+ },
15
+ "dependencies": {
16
+ "axios": "^1.6.0"
17
+ },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "keywords": [
25
+ "cli",
26
+ "ai",
27
+ "video",
28
+ "image-generation",
29
+ "video-generation",
30
+ "cocoviral"
31
+ ],
32
+ "license": "MIT",
33
+ "homepage": "https://www.cocoviral.ai/mcp-skills",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/Jet1217/CloneViral.git",
37
+ "directory": "cli"
38
+ }
39
+ }