egaki 0.0.2 → 0.2.0
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/CHANGELOG.md +69 -0
- package/README.md +88 -15
- package/dist/antigravity-auth.d.ts +15 -0
- package/dist/antigravity-auth.d.ts.map +1 -0
- package/dist/antigravity-auth.js +544 -0
- package/dist/antigravity-image.d.ts +11 -0
- package/dist/antigravity-image.d.ts.map +1 -0
- package/dist/antigravity-image.js +285 -0
- package/dist/cli.js +215 -20
- package/dist/credentials.d.ts +1 -1
- package/dist/credentials.js +1 -1
- package/dist/gateway-model-candidates.generated.d.ts +11 -0
- package/dist/gateway-model-candidates.generated.d.ts.map +1 -0
- package/dist/gateway-model-candidates.generated.js +64 -0
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +169 -0
- package/dist/models.d.ts +7 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +50 -1
- package/dist/open-browser.d.ts +2 -0
- package/dist/open-browser.d.ts.map +1 -0
- package/dist/open-browser.js +37 -0
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +38 -14
- package/dist/video-model-catalog.d.ts +52 -0
- package/dist/video-model-catalog.d.ts.map +1 -0
- package/dist/video-model-catalog.js +245 -0
- package/package.json +6 -6
- package/src/cli.ts +342 -23
- package/src/credentials.ts +1 -1
- package/src/model-catalog.ts +175 -0
- package/src/models.ts +64 -3
- package/src/open-browser.ts +48 -0
- package/src/subscription.ts +41 -14
- package/src/video-model-catalog.ts +302 -0
package/src/cli.ts
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
import { goke } from 'goke'
|
|
11
11
|
import { z } from 'zod'
|
|
12
12
|
import dedent from 'string-dedent'
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
generateImage as aiGenerateImage,
|
|
15
|
+
generateText,
|
|
16
|
+
experimental_generateVideo as aiGenerateVideo,
|
|
17
|
+
} from 'ai'
|
|
14
18
|
import fs from 'node:fs'
|
|
15
19
|
import path from 'node:path'
|
|
16
20
|
import pc from 'picocolors'
|
|
@@ -18,12 +22,15 @@ import pkg from '../package.json' with { type: 'json' }
|
|
|
18
22
|
import { injectCredentialsToEnv, PROVIDERS } from './credentials.js'
|
|
19
23
|
import {
|
|
20
24
|
IMAGE_MODELS,
|
|
25
|
+
VIDEO_MODELS,
|
|
21
26
|
DEFAULT_MODEL,
|
|
27
|
+
DEFAULT_VIDEO_MODEL,
|
|
22
28
|
getModelConfig,
|
|
23
29
|
createImageModel,
|
|
24
30
|
createTextModel,
|
|
31
|
+
createVideoModel,
|
|
25
32
|
} from './models.js'
|
|
26
|
-
import
|
|
33
|
+
import { VIDEO_CATALOG } from './video-model-catalog.js'
|
|
27
34
|
import {
|
|
28
35
|
loginInteractive,
|
|
29
36
|
loginNonInteractive,
|
|
@@ -131,13 +138,13 @@ cli
|
|
|
131
138
|
Egaki subscription avoids managing one key per provider.
|
|
132
139
|
Three plans: Starter ($9/mo, 100 credits), Pro ($29/mo, 500 credits),
|
|
133
140
|
Unlimited ($99/mo, 2000 credits). One credit ≈ one standard image.
|
|
134
|
-
Interactive mode: pick a plan
|
|
135
|
-
Non-interactive:
|
|
141
|
+
Interactive mode: pick a plan and get a checkout URL (email prefill optional).
|
|
142
|
+
Non-interactive: --email is optional and only pre-fills checkout.
|
|
136
143
|
`,
|
|
137
144
|
)
|
|
138
145
|
.option(
|
|
139
146
|
'-e, --email [email]',
|
|
140
|
-
z.string().describe('
|
|
147
|
+
z.string().describe('Optional email prefill for checkout (skips interactive prompt)'),
|
|
141
148
|
)
|
|
142
149
|
.option(
|
|
143
150
|
'--plan [plan]',
|
|
@@ -147,6 +154,8 @@ cli
|
|
|
147
154
|
.example('egaki subscribe')
|
|
148
155
|
.example('# Non-interactive (for agents)')
|
|
149
156
|
.example('egaki subscribe --email user@example.com --plan pro')
|
|
157
|
+
.example('# Non-interactive without email prefill')
|
|
158
|
+
.example('egaki subscribe --plan pro')
|
|
150
159
|
.action(async (options) => {
|
|
151
160
|
const isTTY = process.stdout.isTTY && process.stdin.isTTY
|
|
152
161
|
if (!isTTY || options.email || options.plan) {
|
|
@@ -331,6 +340,118 @@ cli
|
|
|
331
340
|
}
|
|
332
341
|
})
|
|
333
342
|
|
|
343
|
+
// ─── video command ───────────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
cli
|
|
346
|
+
.command(
|
|
347
|
+
'video <prompt>',
|
|
348
|
+
dedent`
|
|
349
|
+
Generate videos from a text prompt (or image+text prompt for models that
|
|
350
|
+
support image-to-video). Uses AI SDK experimental_generateVideo under the hood.
|
|
351
|
+
|
|
352
|
+
Agent note: video generation can be slow. When invoking this command from
|
|
353
|
+
automation, use a command timeout of at least 5 minutes.
|
|
354
|
+
`,
|
|
355
|
+
)
|
|
356
|
+
.option(
|
|
357
|
+
'-m, --model [model]',
|
|
358
|
+
z.enum(VIDEO_MODELS).default(DEFAULT_VIDEO_MODEL).describe('Video model ID for generation'),
|
|
359
|
+
)
|
|
360
|
+
.option(
|
|
361
|
+
'-o, --output [path]',
|
|
362
|
+
z
|
|
363
|
+
.string()
|
|
364
|
+
.default('egaki-output.mp4')
|
|
365
|
+
.describe('Output file path (index suffix added when generating multiple)'),
|
|
366
|
+
)
|
|
367
|
+
.option(
|
|
368
|
+
'-n, --count [n]',
|
|
369
|
+
z.number().default(1).describe('Number of videos to generate'),
|
|
370
|
+
)
|
|
371
|
+
.option(
|
|
372
|
+
'--aspect-ratio [ratio]',
|
|
373
|
+
z.string().describe('Video aspect ratio in WIDTH:HEIGHT format (e.g. 16:9, 9:16)'),
|
|
374
|
+
)
|
|
375
|
+
.option(
|
|
376
|
+
'--resolution [resolution]',
|
|
377
|
+
z
|
|
378
|
+
.string()
|
|
379
|
+
.describe('Video resolution in WIDTHxHEIGHT or provider format (e.g. 1280x720, 720p)'),
|
|
380
|
+
)
|
|
381
|
+
.option(
|
|
382
|
+
'--duration [seconds]',
|
|
383
|
+
z.number().describe('Video duration in seconds (provider/model-specific limits apply)'),
|
|
384
|
+
)
|
|
385
|
+
.option(
|
|
386
|
+
'--fps [fps]',
|
|
387
|
+
z.number().describe('Frames per second for video models that support fps override'),
|
|
388
|
+
)
|
|
389
|
+
.option(
|
|
390
|
+
'--seed [seed]',
|
|
391
|
+
z.number().describe('Seed for reproducible video generation (model support varies)'),
|
|
392
|
+
)
|
|
393
|
+
.option(
|
|
394
|
+
'-i, --input [file]',
|
|
395
|
+
z
|
|
396
|
+
.string()
|
|
397
|
+
.describe(
|
|
398
|
+
'Optional reference image for image-to-video. Accepts local file path or URL (http/https)',
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
.option(
|
|
402
|
+
'--json',
|
|
403
|
+
'Output result metadata as JSON to stdout (model, usage, warnings, file paths)',
|
|
404
|
+
)
|
|
405
|
+
.option(
|
|
406
|
+
'--stdout',
|
|
407
|
+
'Write raw video bytes to stdout instead of saving to a file. Useful for piping to other tools',
|
|
408
|
+
)
|
|
409
|
+
.example('# Generate a video')
|
|
410
|
+
.example('egaki video "A paper airplane gliding through clouds" -o airplane.mp4')
|
|
411
|
+
.example('# Generate with Veo model + duration')
|
|
412
|
+
.example('egaki video "cinematic rainy street at night" -m veo-3.1-fast-generate-001 --duration 6')
|
|
413
|
+
.example('# Image-to-video (model support required)')
|
|
414
|
+
.example('egaki video "animate subtle camera pan" --model luma-ray-2 --input frame.png -o animated.mp4')
|
|
415
|
+
.example('# Generate multiple videos')
|
|
416
|
+
.example('egaki video "waves crashing on cliffs" -n 2 -o waves.mp4')
|
|
417
|
+
.action(async (prompt, options) => {
|
|
418
|
+
injectCredentialsToEnv()
|
|
419
|
+
|
|
420
|
+
const model = options.model
|
|
421
|
+
const outputPath = options.output
|
|
422
|
+
const config = getModelConfig(model)
|
|
423
|
+
|
|
424
|
+
if (config.strategy !== 'video') {
|
|
425
|
+
console.error(pc.red(`Model ${model} is not a video model`))
|
|
426
|
+
process.exit(1)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!options.stdout) {
|
|
430
|
+
console.error(pc.dim(`Model: ${model}`))
|
|
431
|
+
console.error(pc.dim(`Prompt: ${prompt}`))
|
|
432
|
+
console.error(pc.dim('Mode: Video API (experimental_generateVideo)'))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const inputImage = options.input
|
|
436
|
+
? await readInputSource(options.input)
|
|
437
|
+
: undefined
|
|
438
|
+
|
|
439
|
+
await generateWithVideoModel({
|
|
440
|
+
prompt,
|
|
441
|
+
model,
|
|
442
|
+
outputPath,
|
|
443
|
+
count: options.count,
|
|
444
|
+
aspectRatio: options.aspectRatio,
|
|
445
|
+
resolution: options.resolution,
|
|
446
|
+
duration: options.duration,
|
|
447
|
+
fps: options.fps,
|
|
448
|
+
seed: options.seed,
|
|
449
|
+
inputImage,
|
|
450
|
+
json: options.json || false,
|
|
451
|
+
stdout: options.stdout || false,
|
|
452
|
+
})
|
|
453
|
+
})
|
|
454
|
+
|
|
334
455
|
// ─── models command ──────────────────────────────────────────────────────────
|
|
335
456
|
|
|
336
457
|
cli
|
|
@@ -345,12 +466,24 @@ cli
|
|
|
345
466
|
'-p, --provider [provider]',
|
|
346
467
|
z.string().describe('Filter models by provider name (e.g. google, openai, replicate, fal)'),
|
|
347
468
|
)
|
|
469
|
+
.option(
|
|
470
|
+
'--type [type]',
|
|
471
|
+
z
|
|
472
|
+
.enum(['all', 'image', 'video'])
|
|
473
|
+
.default('all')
|
|
474
|
+
.describe('Filter by model type: image (image+text-image), video, or all'),
|
|
475
|
+
)
|
|
348
476
|
.option('--json', 'Output as JSON instead of YAML')
|
|
349
477
|
.action(async (options) => {
|
|
350
478
|
const { CATALOG } = await import('./model-catalog.js')
|
|
351
479
|
const yaml = await import('js-yaml')
|
|
352
480
|
|
|
353
|
-
let models =
|
|
481
|
+
let models = options.type === 'video'
|
|
482
|
+
? VIDEO_CATALOG
|
|
483
|
+
: options.type === 'image'
|
|
484
|
+
? CATALOG
|
|
485
|
+
: [...CATALOG, ...VIDEO_CATALOG]
|
|
486
|
+
|
|
354
487
|
if (options.provider) {
|
|
355
488
|
models = models.filter((m) => m.provider === options.provider)
|
|
356
489
|
if (models.length === 0) {
|
|
@@ -366,18 +499,29 @@ cli
|
|
|
366
499
|
provider: m.provider,
|
|
367
500
|
strategy: m.strategy,
|
|
368
501
|
released: m.released,
|
|
369
|
-
cost:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
502
|
+
cost: formatCatalogCost(m.cost),
|
|
503
|
+
features:
|
|
504
|
+
m.strategy === 'video'
|
|
505
|
+
? {
|
|
506
|
+
textToVideo: m.features.textToVideo,
|
|
507
|
+
imageToVideo: m.features.imageToVideo,
|
|
508
|
+
capabilities: m.features.capabilities.join(', ') || 'none',
|
|
509
|
+
seed: m.features.seed,
|
|
510
|
+
multipleVideos: m.features.multipleVideos,
|
|
511
|
+
aspectRatios: m.features.aspectRatios?.join(', ') || 'none',
|
|
512
|
+
resolutions: m.features.resolutions?.join(', ') || 'unknown',
|
|
513
|
+
durationRangeSec: m.features.durationRangeSec
|
|
514
|
+
? `${m.features.durationRangeSec.min}-${m.features.durationRangeSec.max}`
|
|
515
|
+
: 'unknown',
|
|
516
|
+
}
|
|
517
|
+
: {
|
|
518
|
+
editing: m.features.editing,
|
|
519
|
+
inpainting: m.features.inpainting,
|
|
520
|
+
seed: m.features.seed,
|
|
521
|
+
multipleImages: m.features.multipleImages,
|
|
522
|
+
aspectRatios: m.features.aspectRatios.join(', ') || 'none',
|
|
523
|
+
...(m.features.sizes ? { sizes: m.features.sizes.join(', ') } : {}),
|
|
524
|
+
},
|
|
381
525
|
}))
|
|
382
526
|
|
|
383
527
|
if (options.json) {
|
|
@@ -629,24 +773,187 @@ async function generateWithTextModel({
|
|
|
629
773
|
}
|
|
630
774
|
}
|
|
631
775
|
|
|
776
|
+
// Generate using experimental_generateVideo (video models)
|
|
777
|
+
async function generateWithVideoModel({
|
|
778
|
+
prompt,
|
|
779
|
+
model,
|
|
780
|
+
outputPath,
|
|
781
|
+
count,
|
|
782
|
+
aspectRatio,
|
|
783
|
+
resolution,
|
|
784
|
+
duration,
|
|
785
|
+
fps,
|
|
786
|
+
seed,
|
|
787
|
+
inputImage,
|
|
788
|
+
json,
|
|
789
|
+
stdout,
|
|
790
|
+
}: {
|
|
791
|
+
prompt: string
|
|
792
|
+
model: string
|
|
793
|
+
outputPath: string
|
|
794
|
+
count: number
|
|
795
|
+
aspectRatio?: string
|
|
796
|
+
resolution?: string
|
|
797
|
+
duration?: number
|
|
798
|
+
fps?: number
|
|
799
|
+
seed?: number
|
|
800
|
+
inputImage?: Uint8Array
|
|
801
|
+
json: boolean
|
|
802
|
+
stdout: boolean
|
|
803
|
+
}) {
|
|
804
|
+
const videoModel = await createVideoModel(model)
|
|
805
|
+
|
|
806
|
+
if (!stdout) {
|
|
807
|
+
console.error(pc.cyan('Generating...'))
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const result = await aiGenerateVideo({
|
|
811
|
+
model: videoModel,
|
|
812
|
+
prompt: inputImage
|
|
813
|
+
? { image: inputImage, text: prompt }
|
|
814
|
+
: prompt,
|
|
815
|
+
n: count,
|
|
816
|
+
...(aspectRatio ? { aspectRatio: aspectRatio as `${number}:${number}` } : {}),
|
|
817
|
+
...(resolution ? { resolution: resolution as `${number}x${number}` } : {}),
|
|
818
|
+
...(duration != null ? { duration } : {}),
|
|
819
|
+
...(fps != null ? { fps } : {}),
|
|
820
|
+
...(seed != null ? { seed } : {}),
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
if (stdout) {
|
|
824
|
+
const video = result.videos[0]
|
|
825
|
+
if (video) {
|
|
826
|
+
process.stdout.write(Buffer.from(video.uint8Array))
|
|
827
|
+
}
|
|
828
|
+
return
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const savedFiles: string[] = []
|
|
832
|
+
for (let i = 0; i < result.videos.length; i++) {
|
|
833
|
+
const video = result.videos[i]!
|
|
834
|
+
const ext = extensionFromMediaType(video.mediaType)
|
|
835
|
+
const filePath =
|
|
836
|
+
result.videos.length === 1
|
|
837
|
+
? ensureExtension(outputPath, ext)
|
|
838
|
+
: insertIndex(outputPath, i, ext)
|
|
839
|
+
|
|
840
|
+
fs.writeFileSync(filePath, video.uint8Array)
|
|
841
|
+
console.error(pc.green(`Saved: ${filePath}`))
|
|
842
|
+
savedFiles.push(filePath)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const config = getModelConfig(model)
|
|
846
|
+
const cost = calculateCost(config.cost, {
|
|
847
|
+
videosGenerated: result.videos.length,
|
|
848
|
+
durationSeconds: duration,
|
|
849
|
+
resolution,
|
|
850
|
+
}, result.videos.length)
|
|
851
|
+
if (cost != null) {
|
|
852
|
+
console.error(pc.dim(`Cost: ${formatCost(cost)}`))
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (json) {
|
|
856
|
+
const output = {
|
|
857
|
+
model,
|
|
858
|
+
files: savedFiles,
|
|
859
|
+
count: result.videos.length,
|
|
860
|
+
cost,
|
|
861
|
+
warnings: result.warnings,
|
|
862
|
+
responses: result.responses,
|
|
863
|
+
}
|
|
864
|
+
console.log(JSON.stringify(output, null, 2))
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
632
868
|
// ─── cost helpers ────────────────────────────────────────────────────────────
|
|
633
869
|
|
|
634
870
|
function calculateCost(
|
|
635
|
-
cost:
|
|
636
|
-
|
|
637
|
-
|
|
871
|
+
cost: {
|
|
872
|
+
type: 'per-image'
|
|
873
|
+
perImage: number
|
|
874
|
+
} | {
|
|
875
|
+
type: 'per-token'
|
|
876
|
+
inputPerM: number
|
|
877
|
+
outputPerM: number
|
|
878
|
+
} | {
|
|
879
|
+
type: 'per-video-second'
|
|
880
|
+
defaultDurationSec: number
|
|
881
|
+
tiers: Array<{ resolution?: string; costPerSecond: number }>
|
|
882
|
+
} | {
|
|
883
|
+
type: 'unknown'
|
|
884
|
+
},
|
|
885
|
+
usage: {
|
|
886
|
+
inputTokens?: number
|
|
887
|
+
outputTokens?: number
|
|
888
|
+
imagesGenerated?: number
|
|
889
|
+
videosGenerated?: number
|
|
890
|
+
durationSeconds?: number
|
|
891
|
+
resolution?: string
|
|
892
|
+
},
|
|
893
|
+
count: number = 1,
|
|
638
894
|
): number | null {
|
|
639
895
|
if (cost.type === 'per-image') {
|
|
640
|
-
return cost.perImage *
|
|
896
|
+
return cost.perImage * count
|
|
641
897
|
}
|
|
642
898
|
if (cost.type === 'per-token' && usage.inputTokens != null && usage.outputTokens != null) {
|
|
643
899
|
return (
|
|
644
900
|
(usage.inputTokens * cost.inputPerM + usage.outputTokens * cost.outputPerM) / 1_000_000
|
|
645
901
|
)
|
|
646
902
|
}
|
|
903
|
+
if (cost.type === 'per-video-second') {
|
|
904
|
+
const durationSec = usage.durationSeconds ?? cost.defaultDurationSec
|
|
905
|
+
const resolution = normalizeResolutionKey(usage.resolution)
|
|
906
|
+
const tier =
|
|
907
|
+
cost.tiers.find((t) => normalizeResolutionKey(t.resolution) === resolution) ??
|
|
908
|
+
cost.tiers[0]
|
|
909
|
+
if (!tier) {
|
|
910
|
+
return null
|
|
911
|
+
}
|
|
912
|
+
return tier.costPerSecond * durationSec * count
|
|
913
|
+
}
|
|
647
914
|
return null
|
|
648
915
|
}
|
|
649
916
|
|
|
917
|
+
function formatCatalogCost(
|
|
918
|
+
cost: {
|
|
919
|
+
type: 'per-image'
|
|
920
|
+
perImage: number
|
|
921
|
+
} | {
|
|
922
|
+
type: 'per-token'
|
|
923
|
+
inputPerM: number
|
|
924
|
+
outputPerM: number
|
|
925
|
+
} | {
|
|
926
|
+
type: 'per-video-second'
|
|
927
|
+
defaultDurationSec: number
|
|
928
|
+
tiers: Array<{ resolution?: string; mode?: string; audio?: boolean; costPerSecond: number }>
|
|
929
|
+
} | {
|
|
930
|
+
type: 'unknown'
|
|
931
|
+
},
|
|
932
|
+
): string {
|
|
933
|
+
if (cost.type === 'per-image') {
|
|
934
|
+
return `$${cost.perImage}/image`
|
|
935
|
+
}
|
|
936
|
+
if (cost.type === 'per-token') {
|
|
937
|
+
return `$${cost.inputPerM}/M input, $${cost.outputPerM}/M output`
|
|
938
|
+
}
|
|
939
|
+
if (cost.type === 'per-video-second') {
|
|
940
|
+
const tierText = cost.tiers
|
|
941
|
+
.map((t) => {
|
|
942
|
+
const parts = [
|
|
943
|
+
t.resolution,
|
|
944
|
+
t.mode ? `mode=${t.mode}` : undefined,
|
|
945
|
+
t.audio != null ? `audio=${t.audio}` : undefined,
|
|
946
|
+
].filter(Boolean)
|
|
947
|
+
return parts.length > 0
|
|
948
|
+
? `$${t.costPerSecond}/s (${parts.join(', ')})`
|
|
949
|
+
: `$${t.costPerSecond}/s`
|
|
950
|
+
})
|
|
951
|
+
.join('; ')
|
|
952
|
+
return `${tierText} (default duration ${cost.defaultDurationSec}s)`
|
|
953
|
+
}
|
|
954
|
+
return 'unknown'
|
|
955
|
+
}
|
|
956
|
+
|
|
650
957
|
function formatCost(dollars: number): string {
|
|
651
958
|
if (dollars < 0.01) {
|
|
652
959
|
return `$${dollars.toFixed(4)}`
|
|
@@ -660,8 +967,20 @@ function extensionFromMediaType(mediaType: string): string {
|
|
|
660
967
|
'image/jpeg': '.jpg',
|
|
661
968
|
'image/webp': '.webp',
|
|
662
969
|
'image/gif': '.gif',
|
|
970
|
+
'video/mp4': '.mp4',
|
|
971
|
+
'video/webm': '.webm',
|
|
972
|
+
'video/quicktime': '.mov',
|
|
663
973
|
}
|
|
664
|
-
return map[mediaType] || '.png'
|
|
974
|
+
return map[mediaType] || (mediaType.startsWith('video/') ? '.mp4' : '.png')
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function normalizeResolutionKey(input?: string): string | undefined {
|
|
978
|
+
if (!input) return undefined
|
|
979
|
+
const normalized = input.trim().toLowerCase()
|
|
980
|
+
if (normalized === '1920x1080') return '1080p'
|
|
981
|
+
if (normalized === '1280x720') return '720p'
|
|
982
|
+
if (normalized === '854x480' || normalized === '848x480') return '480p'
|
|
983
|
+
return normalized
|
|
665
984
|
}
|
|
666
985
|
|
|
667
986
|
function ensureExtension(filePath: string, ext: string): string {
|
package/src/credentials.ts
CHANGED
package/src/model-catalog.ts
CHANGED
|
@@ -94,6 +94,21 @@ const falImage = {
|
|
|
94
94
|
strategy: 'image' as const,
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const bflImage = {
|
|
98
|
+
provider: 'bfl',
|
|
99
|
+
strategy: 'image' as const,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const recraftImage = {
|
|
103
|
+
provider: 'recraft',
|
|
104
|
+
strategy: 'image' as const,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const xaiImage = {
|
|
108
|
+
provider: 'xai',
|
|
109
|
+
strategy: 'image' as const,
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
const fluxAspectRatios = ['1:1', '3:4', '4:3', '9:16', '16:9', '9:21', '21:9']
|
|
98
113
|
|
|
99
114
|
// ─── catalog ─────────────────────────────────────────────────────────────────
|
|
@@ -181,6 +196,166 @@ export const CATALOG: ModelEntry[] = [
|
|
|
181
196
|
cost: { type: 'per-token', inputPerM: 0.5, outputPerM: 3.0 },
|
|
182
197
|
},
|
|
183
198
|
|
|
199
|
+
// ── BFL (AI Gateway) ───────────────────────────────────────────────────
|
|
200
|
+
{
|
|
201
|
+
id: 'flux-kontext-max',
|
|
202
|
+
name: 'FLUX Kontext Max',
|
|
203
|
+
released: '2025-06-12',
|
|
204
|
+
...bflImage,
|
|
205
|
+
cost: { type: 'per-image', perImage: 0.08 },
|
|
206
|
+
features: {
|
|
207
|
+
editing: true,
|
|
208
|
+
inpainting: false,
|
|
209
|
+
aspectRatios: fluxAspectRatios,
|
|
210
|
+
seed: true,
|
|
211
|
+
multipleImages: false,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'flux-kontext-pro',
|
|
216
|
+
name: 'FLUX Kontext Pro',
|
|
217
|
+
released: '2025-06-12',
|
|
218
|
+
...bflImage,
|
|
219
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
220
|
+
features: {
|
|
221
|
+
editing: true,
|
|
222
|
+
inpainting: false,
|
|
223
|
+
aspectRatios: fluxAspectRatios,
|
|
224
|
+
seed: true,
|
|
225
|
+
multipleImages: false,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: 'flux-pro-1.0-fill',
|
|
230
|
+
name: 'FLUX Pro 1.0 Fill',
|
|
231
|
+
released: '2024-10-15',
|
|
232
|
+
...bflImage,
|
|
233
|
+
cost: { type: 'per-image', perImage: 0.05 },
|
|
234
|
+
features: {
|
|
235
|
+
editing: true,
|
|
236
|
+
inpainting: true,
|
|
237
|
+
aspectRatios: fluxAspectRatios,
|
|
238
|
+
seed: true,
|
|
239
|
+
multipleImages: false,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'flux-pro-1.1',
|
|
244
|
+
name: 'FLUX Pro 1.1',
|
|
245
|
+
released: '2024-10-01',
|
|
246
|
+
...bflImage,
|
|
247
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
248
|
+
features: {
|
|
249
|
+
editing: false,
|
|
250
|
+
inpainting: false,
|
|
251
|
+
aspectRatios: fluxAspectRatios,
|
|
252
|
+
seed: true,
|
|
253
|
+
multipleImages: false,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'flux-pro-1.1-ultra',
|
|
258
|
+
name: 'FLUX Pro 1.1 Ultra',
|
|
259
|
+
released: '2024-11-06',
|
|
260
|
+
...bflImage,
|
|
261
|
+
cost: { type: 'per-image', perImage: 0.06 },
|
|
262
|
+
features: {
|
|
263
|
+
editing: false,
|
|
264
|
+
inpainting: false,
|
|
265
|
+
aspectRatios: fluxAspectRatios,
|
|
266
|
+
seed: true,
|
|
267
|
+
multipleImages: false,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// ── Recraft (AI Gateway) ───────────────────────────────────────────────
|
|
272
|
+
{
|
|
273
|
+
id: 'recraft-v2',
|
|
274
|
+
name: 'Recraft v2',
|
|
275
|
+
released: '2024-08-19',
|
|
276
|
+
...recraftImage,
|
|
277
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
278
|
+
features: {
|
|
279
|
+
editing: false,
|
|
280
|
+
inpainting: false,
|
|
281
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
282
|
+
seed: false,
|
|
283
|
+
multipleImages: false,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: 'recraft-v3',
|
|
288
|
+
name: 'Recraft v3 (AI Gateway)',
|
|
289
|
+
released: '2024-10-29',
|
|
290
|
+
...recraftImage,
|
|
291
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
292
|
+
features: {
|
|
293
|
+
editing: false,
|
|
294
|
+
inpainting: false,
|
|
295
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
296
|
+
seed: false,
|
|
297
|
+
multipleImages: false,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'recraft-v4',
|
|
302
|
+
name: 'Recraft v4',
|
|
303
|
+
released: '2025-10-08',
|
|
304
|
+
...recraftImage,
|
|
305
|
+
cost: { type: 'per-image', perImage: 0.04 },
|
|
306
|
+
features: {
|
|
307
|
+
editing: false,
|
|
308
|
+
inpainting: false,
|
|
309
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
310
|
+
seed: false,
|
|
311
|
+
multipleImages: false,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'recraft-v4-pro',
|
|
316
|
+
name: 'Recraft v4 Pro',
|
|
317
|
+
released: '2025-10-08',
|
|
318
|
+
...recraftImage,
|
|
319
|
+
cost: { type: 'per-image', perImage: 0.25 },
|
|
320
|
+
features: {
|
|
321
|
+
editing: false,
|
|
322
|
+
inpainting: false,
|
|
323
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
324
|
+
seed: false,
|
|
325
|
+
multipleImages: false,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// ── xAI (AI Gateway) ────────────────────────────────────────────────────
|
|
330
|
+
{
|
|
331
|
+
id: 'grok-imagine-image',
|
|
332
|
+
name: 'Grok Imagine Image',
|
|
333
|
+
released: '2026-03',
|
|
334
|
+
...xaiImage,
|
|
335
|
+
cost: { type: 'per-image', perImage: 0.02 },
|
|
336
|
+
features: {
|
|
337
|
+
editing: true,
|
|
338
|
+
inpainting: false,
|
|
339
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
340
|
+
seed: false,
|
|
341
|
+
multipleImages: true,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: 'grok-imagine-image-pro',
|
|
346
|
+
name: 'Grok Imagine Image Pro',
|
|
347
|
+
released: '2026-03',
|
|
348
|
+
...xaiImage,
|
|
349
|
+
cost: { type: 'per-image', perImage: 0.07 },
|
|
350
|
+
features: {
|
|
351
|
+
editing: true,
|
|
352
|
+
inpainting: false,
|
|
353
|
+
aspectRatios: ['1:1', '3:4', '4:3', '9:16', '16:9'],
|
|
354
|
+
seed: false,
|
|
355
|
+
multipleImages: true,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
|
|
184
359
|
// ── OpenAI ─────────────────────────────────────────────────────────────
|
|
185
360
|
{
|
|
186
361
|
id: 'dall-e-2',
|