vargai 0.4.0-alpha34 → 0.4.0-alpha35

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/.env.example CHANGED
@@ -25,3 +25,9 @@ REPLICATE_API_TOKEN=r8_xxx
25
25
 
26
26
  # apify (web scraping actors)
27
27
  APIFY_TOKEN=apify_api_xxx
28
+
29
+ # decart ai (real-time & batch video/image)
30
+ DECART_API_KEY=decart_xxx
31
+
32
+ # together ai (fast flux-schnell, no queue)
33
+ TOGETHER_API_KEY=together_xxx
package/package.json CHANGED
@@ -66,10 +66,9 @@
66
66
  "remotion": "^4.0.377",
67
67
  "replicate": "^1.4.0",
68
68
  "sharp": "^0.34.5",
69
- "vargai": "^0.4.0-alpha11",
70
69
  "zod": "^4.2.1"
71
70
  },
72
- "version": "0.4.0-alpha34",
71
+ "version": "0.4.0-alpha35",
73
72
  "exports": {
74
73
  ".": "./src/index.ts",
75
74
  "./ai": "./src/ai-sdk/index.ts",
@@ -115,7 +115,6 @@ export function withCache<T extends object, R>(
115
115
  const storage = options.storage ?? defaultStorage;
116
116
  const ttl = parseTTL(options.ttl ?? DEFAULT_TTL);
117
117
  const prefix = fn.name || "anonymous";
118
-
119
118
  return async (opts: WithCacheKey<T>): Promise<R> => {
120
119
  const { cacheKey, ...rest } = opts;
121
120
 
@@ -128,7 +127,6 @@ export function withCache<T extends object, R>(
128
127
  if (cached !== undefined) {
129
128
  return cached as R;
130
129
  }
131
-
132
130
  const result = await fn(rest as T);
133
131
  const flattened = flatten(result);
134
132
  await storage.set(key, flattened, ttl);
@@ -88,6 +88,10 @@ export {
88
88
  type ReplicateProviderSettings,
89
89
  replicate,
90
90
  } from "./providers/replicate";
91
+ export {
92
+ createTogetherProvider,
93
+ together,
94
+ } from "./providers/together";
91
95
  export type {
92
96
  VideoModelV3,
93
97
  VideoModelV3CallOptions,
@@ -0,0 +1,457 @@
1
+ # Adding Models & Providers
2
+
3
+ This guide explains how to add new AI models and providers to the varg SDK.
4
+
5
+ ## Overview
6
+
7
+ Providers in varg extend the [Vercel AI SDK](https://sdk.vercel.ai/) with additional model types for video, music, and other media generation. Each provider implements a consistent interface pattern.
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ src/ai-sdk/providers/
13
+ ├── fal.ts # Full provider (video, image, transcription)
14
+ ├── elevenlabs.ts # Speech & music provider
15
+ ├── openai.ts # Extends @ai-sdk/openai with video
16
+ ├── google.ts # Image & video provider
17
+ ├── higgsfield.ts # Image-only provider
18
+ ├── replicate.ts # Re-exports @ai-sdk/replicate
19
+ └── CONTRIBUTING.md # This file
20
+ ```
21
+
22
+ ## Model Types
23
+
24
+ | Type | Interface | Use Case |
25
+ |------|-----------|----------|
26
+ | `VideoModelV3` | `../video-model.ts` | Video generation (t2v, i2v, lipsync) |
27
+ | `ImageModelV3` | `@ai-sdk/provider` | Image generation |
28
+ | `SpeechModelV3` | `@ai-sdk/provider` | Text-to-speech |
29
+ | `MusicModelV3` | `../music-model.ts` | Music generation |
30
+ | `TranscriptionModelV3` | `@ai-sdk/provider` | Speech-to-text |
31
+ | `LanguageModelV3` | `@ai-sdk/provider` | LLM text generation |
32
+ | `EmbeddingModelV3` | `@ai-sdk/provider` | Text embeddings |
33
+
34
+ ## Adding a New Model to an Existing Provider
35
+
36
+ ### Example: Adding a new video model to fal.ts
37
+
38
+ 1. **Add to the model mapping:**
39
+
40
+ ```typescript
41
+ const VIDEO_MODELS: Record<string, { t2v: string; i2v: string }> = {
42
+ // existing models...
43
+ "new-model-v1": {
44
+ t2v: "fal-ai/new-model/text-to-video",
45
+ i2v: "fal-ai/new-model/image-to-video",
46
+ },
47
+ };
48
+ ```
49
+
50
+ 2. **That's it!** The existing `FalVideoModel` class handles the rest.
51
+
52
+ ### Example: Adding a model with special handling
53
+
54
+ If the new model needs custom logic, add conditional handling in `doGenerate()`:
55
+
56
+ ```typescript
57
+ async doGenerate(options: VideoModelV3CallOptions) {
58
+ const isNewModel = this.modelId === "new-model-v1";
59
+
60
+ if (isNewModel) {
61
+ // Custom input handling for this model
62
+ input.special_param = options.providerOptions?.fal?.specialParam;
63
+ }
64
+
65
+ // ... rest of generation logic
66
+ }
67
+ ```
68
+
69
+ ## Creating a New Provider
70
+
71
+ ### Step 1: Define the Provider Interface
72
+
73
+ ```typescript
74
+ import {
75
+ type EmbeddingModelV3,
76
+ type ImageModelV3,
77
+ type LanguageModelV3,
78
+ NoSuchModelError,
79
+ type ProviderV3,
80
+ } from "@ai-sdk/provider";
81
+ import type { VideoModelV3 } from "../video-model";
82
+
83
+ export interface MyProviderSettings {
84
+ apiKey?: string;
85
+ baseURL?: string;
86
+ }
87
+
88
+ export interface MyProvider extends ProviderV3 {
89
+ // Add methods for each model type you support
90
+ videoModel(modelId: string): VideoModelV3;
91
+ imageModel(modelId: string): ImageModelV3;
92
+ }
93
+ ```
94
+
95
+ ### Step 2: Implement Model Classes
96
+
97
+ Each model class must implement the corresponding interface:
98
+
99
+ ```typescript
100
+ class MyVideoModel implements VideoModelV3 {
101
+ readonly specificationVersion = "v3" as const;
102
+ readonly provider = "myprovider";
103
+ readonly modelId: string;
104
+ readonly maxVideosPerCall = 1;
105
+
106
+ private apiKey: string;
107
+
108
+ constructor(modelId: string, options: { apiKey?: string } = {}) {
109
+ this.modelId = modelId;
110
+ this.apiKey = options.apiKey ?? process.env.MY_PROVIDER_API_KEY ?? "";
111
+ }
112
+
113
+ async doGenerate(options: VideoModelV3CallOptions) {
114
+ const {
115
+ prompt,
116
+ duration,
117
+ aspectRatio,
118
+ files,
119
+ providerOptions,
120
+ abortSignal,
121
+ } = options;
122
+
123
+ const warnings: SharedV3Warning[] = [];
124
+
125
+ // 1. Build API request
126
+ const input: Record<string, unknown> = {
127
+ prompt,
128
+ duration: duration ?? 5,
129
+ ...(providerOptions?.myprovider ?? {}),
130
+ };
131
+
132
+ // 2. Handle file inputs (for image-to-video, etc.)
133
+ if (files && files.length > 0) {
134
+ const imageFile = files.find(f =>
135
+ f.type === "file"
136
+ ? f.mediaType?.startsWith("image/")
137
+ : /\.(jpg|jpeg|png|webp)$/i.test(f.url)
138
+ );
139
+ if (imageFile) {
140
+ input.image_url = await this.uploadFile(imageFile);
141
+ }
142
+ }
143
+
144
+ // 3. Call the API
145
+ const response = await fetch("https://api.myprovider.com/v1/generate", {
146
+ method: "POST",
147
+ headers: {
148
+ "Authorization": `Bearer ${this.apiKey}`,
149
+ "Content-Type": "application/json",
150
+ },
151
+ body: JSON.stringify(input),
152
+ signal: abortSignal,
153
+ });
154
+
155
+ if (!response.ok) {
156
+ throw new Error(`API error: ${await response.text()}`);
157
+ }
158
+
159
+ const data = await response.json();
160
+
161
+ // 4. Download the result
162
+ const videoResponse = await fetch(data.video_url, { signal: abortSignal });
163
+ const videoBuffer = new Uint8Array(await videoResponse.arrayBuffer());
164
+
165
+ // 5. Return in standard format
166
+ return {
167
+ videos: [videoBuffer],
168
+ warnings,
169
+ response: {
170
+ timestamp: new Date(),
171
+ modelId: this.modelId,
172
+ headers: undefined,
173
+ },
174
+ };
175
+ }
176
+
177
+ private async uploadFile(file: ImageModelV3File): Promise<string> {
178
+ // Implementation depends on provider's upload mechanism
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### Step 3: Create the Provider Factory
184
+
185
+ ```typescript
186
+ export function createMyProvider(
187
+ settings: MyProviderSettings = {},
188
+ ): MyProvider {
189
+ const apiKey = settings.apiKey ?? process.env.MY_PROVIDER_API_KEY;
190
+
191
+ if (!apiKey) {
192
+ throw new Error("MY_PROVIDER_API_KEY not set");
193
+ }
194
+
195
+ return {
196
+ specificationVersion: "v3",
197
+
198
+ videoModel(modelId: string): VideoModelV3 {
199
+ return new MyVideoModel(modelId, { apiKey });
200
+ },
201
+
202
+ imageModel(modelId: string): ImageModelV3 {
203
+ return new MyImageModel(modelId, { apiKey });
204
+ },
205
+
206
+ // Throw NoSuchModelError for unsupported model types
207
+ languageModel(modelId: string): LanguageModelV3 {
208
+ throw new NoSuchModelError({ modelId, modelType: "languageModel" });
209
+ },
210
+
211
+ embeddingModel(modelId: string): EmbeddingModelV3 {
212
+ throw new NoSuchModelError({ modelId, modelType: "embeddingModel" });
213
+ },
214
+ };
215
+ }
216
+ ```
217
+
218
+ ### Step 4: Export a Lazy Singleton
219
+
220
+ ```typescript
221
+ // Lazy initialization - only creates client when first accessed
222
+ let _myprovider: MyProvider | undefined;
223
+
224
+ export const myprovider = new Proxy({} as MyProvider, {
225
+ get(_, prop) {
226
+ if (!_myprovider) {
227
+ _myprovider = createMyProvider();
228
+ }
229
+ return _myprovider[prop as keyof MyProvider];
230
+ },
231
+ });
232
+ ```
233
+
234
+ ### Step 5: Re-export from index
235
+
236
+ Add to `src/ai-sdk/index.ts`:
237
+
238
+ ```typescript
239
+ export { createMyProvider, myprovider } from "./providers/myprovider";
240
+ export type { MyProvider, MyProviderSettings } from "./providers/myprovider";
241
+ ```
242
+
243
+ ## Handling Warnings
244
+
245
+ Use warnings to communicate unsupported features without failing:
246
+
247
+ ```typescript
248
+ if (options.seed !== undefined) {
249
+ warnings.push({
250
+ type: "unsupported",
251
+ feature: "seed",
252
+ details: "Seed is not supported by this model",
253
+ });
254
+ }
255
+
256
+ if (options.fps !== undefined) {
257
+ warnings.push({
258
+ type: "unsupported",
259
+ feature: "fps",
260
+ details: "FPS is not configurable, using provider default",
261
+ });
262
+ }
263
+ ```
264
+
265
+ ## Provider Options Passthrough
266
+
267
+ Allow provider-specific options via `providerOptions`:
268
+
269
+ ```typescript
270
+ // User code:
271
+ await generateVideo({
272
+ model: myprovider.videoModel("model-v1"),
273
+ prompt: "a cat",
274
+ providerOptions: {
275
+ myprovider: {
276
+ customParam: "value",
277
+ negativePrompt: "blurry",
278
+ },
279
+ },
280
+ });
281
+
282
+ // In your model:
283
+ const customOptions = providerOptions?.myprovider ?? {};
284
+ input.custom_param = customOptions.customParam;
285
+ input.negative_prompt = customOptions.negativePrompt;
286
+ ```
287
+
288
+ ## Async Job Polling
289
+
290
+ Many video APIs are async. Here's the standard polling pattern:
291
+
292
+ ```typescript
293
+ async doGenerate(options: VideoModelV3CallOptions) {
294
+ // 1. Create job
295
+ const createResponse = await fetch(`${this.baseURL}/jobs`, {
296
+ method: "POST",
297
+ headers: { Authorization: `Bearer ${this.apiKey}` },
298
+ body: JSON.stringify(input),
299
+ signal: options.abortSignal,
300
+ });
301
+
302
+ const job = await createResponse.json();
303
+
304
+ // 2. Poll for completion
305
+ let status = job.status;
306
+ while (status === "queued" || status === "processing") {
307
+ await new Promise(resolve => setTimeout(resolve, 2000));
308
+
309
+ const statusResponse = await fetch(`${this.baseURL}/jobs/${job.id}`, {
310
+ headers: { Authorization: `Bearer ${this.apiKey}` },
311
+ signal: options.abortSignal,
312
+ });
313
+
314
+ const statusData = await statusResponse.json();
315
+ status = statusData.status;
316
+ }
317
+
318
+ if (status === "failed") {
319
+ throw new Error(`Generation failed: ${job.error}`);
320
+ }
321
+
322
+ // 3. Download result
323
+ const videoResponse = await fetch(job.output_url);
324
+ return { videos: [new Uint8Array(await videoResponse.arrayBuffer())] };
325
+ }
326
+ ```
327
+
328
+ ## File Upload Helpers
329
+
330
+ Common pattern for handling file inputs:
331
+
332
+ ```typescript
333
+ import type { ImageModelV3File } from "@ai-sdk/provider";
334
+
335
+ async function fileToUrl(file: ImageModelV3File): Promise<string> {
336
+ if (file.type === "url") {
337
+ return file.url;
338
+ }
339
+
340
+ // Convert base64/Uint8Array to upload
341
+ const bytes = typeof file.data === "string"
342
+ ? Uint8Array.from(atob(file.data), c => c.charCodeAt(0))
343
+ : file.data;
344
+
345
+ const blob = new Blob([bytes], { type: file.mediaType ?? "image/png" });
346
+
347
+ // Upload to provider's storage (or use data URL for small files)
348
+ return await uploadToStorage(blob);
349
+ }
350
+
351
+ function getMediaType(file: ImageModelV3File): string | undefined {
352
+ if (file.type === "file") return file.mediaType;
353
+
354
+ const ext = file.url.split(".").pop()?.toLowerCase();
355
+ const mimeTypes: Record<string, string> = {
356
+ png: "image/png",
357
+ jpg: "image/jpeg",
358
+ jpeg: "image/jpeg",
359
+ mp3: "audio/mpeg",
360
+ wav: "audio/wav",
361
+ mp4: "video/mp4",
362
+ };
363
+ return mimeTypes[ext ?? ""];
364
+ }
365
+ ```
366
+
367
+ ## Extending Existing Providers
368
+
369
+ To add video support to an existing AI SDK provider (like OpenAI):
370
+
371
+ ```typescript
372
+ import {
373
+ createOpenAI as createOpenAIBase,
374
+ type OpenAIProvider as OpenAIProviderBase,
375
+ } from "@ai-sdk/openai";
376
+
377
+ // Extend the base provider interface
378
+ export interface OpenAIProvider extends OpenAIProviderBase {
379
+ videoModel(modelId: string): VideoModelV3;
380
+ }
381
+
382
+ export function createOpenAI(settings = {}): OpenAIProvider {
383
+ const base = createOpenAIBase(settings);
384
+
385
+ // Create callable function with all base methods
386
+ const provider = ((modelId: string) => base(modelId)) as OpenAIProvider;
387
+ Object.assign(provider, base);
388
+
389
+ // Add video support
390
+ provider.videoModel = (modelId: string): VideoModelV3 =>
391
+ new OpenAIVideoModel(modelId, settings);
392
+
393
+ return provider;
394
+ }
395
+ ```
396
+
397
+ ## Re-exporting External Providers
398
+
399
+ For providers that work as-is from `@ai-sdk/*`:
400
+
401
+ ```typescript
402
+ // replicate.ts - simple re-export
403
+ export {
404
+ createReplicate,
405
+ replicate,
406
+ type ReplicateProvider,
407
+ type ReplicateProviderSettings,
408
+ } from "@ai-sdk/replicate";
409
+ ```
410
+
411
+ ## Testing Your Provider
412
+
413
+ ```typescript
414
+ import { describe, test, expect } from "bun:test";
415
+ import { createMyProvider } from "./myprovider";
416
+
417
+ describe("MyProvider", () => {
418
+ test("creates video model", () => {
419
+ const provider = createMyProvider({ apiKey: "test-key" });
420
+ const model = provider.videoModel("model-v1");
421
+
422
+ expect(model.provider).toBe("myprovider");
423
+ expect(model.modelId).toBe("model-v1");
424
+ expect(model.specificationVersion).toBe("v3");
425
+ });
426
+
427
+ test("throws on missing api key", () => {
428
+ delete process.env.MY_PROVIDER_API_KEY;
429
+ expect(() => createMyProvider()).toThrow("MY_PROVIDER_API_KEY not set");
430
+ });
431
+ });
432
+ ```
433
+
434
+ ## Checklist for New Providers
435
+
436
+ - [ ] Implements `ProviderV3` interface
437
+ - [ ] Model classes implement correct `*ModelV3` interfaces
438
+ - [ ] `specificationVersion` is `"v3"`
439
+ - [ ] Factory function `createProvider(settings)`
440
+ - [ ] Lazy singleton export for convenience
441
+ - [ ] API key from settings OR environment variable
442
+ - [ ] `NoSuchModelError` for unsupported model types
443
+ - [ ] Warnings for unsupported features (don't fail silently)
444
+ - [ ] `providerOptions` passthrough for provider-specific params
445
+ - [ ] `abortSignal` support for cancellation
446
+ - [ ] Proper error handling with descriptive messages
447
+ - [ ] Re-exported from `src/ai-sdk/index.ts`
448
+ - [ ] Environment variable documented in README
449
+
450
+ ## Questions?
451
+
452
+ Check existing providers for reference implementations:
453
+ - **Full provider**: `fal.ts` (video, image, transcription)
454
+ - **Audio provider**: `elevenlabs.ts` (speech, music)
455
+ - **Extended provider**: `openai.ts` (adds video to base)
456
+ - **Simple provider**: `higgsfield.ts` (image only)
457
+ - **Re-export**: `replicate.ts`
@@ -1,6 +1,7 @@
1
1
  export { LocalBackend, localBackend } from "./local";
2
2
  export type {
3
3
  FFmpegBackend,
4
+ FFmpegInput,
4
5
  FFmpegRunOptions,
5
6
  FFmpegRunResult,
6
7
  VideoInfo,
@@ -1,13 +1,12 @@
1
1
  import { $ } from "bun";
2
2
  import type {
3
3
  FFmpegBackend,
4
+ FFmpegInput,
4
5
  FFmpegRunOptions,
5
6
  FFmpegRunResult,
6
7
  VideoInfo,
7
8
  } from "./types";
8
9
 
9
- const FFMPEG_COMMON_ARGS = ["-hide_banner", "-loglevel", "error"];
10
-
11
10
  export class LocalBackend implements FFmpegBackend {
12
11
  readonly name = "local";
13
12
 
@@ -39,13 +38,43 @@ export class LocalBackend implements FFmpegBackend {
39
38
  };
40
39
  }
41
40
 
41
+ private buildInputArgs(inputs: FFmpegInput[]): string[] {
42
+ const args: string[] = [];
43
+ for (const input of inputs) {
44
+ if (typeof input === "string") {
45
+ args.push("-i", input);
46
+ } else if ("raw" in input) {
47
+ args.push(...input.raw.split(" "));
48
+ } else {
49
+ if (input.options) args.push(...input.options);
50
+ args.push("-i", input.path);
51
+ }
52
+ }
53
+ return args;
54
+ }
55
+
42
56
  async run(options: FFmpegRunOptions): Promise<FFmpegRunResult> {
43
- const { args, outputPath, verbose } = options;
57
+ const {
58
+ inputs,
59
+ filterComplex,
60
+ videoFilter,
61
+ outputArgs = [],
62
+ outputPath,
63
+ verbose,
64
+ } = options;
65
+
66
+ const inputArgs = this.buildInputArgs(inputs);
44
67
 
45
68
  const ffmpegArgs = [
46
- ...FFMPEG_COMMON_ARGS.slice(0, 2),
69
+ "-hide_banner",
70
+ "-loglevel",
47
71
  verbose ? "info" : "error",
48
- ...args,
72
+ ...inputArgs,
73
+ ...(filterComplex ? ["-filter_complex", filterComplex] : []),
74
+ ...(videoFilter ? ["-vf", videoFilter] : []),
75
+ ...outputArgs,
76
+ "-y",
77
+ outputPath,
49
78
  ];
50
79
 
51
80
  if (verbose) {
@@ -11,13 +11,33 @@ import type { VideoInfo } from "../types";
11
11
  export type { VideoInfo };
12
12
 
13
13
  /**
14
- * FFmpeg execution options
14
+ * Represents an input to ffmpeg - can be a simple path/URL or structured with options
15
+ */
16
+ export type FFmpegInput =
17
+ | string
18
+ | {
19
+ /** Path or URL to the input file */
20
+ path: string;
21
+ /** Options to apply BEFORE the -i flag (e.g. -ss 5 for seeking) */
22
+ options?: string[];
23
+ }
24
+ | {
25
+ /** Raw ffmpeg args that don't use -i (e.g. "-f lavfi -i color=black") */
26
+ raw: string;
27
+ };
28
+
29
+ /**
30
+ * FFmpeg execution options - new interface where backend builds -i flags
15
31
  */
16
32
  export interface FFmpegRunOptions {
17
- /** ffmpeg arguments (without the 'ffmpeg' command itself) */
18
- args: string[];
19
- /** List of input file paths (local or URLs) */
20
- inputs: string[];
33
+ /** Inputs - backend builds -i flags from these */
34
+ inputs: FFmpegInput[];
35
+ /** Filter complex string (uses input indices like [0:v], [1:a]) */
36
+ filterComplex?: string;
37
+ /** Video filter string for single-input operations */
38
+ videoFilter?: string;
39
+ /** Arguments after inputs but before output (codec, map, etc) */
40
+ outputArgs?: string[];
21
41
  /** Output file path */
22
42
  outputPath: string;
23
43
  /** Enable verbose logging */
@@ -840,10 +840,9 @@ export async function editly(config: EditlyConfig): Promise<EditlyResult> {
840
840
  allFilters.push(audioFilter.filter);
841
841
  }
842
842
 
843
- const inputArgs = allInputs.flatMap((input) => ["-i", input]);
844
843
  const filterComplex = allFilters.join(";");
845
844
 
846
- const outputArgs = customOutputArgs ?? [
845
+ const codecArgs = customOutputArgs ?? [
847
846
  "-c:v",
848
847
  "libx264",
849
848
  "-preset",
@@ -860,30 +859,34 @@ export async function editly(config: EditlyConfig): Promise<EditlyResult> {
860
859
  ? ["-map", `[${finalVideoLabel}]`, "-map", `[${audioFilter.outputLabel}]`]
861
860
  : ["-map", `[${finalVideoLabel}]`];
862
861
 
863
- const ffmpegArgs = [
864
- "-hide_banner",
865
- "-loglevel",
866
- verbose ? "info" : "error",
867
- ...inputArgs,
868
- "-filter_complex",
869
- filterComplex,
862
+ const outputArgs = [
870
863
  ...mapArgs,
871
864
  "-r",
872
865
  String(fps),
873
- ...outputArgs,
866
+ ...codecArgs,
874
867
  ...(config.shortest ? ["-shortest"] : []),
875
- "-y",
876
- outPath,
877
868
  ];
878
869
 
879
870
  if (verbose) {
880
- console.log("ffmpeg", ffmpegArgs.join(" "));
871
+ const inputArgs = allInputs.flatMap((input) => ["-i", input]);
872
+ console.log(
873
+ "ffmpeg",
874
+ [
875
+ ...inputArgs,
876
+ "-filter_complex",
877
+ filterComplex,
878
+ ...outputArgs,
879
+ "-y",
880
+ outPath,
881
+ ].join(" "),
882
+ );
881
883
  console.log("\nFilter complex:\n", filterComplex.split(";").join(";\n"));
882
884
  }
883
885
 
884
886
  const result = await backend.run({
885
- args: ffmpegArgs,
886
887
  inputs: allInputs,
888
+ filterComplex,
889
+ outputArgs,
887
890
  outputPath: outPath,
888
891
  verbose,
889
892
  });