varg.ai-sdk 0.1.0 → 0.4.0-alpha.1

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.
Files changed (236) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +48 -8
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +63 -90
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -227
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -493
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -112
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -187
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -135
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -201
  217. package/index.ts +0 -38
  218. package/lib/README.md +0 -144
  219. package/lib/ai-sdk/fal.ts +0 -106
  220. package/lib/ai-sdk/replicate.ts +0 -107
  221. package/lib/elevenlabs.ts +0 -382
  222. package/lib/fal.ts +0 -478
  223. package/lib/ffmpeg.ts +0 -467
  224. package/lib/fireworks.ts +0 -235
  225. package/lib/groq.ts +0 -246
  226. package/lib/higgsfield.ts +0 -176
  227. package/lib/remotion/SKILL.md +0 -823
  228. package/lib/remotion/cli.ts +0 -115
  229. package/lib/remotion/functions.ts +0 -283
  230. package/lib/remotion/index.ts +0 -19
  231. package/lib/remotion/templates.ts +0 -73
  232. package/lib/replicate.ts +0 -304
  233. package/output.txt +0 -1
  234. package/test-import.ts +0 -7
  235. package/test-services.ts +0 -97
  236. package/utilities/s3.ts +0 -147
@@ -0,0 +1,71 @@
1
+ import { defineCommand } from "citty";
2
+ import { render } from "../../react/render";
3
+ import type { VargElement } from "../../react/types";
4
+
5
+ export const renderCmd = defineCommand({
6
+ meta: {
7
+ name: "render",
8
+ description: "render a react component to video",
9
+ },
10
+ args: {
11
+ file: {
12
+ type: "positional",
13
+ description: "component file (.tsx)",
14
+ required: true,
15
+ },
16
+ output: {
17
+ type: "string",
18
+ alias: "o",
19
+ description: "output path",
20
+ },
21
+ cache: {
22
+ type: "string",
23
+ alias: "c",
24
+ description: "cache directory",
25
+ default: ".cache/ai",
26
+ },
27
+ quiet: {
28
+ type: "boolean",
29
+ alias: "q",
30
+ description: "minimal output",
31
+ default: false,
32
+ },
33
+ },
34
+ async run({ args }) {
35
+ const file = args.file as string;
36
+
37
+ if (!file) {
38
+ console.error("usage: varg render <component.tsx> [-o output.mp4]");
39
+ process.exit(1);
40
+ }
41
+
42
+ const resolvedPath = Bun.resolveSync(file, process.cwd());
43
+ const mod = await import(resolvedPath);
44
+ const component: VargElement = mod.default;
45
+
46
+ if (!component || component.type !== "render") {
47
+ console.error("error: default export must be a <Render> element");
48
+ process.exit(1);
49
+ }
50
+
51
+ const outputPath =
52
+ args.output ??
53
+ `output/${file
54
+ .replace(/\.tsx?$/, "")
55
+ .split("/")
56
+ .pop()}.mp4`;
57
+
58
+ if (!args.quiet) {
59
+ console.log(`rendering ${file} → ${outputPath}`);
60
+ }
61
+
62
+ const buffer = await render(component, {
63
+ output: outputPath,
64
+ cache: args.cache,
65
+ });
66
+
67
+ if (!args.quiet) {
68
+ console.log(`done! ${buffer.byteLength} bytes → ${outputPath}`);
69
+ }
70
+ },
71
+ });
@@ -0,0 +1,511 @@
1
+ /**
2
+ * varg run command
3
+ * Ink-based execution with live status updates
4
+ */
5
+
6
+ import { existsSync } from "node:fs";
7
+ import { defineCommand } from "citty";
8
+ import { Box, Text } from "ink";
9
+ import { executor } from "../../core/executor/index.ts";
10
+ import { resolve } from "../../core/registry/resolver.ts";
11
+ import {
12
+ coerceCliValue,
13
+ getCliSchemaInfo,
14
+ toJsonSchema,
15
+ } from "../../core/schema/helpers.ts";
16
+ import type { Definition } from "../../core/schema/types.ts";
17
+ import {
18
+ Header,
19
+ OptionRow,
20
+ StatusBox,
21
+ VargBox,
22
+ VargText,
23
+ } from "../ui/index.ts";
24
+ import { renderLive, renderStatic } from "../ui/render.ts";
25
+ import { theme } from "../ui/theme.ts";
26
+
27
+ /**
28
+ * Extract detailed error message from provider errors
29
+ * Handles fal.ai ApiError, Replicate errors, and generic errors
30
+ */
31
+ function extractErrorMessage(err: unknown): string {
32
+ if (!(err instanceof Error)) {
33
+ return String(err);
34
+ }
35
+
36
+ // Check for fal.ai ApiError with body details
37
+ const apiError = err as Error & {
38
+ body?: { detail?: string | Array<{ msg: string; loc: string[] }> };
39
+ status?: number;
40
+ };
41
+
42
+ if (apiError.body?.detail) {
43
+ // Handle validation errors (array of field errors)
44
+ if (Array.isArray(apiError.body.detail)) {
45
+ return apiError.body.detail
46
+ .map((e) => `${e.loc?.join(".") || "field"}: ${e.msg}`)
47
+ .join("; ");
48
+ }
49
+ // Handle string detail
50
+ if (typeof apiError.body.detail === "string") {
51
+ return apiError.body.detail;
52
+ }
53
+ }
54
+
55
+ // Check for body as string or with message
56
+ if (apiError.body) {
57
+ const body = apiError.body as Record<string, unknown>;
58
+ if (body.message && typeof body.message === "string") {
59
+ return body.message;
60
+ }
61
+ if (body.error && typeof body.error === "string") {
62
+ return body.error;
63
+ }
64
+ }
65
+
66
+ // Add status code context if available
67
+ if (apiError.status && apiError.message) {
68
+ return `${apiError.message} (${apiError.status})`;
69
+ }
70
+
71
+ return err.message;
72
+ }
73
+
74
+ function sanitizeOutput(_key: string, value: unknown): unknown {
75
+ if (value instanceof Buffer) {
76
+ return `<Buffer ${value.length} bytes>`;
77
+ }
78
+ if (
79
+ value &&
80
+ typeof value === "object" &&
81
+ "type" in value &&
82
+ (value as { type: string }).type === "Buffer" &&
83
+ "data" in value
84
+ ) {
85
+ const data = (value as { data: unknown[] }).data;
86
+ return `<Buffer ${Array.isArray(data) ? data.length : "?"} bytes>`;
87
+ }
88
+ return value;
89
+ }
90
+
91
+ interface RunOptions {
92
+ [key: string]: string | boolean | undefined;
93
+ help?: boolean;
94
+ schema?: boolean;
95
+ json?: boolean;
96
+ quiet?: boolean;
97
+ provider?: string;
98
+ }
99
+
100
+ function parseArgs(args: string[]): { target: string; options: RunOptions } {
101
+ const options: RunOptions = {};
102
+ let target = "";
103
+
104
+ for (let i = 0; i < args.length; i++) {
105
+ const arg = args[i];
106
+ if (!arg) continue;
107
+
108
+ if (arg === "--help" || arg === "-h") {
109
+ options.help = true;
110
+ } else if (arg.startsWith("--")) {
111
+ const key = arg.slice(2);
112
+ if (["schema", "json", "quiet"].includes(key)) {
113
+ options[key] = true;
114
+ } else {
115
+ const value = args[++i];
116
+ if (value) options[key] = value;
117
+ }
118
+ } else if (!target) {
119
+ target = arg;
120
+ } else {
121
+ // Positional args - check if it looks like a file
122
+ if (existsSync(arg) || arg.startsWith("./") || arg.startsWith("/")) {
123
+ if (!options.image && !options.audio) {
124
+ options.image = arg;
125
+ }
126
+ } else if (!options.prompt && !options.text) {
127
+ options.prompt = arg;
128
+ }
129
+ }
130
+ }
131
+
132
+ return { target, options };
133
+ }
134
+
135
+ interface HelpViewProps {
136
+ item: Definition;
137
+ }
138
+
139
+ function HelpView({ item }: HelpViewProps) {
140
+ const { properties, required } = getCliSchemaInfo(item.schema.input);
141
+ // Only show truly required args (no default value)
142
+ const trulyRequired = required.filter(
143
+ (r) => properties[r]?.default === undefined,
144
+ );
145
+ const reqArgs = trulyRequired.map((r) => `--${r} <${r}>`).join(" ");
146
+
147
+ return (
148
+ <VargBox title={`${item.type}: ${item.name}`}>
149
+ <Box marginBottom={1}>
150
+ <Text wrap="wrap">{item.description}</Text>
151
+ </Box>
152
+
153
+ <Header>USAGE</Header>
154
+ <Box paddingLeft={2} marginBottom={1}>
155
+ <VargText variant="accent">
156
+ varg run {item.name} {reqArgs} [options]
157
+ </VargText>
158
+ </Box>
159
+
160
+ <Header>OPTIONS</Header>
161
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
162
+ {Object.entries(properties).map(([key, prop]) => {
163
+ const hasDefault = prop.default !== undefined;
164
+ return (
165
+ <OptionRow
166
+ key={key}
167
+ name={key}
168
+ description={prop.description}
169
+ required={required.includes(key) && !hasDefault}
170
+ defaultValue={prop.default}
171
+ enumValues={prop.enum}
172
+ type={prop.type}
173
+ />
174
+ );
175
+ })}
176
+ </Box>
177
+
178
+ {item.type === "model" && (
179
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
180
+ <OptionRow name="provider" description="override default provider" />
181
+ <Box paddingLeft={theme.layout.optionNameWidth}>
182
+ <Text dimColor>available: {item.providers.join(", ")}</Text>
183
+ </Box>
184
+ </Box>
185
+ )}
186
+
187
+ <Header>GLOBAL OPTIONS</Header>
188
+ <Box flexDirection="column" paddingLeft={2}>
189
+ <OptionRow name="json" description="output result as json" />
190
+ <OptionRow name="quiet" description="minimal output" />
191
+ <OptionRow name="help, -h" description="show this help" />
192
+ </Box>
193
+ </VargBox>
194
+ );
195
+ }
196
+
197
+ function ErrorView({ message, hint }: { message: string; hint?: string }) {
198
+ return (
199
+ <Box flexDirection="column" padding={1}>
200
+ <VargText variant="error">error: {message}</VargText>
201
+ {hint && (
202
+ <Box marginTop={1}>
203
+ <VargText variant="muted">run </VargText>
204
+ <VargText variant="accent">{hint}</VargText>
205
+ <VargText variant="muted"> for help</VargText>
206
+ </Box>
207
+ )}
208
+ </Box>
209
+ );
210
+ }
211
+
212
+ /** Help view for run command without target */
213
+ function RunHelpView() {
214
+ return (
215
+ <VargBox title="varg run">
216
+ <Box marginBottom={1}>
217
+ <Text>run a model, action, or skill</Text>
218
+ </Box>
219
+
220
+ <Header>USAGE</Header>
221
+ <Box paddingLeft={2} marginBottom={1}>
222
+ <VargText variant="accent">varg run {"<target>"} [--options]</VargText>
223
+ </Box>
224
+
225
+ <Header>OPTIONS</Header>
226
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
227
+ <Text>--json output result as json</Text>
228
+ <Text>--quiet minimal output</Text>
229
+ <Text>--schema show target schema as json</Text>
230
+ <Text>--provider override default provider (models only)</Text>
231
+ <Text>--help, -h show this help</Text>
232
+ </Box>
233
+
234
+ <Header>EXAMPLES</Header>
235
+ <Box flexDirection="column" paddingLeft={2}>
236
+ <Box flexDirection="column" marginBottom={1}>
237
+ <Text dimColor># generate a video from text</Text>
238
+ <VargText variant="accent">
239
+ varg run video --prompt "ocean waves"
240
+ </VargText>
241
+ </Box>
242
+ <Box flexDirection="column" marginBottom={1}>
243
+ <Text dimColor># get help for a specific target</Text>
244
+ <VargText variant="accent">varg run video --help</VargText>
245
+ </Box>
246
+ <Box flexDirection="column">
247
+ <Text dimColor># see available targets</Text>
248
+ <VargText variant="accent">varg list</VargText>
249
+ </Box>
250
+ </Box>
251
+ </VargBox>
252
+ );
253
+ }
254
+
255
+ /** Show run command help */
256
+ export function showRunHelp() {
257
+ renderStatic(<RunHelpView />);
258
+ }
259
+
260
+ /** Show target-specific help */
261
+ export function showTargetHelp(target: string): boolean {
262
+ const result = resolve(target, { fuzzy: true });
263
+ if (result.definition) {
264
+ renderStatic(<HelpView item={result.definition} />);
265
+ return true;
266
+ }
267
+ return false;
268
+ }
269
+
270
+ export const runCmd = defineCommand({
271
+ meta: {
272
+ name: "run",
273
+ description: "run a model, action, or skill",
274
+ },
275
+ args: {
276
+ target: {
277
+ type: "positional",
278
+ description: "what to run",
279
+ required: false,
280
+ },
281
+ schema: { type: "boolean", description: "show schema as json" },
282
+ json: { type: "boolean", description: "output result as json" },
283
+ quiet: { type: "boolean", description: "minimal output" },
284
+ },
285
+ async run({ rawArgs }) {
286
+ const { target, options } = parseArgs(rawArgs);
287
+
288
+ // Show run help when no target or --help without target
289
+ if (!target || (options.help && !target)) {
290
+ showRunHelp();
291
+ return;
292
+ }
293
+
294
+ // Resolve the target
295
+ const result = resolve(target, { fuzzy: true });
296
+
297
+ if (!result.definition) {
298
+ renderStatic(
299
+ <Box flexDirection="column" padding={1}>
300
+ <VargText variant="error">error: '{target}' not found</VargText>
301
+ {result.suggestions && result.suggestions.length > 0 && (
302
+ <Box marginTop={1}>
303
+ <Text>
304
+ did you mean: {result.suggestions.slice(0, 3).join(", ")}?
305
+ </Text>
306
+ </Box>
307
+ )}
308
+ <Box marginTop={1}>
309
+ <VargText variant="muted">run </VargText>
310
+ <VargText variant="accent">varg list</VargText>
311
+ <VargText variant="muted"> to see available targets</VargText>
312
+ </Box>
313
+ </Box>,
314
+ );
315
+ process.exit(1);
316
+ }
317
+
318
+ const item = result.definition;
319
+
320
+ // Show target-specific help
321
+ if (options.help) {
322
+ renderStatic(<HelpView item={item} />);
323
+ return;
324
+ }
325
+
326
+ if (options.schema) {
327
+ const schema = {
328
+ name: item.name,
329
+ type: item.type,
330
+ description: item.description,
331
+ input: toJsonSchema(item.schema.input),
332
+ output: toJsonSchema(item.schema.output),
333
+ };
334
+ console.log(JSON.stringify(schema, null, 2));
335
+ return;
336
+ }
337
+
338
+ // Get schema info for validation
339
+ const { properties, required } = getCliSchemaInfo(item.schema.input);
340
+
341
+ // Validate required args (skip fields with default values)
342
+ for (const req of required) {
343
+ const prop = properties[req];
344
+ const hasDefault = prop?.default !== undefined;
345
+ if (!options[req] && !hasDefault) {
346
+ renderStatic(
347
+ <ErrorView
348
+ message={`--${req} is required`}
349
+ hint={`varg run ${target} --help`}
350
+ />,
351
+ );
352
+ process.exit(1);
353
+ }
354
+ }
355
+
356
+ // Build params for display
357
+ const params: Record<string, string> = {};
358
+ for (const key of Object.keys(properties)) {
359
+ if (options[key] && typeof options[key] === "string") {
360
+ params[key] = options[key] as string;
361
+ }
362
+ }
363
+
364
+ const startTime = Date.now();
365
+
366
+ // For quiet/json modes, no UI
367
+ if (options.quiet || options.json) {
368
+ try {
369
+ const inputs: Record<string, unknown> = {};
370
+ for (const key of Object.keys(properties)) {
371
+ const value = options[key];
372
+ if (value !== undefined) {
373
+ const prop = properties[key];
374
+ inputs[key] =
375
+ typeof value === "string" && prop
376
+ ? coerceCliValue(value, prop)
377
+ : value;
378
+ }
379
+ }
380
+
381
+ const execResult = await executor.run(target, inputs, {
382
+ provider: options.provider,
383
+ });
384
+
385
+ const elapsed = Date.now() - startTime;
386
+
387
+ if (options.json) {
388
+ console.log(
389
+ JSON.stringify(
390
+ {
391
+ success: true,
392
+ result: execResult,
393
+ time: elapsed,
394
+ },
395
+ sanitizeOutput,
396
+ ),
397
+ );
398
+ } else {
399
+ console.log(JSON.stringify(execResult.output, sanitizeOutput));
400
+ }
401
+ } catch (err) {
402
+ const elapsed = Date.now() - startTime;
403
+ const errorMsg = extractErrorMessage(err);
404
+
405
+ if (options.json) {
406
+ console.log(
407
+ JSON.stringify({ success: false, error: errorMsg, time: elapsed }),
408
+ );
409
+ } else {
410
+ console.error(errorMsg);
411
+ }
412
+ process.exit(1);
413
+ }
414
+ return;
415
+ }
416
+
417
+ // Interactive mode with live status
418
+ const { rerender, unmount } = renderLive(
419
+ <StatusBox title={target} status="running" params={params} />,
420
+ );
421
+
422
+ try {
423
+ const inputs: Record<string, unknown> = {};
424
+ for (const key of Object.keys(properties)) {
425
+ const value = options[key];
426
+ if (value !== undefined) {
427
+ const prop = properties[key];
428
+ inputs[key] =
429
+ typeof value === "string" && prop
430
+ ? coerceCliValue(value, prop)
431
+ : value;
432
+ }
433
+ }
434
+
435
+ const execResult = await executor.run(target, inputs, {
436
+ provider: options.provider,
437
+ });
438
+
439
+ const elapsed = Date.now() - startTime;
440
+
441
+ // Extract URL from result
442
+ const output = execResult.output as Record<string, unknown> | string;
443
+ let url: string | null = null;
444
+
445
+ if (typeof output === "string") {
446
+ url = output;
447
+ } else if (output) {
448
+ // Try common URL fields
449
+ url =
450
+ (output.imageUrl as string) ||
451
+ (output.videoUrl as string) ||
452
+ (output.url as string) ||
453
+ null;
454
+
455
+ // Handle images array (nano-banana-pro, flux, etc.)
456
+ if (!url && Array.isArray(output.images) && output.images.length > 0) {
457
+ const firstImage = output.images[0] as Record<string, unknown>;
458
+ url = (firstImage?.url as string) || null;
459
+ }
460
+
461
+ // Handle video object
462
+ if (!url && output.video && typeof output.video === "object") {
463
+ url = (output.video as Record<string, unknown>).url as string;
464
+ }
465
+ }
466
+
467
+ // Clear and show done state
468
+ process.stdout.write("\x1b[2J\x1b[H");
469
+
470
+ rerender(
471
+ <Box flexDirection="column">
472
+ <StatusBox
473
+ title={target}
474
+ status="done"
475
+ params={params}
476
+ output={url ? url : "done"}
477
+ duration={elapsed}
478
+ />
479
+ </Box>,
480
+ );
481
+
482
+ // Also log the URL to console for easy copying
483
+ if (url) {
484
+ console.log(`\n${url}`);
485
+ }
486
+
487
+ // Allow render to complete then unmount
488
+ setTimeout(() => unmount(), 100);
489
+ } catch (err) {
490
+ const elapsed = Date.now() - startTime;
491
+ const errorMsg = extractErrorMessage(err);
492
+
493
+ process.stdout.write("\x1b[2J\x1b[H");
494
+
495
+ rerender(
496
+ <StatusBox
497
+ title={target}
498
+ status="error"
499
+ params={params}
500
+ error={errorMsg}
501
+ duration={elapsed}
502
+ />,
503
+ );
504
+
505
+ setTimeout(() => {
506
+ unmount();
507
+ process.exit(1);
508
+ }, 100);
509
+ }
510
+ },
511
+ });