egaki 0.6.0 → 0.7.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 +140 -9
- package/dist/cli/cache-utils.d.ts +33 -0
- package/dist/cli/cache-utils.d.ts.map +1 -0
- package/dist/cli/cache-utils.js +157 -0
- package/dist/cli/cached-generate.d.ts +105 -0
- package/dist/cli/cached-generate.d.ts.map +1 -0
- package/dist/cli/cached-generate.js +208 -0
- package/dist/cli/cached-generate.test.d.ts +11 -0
- package/dist/cli/cached-generate.test.d.ts.map +1 -0
- package/dist/cli/cached-generate.test.js +431 -0
- package/dist/cli/cartesia-provider.d.ts +16 -0
- package/dist/cli/cartesia-provider.d.ts.map +1 -0
- package/dist/cli/cartesia-provider.js +184 -0
- package/dist/cli/chatgpt-auth.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/{cli.js → cli/cli.js} +626 -600
- package/dist/cli/clone-generate.d.ts +26 -0
- package/dist/cli/clone-generate.d.ts.map +1 -0
- package/dist/cli/clone-generate.js +148 -0
- package/dist/cli/credentials.d.ts.map +1 -0
- package/dist/{credentials.js → cli/credentials.js} +20 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
- package/dist/cli/elevenlabs-speech-provider.js +199 -0
- package/dist/cli/generate.d.ts +140 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +604 -0
- package/dist/cli/generate.test.d.ts +2 -0
- package/dist/cli/generate.test.d.ts.map +1 -0
- package/dist/cli/generate.test.js +131 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +7 -0
- package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
- package/dist/cli/model-catalog.d.ts.map +1 -0
- package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
- package/dist/{models.d.ts → cli/models.d.ts} +12 -5
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/{models.js → cli/models.js} +58 -57
- package/dist/cli/open-browser.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.d.ts +3 -0
- package/dist/cli/openai-speech-provider.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.js +28 -0
- package/dist/cli/separation-generate.d.ts +34 -0
- package/dist/cli/separation-generate.d.ts.map +1 -0
- package/dist/cli/separation-generate.js +117 -0
- package/dist/cli/speech-catalog.d.ts +35 -0
- package/dist/cli/speech-catalog.d.ts.map +1 -0
- package/dist/cli/speech-catalog.js +195 -0
- package/dist/cli/speech-generate.d.ts +86 -0
- package/dist/cli/speech-generate.d.ts.map +1 -0
- package/dist/cli/speech-generate.js +156 -0
- package/dist/cli/speech-models.d.ts +8 -0
- package/dist/cli/speech-models.d.ts.map +1 -0
- package/dist/cli/speech-models.js +109 -0
- package/dist/cli/speech-provider.test.d.ts +2 -0
- package/dist/cli/speech-provider.test.d.ts.map +1 -0
- package/dist/cli/speech-provider.test.js +197 -0
- package/dist/cli/subscription.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.d.ts +35 -0
- package/dist/cli/transcription-catalog.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.js +192 -0
- package/dist/cli/transcription-generate.d.ts +62 -0
- package/dist/cli/transcription-generate.d.ts.map +1 -0
- package/dist/cli/transcription-generate.js +144 -0
- package/dist/cli/transcription-models.d.ts +9 -0
- package/dist/cli/transcription-models.d.ts.map +1 -0
- package/dist/cli/transcription-models.js +100 -0
- package/dist/cli/transcription-providers.d.ts +22 -0
- package/dist/cli/transcription-providers.d.ts.map +1 -0
- package/dist/cli/transcription-providers.js +290 -0
- package/dist/cli/video-model-catalog.d.ts.map +1 -0
- package/dist/cli/xai-auth.d.ts.map +1 -0
- package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
- package/dist/vite/angled-screen.d.ts +57 -0
- package/dist/vite/angled-screen.d.ts.map +1 -0
- package/dist/vite/angled-screen.js +115 -0
- package/dist/vite/app.d.ts +51 -0
- package/dist/vite/app.d.ts.map +1 -0
- package/dist/vite/app.js +150 -0
- package/dist/vite/bands-shader.d.ts +47 -0
- package/dist/vite/bands-shader.d.ts.map +1 -0
- package/dist/vite/bands-shader.js +267 -0
- package/dist/vite/code-block.d.ts +107 -0
- package/dist/vite/code-block.d.ts.map +1 -0
- package/dist/vite/code-block.js +641 -0
- package/dist/vite/components.d.ts +36 -0
- package/dist/vite/components.d.ts.map +1 -0
- package/dist/vite/components.js +113 -0
- package/dist/vite/dispersion-rings-shader.d.ts +44 -0
- package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
- package/dist/vite/dispersion-rings-shader.js +282 -0
- package/dist/vite/easing-curves.d.ts +129 -0
- package/dist/vite/easing-curves.d.ts.map +1 -0
- package/dist/vite/easing-curves.js +584 -0
- package/dist/vite/easing-curves.test.d.ts +2 -0
- package/dist/vite/easing-curves.test.d.ts.map +1 -0
- package/dist/vite/easing-curves.test.js +200 -0
- package/dist/vite/generated-media-client.d.ts +35 -0
- package/dist/vite/generated-media-client.d.ts.map +1 -0
- package/dist/vite/generated-media-client.js +76 -0
- package/dist/vite/keyframes.d.ts +129 -0
- package/dist/vite/keyframes.d.ts.map +1 -0
- package/dist/vite/keyframes.js +194 -0
- package/dist/vite/layout-editor.d.ts +45 -0
- package/dist/vite/layout-editor.d.ts.map +1 -0
- package/dist/vite/layout-editor.js +587 -0
- package/dist/vite/layout-transition.d.ts +136 -0
- package/dist/vite/layout-transition.d.ts.map +1 -0
- package/dist/vite/layout-transition.js +581 -0
- package/dist/vite/liquid-gradient-shader.d.ts +62 -0
- package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
- package/dist/vite/liquid-gradient-shader.js +413 -0
- package/dist/vite/mdx-client.d.ts +15 -0
- package/dist/vite/mdx-client.d.ts.map +1 -0
- package/dist/vite/mdx-client.js +265 -0
- package/dist/vite/mdx-parse.d.ts +102 -0
- package/dist/vite/mdx-parse.d.ts.map +1 -0
- package/dist/vite/mdx-parse.js +251 -0
- package/dist/vite/mdx-provided-components.d.ts +18 -0
- package/dist/vite/mdx-provided-components.d.ts.map +1 -0
- package/dist/vite/mdx-provided-components.js +14 -0
- package/dist/vite/mdx-video.d.ts +327 -0
- package/dist/vite/mdx-video.d.ts.map +1 -0
- package/dist/vite/mdx-video.js +596 -0
- package/dist/vite/mdx-video.test.d.ts +9 -0
- package/dist/vite/mdx-video.test.d.ts.map +1 -0
- package/dist/vite/mdx-video.test.js +1945 -0
- package/dist/vite/media-components.d.ts +45 -0
- package/dist/vite/media-components.d.ts.map +1 -0
- package/dist/vite/media-components.js +355 -0
- package/dist/vite/media-duration-store.d.ts +66 -0
- package/dist/vite/media-duration-store.d.ts.map +1 -0
- package/dist/vite/media-duration-store.js +204 -0
- package/dist/vite/player-page.d.ts +35 -0
- package/dist/vite/player-page.d.ts.map +1 -0
- package/dist/vite/player-page.js +720 -0
- package/dist/vite/render-client.d.ts +18 -0
- package/dist/vite/render-client.d.ts.map +1 -0
- package/dist/vite/render-client.js +67 -0
- package/dist/vite/sdk.d.ts +221 -0
- package/dist/vite/sdk.d.ts.map +1 -0
- package/dist/vite/sdk.js +355 -0
- package/dist/vite/server-components.d.ts +55 -0
- package/dist/vite/server-components.d.ts.map +1 -0
- package/dist/vite/server-components.js +156 -0
- package/dist/vite/server-mdx.d.ts +74 -0
- package/dist/vite/server-mdx.d.ts.map +1 -0
- package/dist/vite/server-mdx.js +259 -0
- package/dist/vite/shader-renderer.d.ts +48 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -0
- package/dist/vite/shader-renderer.js +411 -0
- package/dist/vite/store-hooks.d.ts +12 -0
- package/dist/vite/store-hooks.d.ts.map +1 -0
- package/dist/vite/store-hooks.js +93 -0
- package/dist/vite/store.d.ts +75 -0
- package/dist/vite/store.d.ts.map +1 -0
- package/dist/vite/store.js +52 -0
- package/dist/vite/store.test.d.ts +6 -0
- package/dist/vite/store.test.d.ts.map +1 -0
- package/dist/vite/store.test.js +172 -0
- package/dist/vite/tweakpane-hook.d.ts +94 -0
- package/dist/vite/tweakpane-hook.d.ts.map +1 -0
- package/dist/vite/tweakpane-hook.js +450 -0
- package/dist/vite/vite-plugin.d.ts +28 -0
- package/dist/vite/vite-plugin.d.ts.map +1 -0
- package/dist/vite/vite-plugin.js +538 -0
- package/dist/vite/wave-gradient-shader.d.ts +41 -0
- package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
- package/dist/vite/wave-gradient-shader.js +207 -0
- package/package.json +101 -24
- package/skills/egaki/SKILL.md +162 -1
- package/src/cli/cache-utils.ts +169 -0
- package/src/cli/cached-generate.test.ts +531 -0
- package/src/cli/cached-generate.ts +348 -0
- package/src/cli/cartesia-provider.ts +249 -0
- package/src/{cli.ts → cli/cli.ts} +836 -884
- package/src/cli/clone-generate.ts +202 -0
- package/src/{credentials.ts → cli/credentials.ts} +20 -0
- package/src/cli/elevenlabs-speech-provider.ts +243 -0
- package/src/cli/generate.test.ts +166 -0
- package/src/cli/generate.ts +912 -0
- package/src/cli/main.ts +8 -0
- package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
- package/src/{models.ts → cli/models.ts} +65 -86
- package/src/cli/openai-speech-provider.ts +34 -0
- package/src/cli/separation-generate.ts +187 -0
- package/src/cli/speech-catalog.ts +253 -0
- package/src/cli/speech-generate.ts +275 -0
- package/src/cli/speech-models.ts +146 -0
- package/src/cli/speech-provider.test.ts +226 -0
- package/src/cli/transcription-catalog.ts +250 -0
- package/src/cli/transcription-generate.ts +214 -0
- package/src/cli/transcription-models.ts +136 -0
- package/src/cli/transcription-providers.ts +427 -0
- package/src/{xai-auth.ts → cli/xai-auth.ts} +1 -1
- package/src/vite/angled-screen.tsx +214 -0
- package/src/vite/app.tsx +197 -0
- package/src/vite/bands-shader.tsx +319 -0
- package/src/vite/code-block.tsx +939 -0
- package/src/vite/components.tsx +278 -0
- package/src/vite/dispersion-rings-shader.tsx +331 -0
- package/src/vite/easing-curves.test.ts +232 -0
- package/src/vite/easing-curves.ts +743 -0
- package/src/vite/generated-media-client.tsx +121 -0
- package/src/vite/keyframes.tsx +347 -0
- package/src/vite/layout-editor.tsx +709 -0
- package/src/vite/layout-transition.tsx +706 -0
- package/src/vite/liquid-gradient-shader.tsx +474 -0
- package/src/vite/mdx-client.tsx +420 -0
- package/src/vite/mdx-parse.ts +343 -0
- package/src/vite/mdx-provided-components.ts +19 -0
- package/src/vite/mdx-video.test.tsx +2219 -0
- package/src/vite/mdx-video.tsx +976 -0
- package/src/vite/media-components.tsx +473 -0
- package/src/vite/media-duration-store.ts +225 -0
- package/src/vite/motion-timing.ts +194 -0
- package/src/vite/player-page.tsx +1107 -0
- package/src/vite/render-client.ts +84 -0
- package/src/vite/sdk.ts +598 -0
- package/src/vite/server-components.tsx +241 -0
- package/src/vite/server-mdx.ts +285 -0
- package/src/vite/shader-renderer.tsx +535 -0
- package/src/vite/store-hooks.ts +117 -0
- package/src/vite/store.test.ts +202 -0
- package/src/vite/store.ts +115 -0
- package/src/vite/styles.css +21 -0
- package/src/vite/tweakpane-hook.tsx +593 -0
- package/src/vite/virtual-modules.d.ts +30 -0
- package/src/vite/vite-plugin.ts +584 -0
- package/src/vite/wave-gradient-shader.tsx +253 -0
- package/README.md +0 -370
- package/dist/antigravity-auth.d.ts +0 -15
- package/dist/antigravity-auth.d.ts.map +0 -1
- package/dist/antigravity-auth.js +0 -544
- package/dist/antigravity-image.d.ts +0 -11
- package/dist/antigravity-image.d.ts.map +0 -1
- package/dist/antigravity-image.js +0 -285
- package/dist/chatgpt-auth.d.ts.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.d.ts +0 -11
- package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.js +0 -64
- package/dist/login.d.ts.map +0 -1
- package/dist/model-catalog.d.ts.map +0 -1
- package/dist/models.d.ts.map +0 -1
- package/dist/open-browser.d.ts.map +0 -1
- package/dist/subscription.d.ts.map +0 -1
- package/dist/video-model-catalog.d.ts.map +0 -1
- package/dist/xai-auth.d.ts.map +0 -1
- /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
- /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
- /package/dist/{credentials.d.ts → cli/credentials.d.ts} +0 -0
- /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
- /package/dist/{login.js → cli/login.js} +0 -0
- /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
- /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
- /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
- /package/dist/{subscription.js → cli/subscription.js} +0 -0
- /package/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
- /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
- /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
- /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
- /package/src/{login.ts → cli/login.ts} +0 -0
- /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
- /package/src/{subscription.ts → cli/subscription.ts} +0 -0
- /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
|
@@ -36,6 +36,17 @@ export type { ImageModelEntry, VideoModelEntry, AnyModelEntry }
|
|
|
36
36
|
/** @deprecated Use ImageModelEntry instead */
|
|
37
37
|
export type ModelEntry = ImageModelEntry
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Validation error for user-facing input issues (wrong model, missing key, etc.).
|
|
41
|
+
* CLI prints just the message; provider/runtime errors get full stack + details.
|
|
42
|
+
*/
|
|
43
|
+
export class ValidationError extends Error {
|
|
44
|
+
constructor(message: string) {
|
|
45
|
+
super(message)
|
|
46
|
+
this.name = 'ValidationError'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export const IMAGE_MODELS = CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
40
51
|
export const VIDEO_MODELS = VIDEO_CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
41
52
|
|
|
@@ -52,11 +63,10 @@ function stripProviderPrefix(modelId: string): string {
|
|
|
52
63
|
export const DEFAULT_MODEL = 'nano-banana-pro-preview'
|
|
53
64
|
export const DEFAULT_VIDEO_MODEL = 'veo-3.1-fast-generate-001'
|
|
54
65
|
|
|
55
|
-
export function getModelConfig(modelId: string): AnyModelEntry {
|
|
66
|
+
export function getModelConfig(modelId: string): Error | AnyModelEntry {
|
|
56
67
|
const entry = findAnyModel(modelId)
|
|
57
68
|
if (!entry) {
|
|
58
|
-
|
|
59
|
-
process.exit(1)
|
|
69
|
+
return new ValidationError(`Unknown model: ${modelId}`)
|
|
60
70
|
}
|
|
61
71
|
return entry
|
|
62
72
|
}
|
|
@@ -72,9 +82,9 @@ export function getModelConfig(modelId: string): AnyModelEntry {
|
|
|
72
82
|
// That's it — no switch statements to touch.
|
|
73
83
|
|
|
74
84
|
type ProviderSdk = {
|
|
75
|
-
image?: (modelId: string) => Promise<ImageModel>
|
|
76
|
-
text?: (modelId: string) => Promise<LanguageModel>
|
|
77
|
-
video?: (modelId: string) => Promise<any>
|
|
85
|
+
image?: (modelId: string) => Promise<Error | ImageModel>
|
|
86
|
+
text?: (modelId: string) => Promise<Error | LanguageModel>
|
|
87
|
+
video?: (modelId: string) => Promise<Error | any>
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
// Vertex is the only provider that uses a prefix (vertex/model-id) in the catalog
|
|
@@ -105,10 +115,12 @@ const PROVIDER_SDKS: Record<string, ProviderSdk> = {
|
|
|
105
115
|
xai: {
|
|
106
116
|
image: async (id) => {
|
|
107
117
|
const xaiProvider = await getXaiProviderInstance()
|
|
118
|
+
if (xaiProvider instanceof Error) return xaiProvider
|
|
108
119
|
return xaiProvider.image(id)
|
|
109
120
|
},
|
|
110
121
|
video: async (id) => {
|
|
111
122
|
const xaiProvider = await getXaiProviderInstance()
|
|
123
|
+
if (xaiProvider instanceof Error) return xaiProvider
|
|
112
124
|
return xaiProvider.video(id)
|
|
113
125
|
},
|
|
114
126
|
},
|
|
@@ -122,7 +134,7 @@ const PROVIDER_SDKS: Record<string, ProviderSdk> = {
|
|
|
122
134
|
// OAuth tokens from the Grok Build subscription. OAuth tokens are injected via
|
|
123
135
|
// a custom fetch that replaces the Authorization header on every request.
|
|
124
136
|
|
|
125
|
-
async function getXaiProviderInstance() {
|
|
137
|
+
async function getXaiProviderInstance(): Promise<Error | ReturnType<Awaited<typeof import('@ai-sdk/xai')>['createXai']>> {
|
|
126
138
|
const { createXai } = await import('@ai-sdk/xai')
|
|
127
139
|
|
|
128
140
|
// Direct API key path (env or stored)
|
|
@@ -135,14 +147,12 @@ async function getXaiProviderInstance() {
|
|
|
135
147
|
// OAuth path: inject bearer token via custom fetch
|
|
136
148
|
const storedAuth = getXaiAuth()
|
|
137
149
|
if (!storedAuth) {
|
|
138
|
-
|
|
139
|
-
process.exit(1)
|
|
150
|
+
return new ValidationError('Missing xAI OAuth tokens. Please run `egaki login` and select xAI Grok Build.')
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
const auth = await getValidXaiAuth(storedAuth, saveXaiAuth)
|
|
143
154
|
if (auth instanceof Error) {
|
|
144
|
-
|
|
145
|
-
process.exit(1)
|
|
155
|
+
return auth
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
return createXai({
|
|
@@ -179,54 +189,29 @@ function hasDirectProviderKey(providerName: string): boolean {
|
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
// Check that the provider's API key or OAuth login is available before making API calls.
|
|
182
|
-
//
|
|
183
|
-
export function ensureProviderKey(providerName: string):
|
|
184
|
-
if (hasDirectProviderKey(providerName)) return
|
|
192
|
+
// Returns an Error with a user-friendly message if no key is found.
|
|
193
|
+
export function ensureProviderKey(providerName: string): Error | undefined {
|
|
194
|
+
if (hasDirectProviderKey(providerName)) return undefined
|
|
185
195
|
|
|
186
196
|
// Vertex models require a direct key — the upstream Vercel AI Gateway
|
|
187
197
|
// does not support vertex/ routing, so egaki gateway can't proxy them.
|
|
188
198
|
if (providerName === 'vertex') {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
console.error(pc.dim('Get a key at https://console.cloud.google.com/apis/credentials'))
|
|
195
|
-
console.error(pc.dim('Egaki subscription does not cover Vertex routing yet.'))
|
|
196
|
-
console.error('')
|
|
197
|
-
process.exit(1)
|
|
199
|
+
return new ValidationError(
|
|
200
|
+
'Vertex models require a direct Google Cloud API key. ' +
|
|
201
|
+
'Run: egaki login --provider vertex --key <key>. ' +
|
|
202
|
+
'Get a key at https://console.cloud.google.com/apis/credentials',
|
|
203
|
+
)
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
if (hasEgakiKey()) return
|
|
206
|
+
if (hasEgakiKey()) return undefined
|
|
201
207
|
|
|
202
208
|
const info = PROVIDERS[providerName]
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.error('')
|
|
209
|
-
console.error(
|
|
210
|
-
` ${pc.cyan('egaki subscribe')} get started in 30 seconds`,
|
|
211
|
-
)
|
|
212
|
-
console.error('')
|
|
213
|
-
console.error(` ${pc.dim('Or configure a provider key directly:')}`)
|
|
214
|
-
console.error('')
|
|
215
|
-
console.error(
|
|
216
|
-
` ${pc.cyan('egaki login')} interactive setup`,
|
|
209
|
+
const label = info?.label || providerName
|
|
210
|
+
return new ValidationError(
|
|
211
|
+
`Missing API key for ${label}. ` +
|
|
212
|
+
'Use an Egaki subscription (egaki subscribe) or configure a provider key directly (egaki login).' +
|
|
213
|
+
(info ? ` ${info.hint}` : ''),
|
|
217
214
|
)
|
|
218
|
-
if (info) {
|
|
219
|
-
console.error(
|
|
220
|
-
` ${pc.cyan(`${info.envVar}=...`)} egaki image ... inline env var`,
|
|
221
|
-
)
|
|
222
|
-
console.error(
|
|
223
|
-
` ${pc.cyan(`egaki login --provider ${providerName} --key <key>`)}`,
|
|
224
|
-
)
|
|
225
|
-
console.error('')
|
|
226
|
-
console.error(` ${pc.dim(info.hint)}`)
|
|
227
|
-
}
|
|
228
|
-
console.error('')
|
|
229
|
-
process.exit(1)
|
|
230
215
|
}
|
|
231
216
|
|
|
232
217
|
// ─── gateway model factories ─────────────────────────────────────────────────
|
|
@@ -335,9 +320,12 @@ function logAuthSource(source: AuthSource): void {
|
|
|
335
320
|
// Priority for others: direct provider key > egaki gateway > error.
|
|
336
321
|
// Uses PROVIDER_SDKS map for direct provider creation, eliminating switch blocks.
|
|
337
322
|
|
|
338
|
-
export async function createImageModel(modelId: string): Promise<ImageModel> {
|
|
323
|
+
export async function createImageModel(modelId: string): Promise<Error | ImageModel> {
|
|
339
324
|
const config = getModelConfig(modelId)
|
|
340
|
-
|
|
325
|
+
if (config instanceof Error) return config
|
|
326
|
+
|
|
327
|
+
const keyError = ensureProviderKey(config.provider)
|
|
328
|
+
if (keyError) return keyError
|
|
341
329
|
|
|
342
330
|
const authSource = resolveAuthSource(config.provider)
|
|
343
331
|
logAuthSource(authSource)
|
|
@@ -345,31 +333,30 @@ export async function createImageModel(modelId: string): Promise<ImageModel> {
|
|
|
345
333
|
if (hasDirectProviderKey(config.provider)) {
|
|
346
334
|
const factory = PROVIDER_SDKS[config.provider]?.image
|
|
347
335
|
if (factory) {
|
|
348
|
-
|
|
336
|
+
const result = await factory(modelId)
|
|
337
|
+
if (result instanceof Error) return result
|
|
338
|
+
return result
|
|
349
339
|
}
|
|
350
|
-
|
|
351
|
-
pc.red(
|
|
352
|
-
`Direct image generation is not supported for provider: ${config.provider}.`,
|
|
353
|
-
),
|
|
354
|
-
)
|
|
355
|
-
process.exit(1)
|
|
340
|
+
return new ValidationError(`Direct image generation is not supported for provider: ${config.provider}.`)
|
|
356
341
|
}
|
|
357
342
|
|
|
358
343
|
if (hasEgakiKey()) {
|
|
359
344
|
return createGatewayImageModel(modelId, config.provider)
|
|
360
345
|
}
|
|
361
346
|
|
|
362
|
-
|
|
363
|
-
process.exit(1)
|
|
347
|
+
return new ValidationError(`No API key available for provider: ${config.provider}`)
|
|
364
348
|
}
|
|
365
349
|
|
|
366
|
-
export async function createTextModel(modelId: string): Promise<LanguageModel> {
|
|
350
|
+
export async function createTextModel(modelId: string): Promise<Error | LanguageModel> {
|
|
367
351
|
const config = getModelConfig(modelId)
|
|
352
|
+
if (config instanceof Error) return config
|
|
353
|
+
|
|
368
354
|
if (config.strategy !== 'text') {
|
|
369
|
-
|
|
370
|
-
process.exit(1)
|
|
355
|
+
return new ValidationError(`Model ${modelId} is not a text model`)
|
|
371
356
|
}
|
|
372
|
-
|
|
357
|
+
|
|
358
|
+
const keyError = ensureProviderKey(config.provider)
|
|
359
|
+
if (keyError) return keyError
|
|
373
360
|
|
|
374
361
|
const authSource = resolveAuthSource(config.provider)
|
|
375
362
|
logAuthSource(authSource)
|
|
@@ -379,30 +366,26 @@ export async function createTextModel(modelId: string): Promise<LanguageModel> {
|
|
|
379
366
|
if (factory) {
|
|
380
367
|
return factory(modelId)
|
|
381
368
|
}
|
|
382
|
-
|
|
383
|
-
pc.red(
|
|
384
|
-
`Text+image generation is not supported for provider: ${config.provider}`,
|
|
385
|
-
),
|
|
386
|
-
)
|
|
387
|
-
process.exit(1)
|
|
369
|
+
return new ValidationError(`Text+image generation is not supported for provider: ${config.provider}`)
|
|
388
370
|
}
|
|
389
371
|
|
|
390
372
|
if (hasEgakiKey()) {
|
|
391
373
|
return createGatewayTextModel(modelId, config.provider)
|
|
392
374
|
}
|
|
393
375
|
|
|
394
|
-
|
|
395
|
-
process.exit(1)
|
|
376
|
+
return new ValidationError(`No API key available for provider: ${config.provider}`)
|
|
396
377
|
}
|
|
397
378
|
|
|
398
|
-
export async function createVideoModel(modelId: string): Promise<any> {
|
|
379
|
+
export async function createVideoModel(modelId: string): Promise<Error | any> {
|
|
399
380
|
const config = getModelConfig(modelId)
|
|
381
|
+
if (config instanceof Error) return config
|
|
382
|
+
|
|
400
383
|
if (config.strategy !== 'video') {
|
|
401
|
-
|
|
402
|
-
process.exit(1)
|
|
384
|
+
return new ValidationError(`Model ${modelId} is not a video model`)
|
|
403
385
|
}
|
|
404
386
|
|
|
405
|
-
ensureProviderKey(config.provider)
|
|
387
|
+
const keyError = ensureProviderKey(config.provider)
|
|
388
|
+
if (keyError) return keyError
|
|
406
389
|
|
|
407
390
|
const authSource = resolveAuthSource(config.provider)
|
|
408
391
|
logAuthSource(authSource)
|
|
@@ -410,20 +393,16 @@ export async function createVideoModel(modelId: string): Promise<any> {
|
|
|
410
393
|
if (hasDirectProviderKey(config.provider)) {
|
|
411
394
|
const factory = PROVIDER_SDKS[config.provider]?.video
|
|
412
395
|
if (factory) {
|
|
413
|
-
|
|
396
|
+
const result = await factory(modelId)
|
|
397
|
+
if (result instanceof Error) return result
|
|
398
|
+
return result
|
|
414
399
|
}
|
|
415
|
-
|
|
416
|
-
pc.red(
|
|
417
|
-
`Direct video generation is not supported for provider: ${config.provider}. Use an egaki subscription instead.`,
|
|
418
|
-
),
|
|
419
|
-
)
|
|
420
|
-
process.exit(1)
|
|
400
|
+
return new ValidationError(`Direct video generation is not supported for provider: ${config.provider}. Use an egaki subscription instead.`)
|
|
421
401
|
}
|
|
422
402
|
|
|
423
403
|
if (hasEgakiKey()) {
|
|
424
404
|
return createGatewayVideoModel(modelId, config.provider)
|
|
425
405
|
}
|
|
426
406
|
|
|
427
|
-
|
|
428
|
-
process.exit(1)
|
|
407
|
+
return new ValidationError(`No API key available for provider: ${config.provider}`)
|
|
429
408
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// OpenAI speech provider for egaki.
|
|
2
|
+
// Wraps the Vercel AI SDK @ai-sdk/openai since OpenAI TTS has no
|
|
3
|
+
// timestamp support. Returns audio only, timestamps are always undefined.
|
|
4
|
+
import {
|
|
5
|
+
experimental_generateSpeech as aiGenerateSpeech,
|
|
6
|
+
} from 'ai'
|
|
7
|
+
import type { SpeechProvider, SpeechProviderOptions, SpeechProviderResult } from './speech-generate.js'
|
|
8
|
+
|
|
9
|
+
export function createOpenAISpeechProvider(): SpeechProvider {
|
|
10
|
+
return {
|
|
11
|
+
async generate(options: SpeechProviderOptions): Promise<SpeechProviderResult> {
|
|
12
|
+
const { openai } = await import('@ai-sdk/openai')
|
|
13
|
+
const model = openai.speech(options.modelId)
|
|
14
|
+
|
|
15
|
+
const result = await aiGenerateSpeech({
|
|
16
|
+
model,
|
|
17
|
+
text: options.text,
|
|
18
|
+
...(options.voice ? { voice: options.voice } : {}),
|
|
19
|
+
...(options.outputFormat ? { outputFormat: options.outputFormat } : {}),
|
|
20
|
+
...(options.instructions ? { instructions: options.instructions } : {}),
|
|
21
|
+
...(options.speed != null ? { speed: options.speed } : {}),
|
|
22
|
+
...(options.language ? { language: options.language } : {}),
|
|
23
|
+
...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
audio: result.audio.uint8Array,
|
|
28
|
+
mediaType: result.audio.mediaType || 'audio/mpeg',
|
|
29
|
+
// OpenAI TTS does not support word timestamps
|
|
30
|
+
timestamps: undefined,
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Audio separation using fal.ai's Demucs API.
|
|
2
|
+
// Splits audio into stems (vocals, drums, bass, guitar, piano, other).
|
|
3
|
+
// Returns Error | Result (errore style) instead of throwing.
|
|
4
|
+
//
|
|
5
|
+
// Uses @fal-ai/client directly since Demucs is a fal-specific API
|
|
6
|
+
// with no Vercel AI SDK wrapper. Requires FAL_KEY env var or
|
|
7
|
+
// egaki login --provider fal.
|
|
8
|
+
import { fal } from '@fal-ai/client'
|
|
9
|
+
import { injectCredentialsToEnv } from './credentials.js'
|
|
10
|
+
|
|
11
|
+
// ─── types ──────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type StemName = 'vocals' | 'drums' | 'bass' | 'other' | 'guitar' | 'piano'
|
|
14
|
+
|
|
15
|
+
export type DemucsModel =
|
|
16
|
+
| 'htdemucs'
|
|
17
|
+
| 'htdemucs_ft'
|
|
18
|
+
| 'htdemucs_6s'
|
|
19
|
+
| 'hdemucs_mmi'
|
|
20
|
+
| 'mdx'
|
|
21
|
+
| 'mdx_extra'
|
|
22
|
+
| 'mdx_q'
|
|
23
|
+
| 'mdx_extra_q'
|
|
24
|
+
|
|
25
|
+
export interface SeparateAudioOptions {
|
|
26
|
+
/** Raw audio bytes to separate. */
|
|
27
|
+
audio: Uint8Array
|
|
28
|
+
/** Which stems to extract. Default: ['vocals', 'other'] */
|
|
29
|
+
stems?: StemName[]
|
|
30
|
+
/** Demucs model variant. Default: 'htdemucs_6s' */
|
|
31
|
+
model?: DemucsModel
|
|
32
|
+
/** Output format: 'mp3' or 'wav'. Default: 'mp3' */
|
|
33
|
+
outputFormat?: 'mp3' | 'wav'
|
|
34
|
+
/** Number of random shifts for quality (higher = better but slower). Default: 1 */
|
|
35
|
+
shifts?: number
|
|
36
|
+
/** Segment overlap 0.0-1.0 (higher = better quality). Default: 0.25 */
|
|
37
|
+
overlap?: number
|
|
38
|
+
/** Original filename for display/logging. */
|
|
39
|
+
filename?: string
|
|
40
|
+
/** Progress callback for queue updates. */
|
|
41
|
+
onProgress?: (message: string) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface StemResult {
|
|
45
|
+
name: StemName
|
|
46
|
+
url: string
|
|
47
|
+
contentType: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SeparateAudioResult {
|
|
51
|
+
stems: StemResult[]
|
|
52
|
+
model: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── validation ─────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const ALL_STEMS: StemName[] = ['vocals', 'drums', 'bass', 'other', 'guitar', 'piano']
|
|
58
|
+
const FOUR_STEM_MODELS: DemucsModel[] = ['htdemucs', 'htdemucs_ft', 'hdemucs_mmi', 'mdx', 'mdx_extra', 'mdx_q', 'mdx_extra_q']
|
|
59
|
+
const SIX_STEM_ONLY: StemName[] = ['guitar', 'piano']
|
|
60
|
+
|
|
61
|
+
export function validateStems(stems: StemName[], model: DemucsModel): Error | void {
|
|
62
|
+
for (const s of stems) {
|
|
63
|
+
if (!ALL_STEMS.includes(s)) {
|
|
64
|
+
return new Error(`Unknown stem: "${s}". Valid stems: ${ALL_STEMS.join(', ')}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (FOUR_STEM_MODELS.includes(model)) {
|
|
68
|
+
const invalid = stems.filter((s) => SIX_STEM_ONLY.includes(s))
|
|
69
|
+
if (invalid.length > 0) {
|
|
70
|
+
return new Error(
|
|
71
|
+
`Stems ${invalid.join(', ')} are only available with htdemucs_6s model. ` +
|
|
72
|
+
`Current model: ${model}`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── main function ──────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export async function separateAudioUncached(
|
|
81
|
+
opts: SeparateAudioOptions,
|
|
82
|
+
): Promise<Error | SeparateAudioResult> {
|
|
83
|
+
injectCredentialsToEnv()
|
|
84
|
+
|
|
85
|
+
const falKey = process.env['FAL_KEY']
|
|
86
|
+
if (!falKey) {
|
|
87
|
+
return new Error(
|
|
88
|
+
'FAL_KEY not found. Run: egaki login --provider fal\n' +
|
|
89
|
+
'Get your key at https://fal.ai/dashboard/keys',
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fal.config({ credentials: falKey })
|
|
94
|
+
|
|
95
|
+
const model: DemucsModel = opts.model ?? 'htdemucs_6s'
|
|
96
|
+
const stems: StemName[] = opts.stems ?? ['vocals', 'other']
|
|
97
|
+
const outputFormat = opts.outputFormat ?? 'mp3'
|
|
98
|
+
|
|
99
|
+
const validationError = validateStems(stems, model)
|
|
100
|
+
if (validationError) return validationError
|
|
101
|
+
|
|
102
|
+
if (opts.audio.length === 0) {
|
|
103
|
+
return new Error('No audio data provided')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Upload the audio file to fal storage so the API can access it
|
|
107
|
+
const file = new File(
|
|
108
|
+
[Buffer.from(opts.audio)],
|
|
109
|
+
opts.filename ?? 'audio.mp3',
|
|
110
|
+
{ type: 'audio/mpeg' },
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
opts.onProgress?.('Uploading audio to fal.ai...')
|
|
114
|
+
let audioUrl: string
|
|
115
|
+
try {
|
|
116
|
+
audioUrl = await fal.storage.upload(file)
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return new Error(`Failed to upload audio: ${err instanceof Error ? err.message : String(err)}`)
|
|
119
|
+
}
|
|
120
|
+
opts.onProgress?.('Audio uploaded. Starting separation...')
|
|
121
|
+
|
|
122
|
+
// Call the Demucs API
|
|
123
|
+
type DemucsOutput = Record<string, { url: string; content_type: string }>
|
|
124
|
+
let result: { data: DemucsOutput }
|
|
125
|
+
try {
|
|
126
|
+
result = await fal.subscribe('fal-ai/demucs', {
|
|
127
|
+
input: {
|
|
128
|
+
audio_url: audioUrl,
|
|
129
|
+
model,
|
|
130
|
+
stems,
|
|
131
|
+
shifts: opts.shifts ?? 1,
|
|
132
|
+
overlap: opts.overlap ?? 0.25,
|
|
133
|
+
output_format: outputFormat,
|
|
134
|
+
},
|
|
135
|
+
logs: true,
|
|
136
|
+
onQueueUpdate: (update) => {
|
|
137
|
+
if (update.status === 'IN_PROGRESS') {
|
|
138
|
+
const u = update as typeof update & { logs?: Array<{ message: string }> }
|
|
139
|
+
if (u.logs) {
|
|
140
|
+
for (const log of u.logs) {
|
|
141
|
+
opts.onProgress?.(log.message)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}) as any
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return new Error(`Separation failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Extract the requested stems from the result
|
|
152
|
+
const resultStems: StemResult[] = []
|
|
153
|
+
for (const stemName of stems) {
|
|
154
|
+
const stemData = result.data[stemName]
|
|
155
|
+
if (stemData?.url) {
|
|
156
|
+
resultStems.push({
|
|
157
|
+
name: stemName,
|
|
158
|
+
url: stemData.url,
|
|
159
|
+
contentType: stemData.content_type ?? `audio/${outputFormat}`,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (resultStems.length === 0) {
|
|
165
|
+
return new Error('No stems returned from the API. Check model and stem names.')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
stems: resultStems,
|
|
170
|
+
model,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── download helper ────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
/** Download a stem from its URL and return the bytes. */
|
|
177
|
+
export async function downloadStem(url: string): Promise<Error | Uint8Array> {
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(url)
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
return new Error(`Failed to download stem: HTTP ${response.status}`)
|
|
182
|
+
}
|
|
183
|
+
return new Uint8Array(await response.arrayBuffer())
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return new Error(`Failed to download stem: ${err instanceof Error ? err.message : String(err)}`)
|
|
186
|
+
}
|
|
187
|
+
}
|