vargai 0.3.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.
Files changed (154) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.env.example +27 -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 +125 -0
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +78 -0
  14. package/SKILLS.md +173 -0
  15. package/STRUCTURE.md +92 -0
  16. package/biome.json +34 -0
  17. package/bun.lock +1254 -0
  18. package/commitlint.config.js +22 -0
  19. package/docs/plan.md +66 -0
  20. package/docs/todo.md +14 -0
  21. package/docs/varg-sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +69 -0
  24. package/pipeline/cookbooks/SKILL.md +285 -0
  25. package/pipeline/cookbooks/remotion-video.md +585 -0
  26. package/pipeline/cookbooks/round-video-character.md +337 -0
  27. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  28. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  29. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  30. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  31. package/pipeline/cookbooks/talking-character.md +59 -0
  32. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  33. package/pipeline/cookbooks/trendwatching.md +156 -0
  34. package/plan.md +281 -0
  35. package/scripts/.gitkeep +0 -0
  36. package/src/ai-sdk/cache.ts +142 -0
  37. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  38. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  39. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  40. package/src/ai-sdk/examples/duet-video.ts +56 -0
  41. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  42. package/src/ai-sdk/examples/editly-test.ts +57 -0
  43. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  44. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  45. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  46. package/src/ai-sdk/examples/music-generation.ts +19 -0
  47. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  48. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  49. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  50. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  51. package/src/ai-sdk/examples/video-generation.ts +39 -0
  52. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  53. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  54. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  55. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  56. package/src/ai-sdk/file-cache.ts +112 -0
  57. package/src/ai-sdk/file.ts +238 -0
  58. package/src/ai-sdk/generate-element.ts +92 -0
  59. package/src/ai-sdk/generate-music.ts +46 -0
  60. package/src/ai-sdk/generate-video.ts +165 -0
  61. package/src/ai-sdk/index.ts +72 -0
  62. package/src/ai-sdk/music-model.ts +110 -0
  63. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  64. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  65. package/src/ai-sdk/providers/editly/index.ts +817 -0
  66. package/src/ai-sdk/providers/editly/layers.ts +772 -0
  67. package/src/ai-sdk/providers/editly/plan.md +144 -0
  68. package/src/ai-sdk/providers/editly/types.ts +328 -0
  69. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  70. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  71. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  72. package/src/ai-sdk/providers/openai.ts +251 -0
  73. package/src/ai-sdk/providers/replicate.ts +16 -0
  74. package/src/ai-sdk/video-model.ts +185 -0
  75. package/src/cli/commands/find.tsx +137 -0
  76. package/src/cli/commands/help.tsx +85 -0
  77. package/src/cli/commands/index.ts +9 -0
  78. package/src/cli/commands/list.tsx +238 -0
  79. package/src/cli/commands/run.tsx +511 -0
  80. package/src/cli/commands/which.tsx +253 -0
  81. package/src/cli/index.ts +112 -0
  82. package/src/cli/quiet.ts +44 -0
  83. package/src/cli/types.ts +32 -0
  84. package/src/cli/ui/components/Badge.tsx +29 -0
  85. package/src/cli/ui/components/DataTable.tsx +51 -0
  86. package/src/cli/ui/components/Header.tsx +23 -0
  87. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  88. package/src/cli/ui/components/KeyValue.tsx +33 -0
  89. package/src/cli/ui/components/OptionRow.tsx +81 -0
  90. package/src/cli/ui/components/Separator.tsx +23 -0
  91. package/src/cli/ui/components/StatusBox.tsx +108 -0
  92. package/src/cli/ui/components/VargBox.tsx +51 -0
  93. package/src/cli/ui/components/VargProgress.tsx +36 -0
  94. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  95. package/src/cli/ui/components/VargText.tsx +56 -0
  96. package/src/cli/ui/components/index.ts +19 -0
  97. package/src/cli/ui/index.ts +12 -0
  98. package/src/cli/ui/render.ts +35 -0
  99. package/src/cli/ui/theme.ts +63 -0
  100. package/src/cli/utils.ts +78 -0
  101. package/src/core/executor/executor.ts +201 -0
  102. package/src/core/executor/index.ts +13 -0
  103. package/src/core/executor/job.ts +214 -0
  104. package/src/core/executor/pipeline.ts +222 -0
  105. package/src/core/index.ts +11 -0
  106. package/src/core/registry/index.ts +9 -0
  107. package/src/core/registry/loader.ts +149 -0
  108. package/src/core/registry/registry.ts +221 -0
  109. package/src/core/registry/resolver.ts +206 -0
  110. package/src/core/schema/helpers.ts +134 -0
  111. package/src/core/schema/index.ts +8 -0
  112. package/src/core/schema/shared.ts +102 -0
  113. package/src/core/schema/types.ts +279 -0
  114. package/src/core/schema/validator.ts +92 -0
  115. package/src/definitions/actions/captions.ts +261 -0
  116. package/src/definitions/actions/edit.ts +298 -0
  117. package/src/definitions/actions/image.ts +125 -0
  118. package/src/definitions/actions/index.ts +114 -0
  119. package/src/definitions/actions/music.ts +205 -0
  120. package/src/definitions/actions/sync.ts +128 -0
  121. package/src/definitions/actions/transcribe.ts +200 -0
  122. package/src/definitions/actions/upload.ts +111 -0
  123. package/src/definitions/actions/video.ts +163 -0
  124. package/src/definitions/actions/voice.ts +119 -0
  125. package/src/definitions/index.ts +23 -0
  126. package/src/definitions/models/elevenlabs.ts +50 -0
  127. package/src/definitions/models/flux.ts +56 -0
  128. package/src/definitions/models/index.ts +36 -0
  129. package/src/definitions/models/kling.ts +56 -0
  130. package/src/definitions/models/llama.ts +54 -0
  131. package/src/definitions/models/nano-banana-pro.ts +102 -0
  132. package/src/definitions/models/sonauto.ts +68 -0
  133. package/src/definitions/models/soul.ts +65 -0
  134. package/src/definitions/models/wan.ts +54 -0
  135. package/src/definitions/models/whisper.ts +44 -0
  136. package/src/definitions/skills/index.ts +12 -0
  137. package/src/definitions/skills/talking-character.ts +87 -0
  138. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  139. package/src/index.ts +118 -0
  140. package/src/providers/apify.ts +269 -0
  141. package/src/providers/base.ts +264 -0
  142. package/src/providers/elevenlabs.ts +217 -0
  143. package/src/providers/fal.ts +392 -0
  144. package/src/providers/ffmpeg.ts +544 -0
  145. package/src/providers/fireworks.ts +193 -0
  146. package/src/providers/groq.ts +149 -0
  147. package/src/providers/higgsfield.ts +145 -0
  148. package/src/providers/index.ts +143 -0
  149. package/src/providers/replicate.ts +147 -0
  150. package/src/providers/storage.ts +206 -0
  151. package/src/tests/all.test.ts +509 -0
  152. package/src/tests/index.ts +33 -0
  153. package/src/tests/unit.test.ts +403 -0
  154. package/tsconfig.json +45 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * varg which command
3
+ * Ink-based inspection view
4
+ */
5
+
6
+ import { defineCommand } from "citty";
7
+ import { Box, Text } from "ink";
8
+ import { resolve } from "../../core/registry/resolver.ts";
9
+ import { getCliSchemaInfo, toJsonSchema } from "../../core/schema/helpers.ts";
10
+ import type {
11
+ ActionDefinition,
12
+ Definition,
13
+ ModelDefinition,
14
+ SkillDefinition,
15
+ } from "../../core/schema/types.ts";
16
+ import {
17
+ Badge,
18
+ Header,
19
+ OptionRow,
20
+ Separator,
21
+ VargBox,
22
+ VargText,
23
+ } from "../ui/index.ts";
24
+ import { renderStatic } from "../ui/render.ts";
25
+ import { theme } from "../ui/theme.ts";
26
+
27
+ interface WhichViewProps {
28
+ item: Definition;
29
+ }
30
+
31
+ function InputSchemaView({ schema }: { schema: unknown }) {
32
+ // biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
33
+ const { properties, required } = getCliSchemaInfo(schema as any);
34
+ return (
35
+ <>
36
+ <Header>INPUT SCHEMA</Header>
37
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
38
+ {Object.entries(properties).map(([key, prop]) => (
39
+ <OptionRow
40
+ key={key}
41
+ name={key}
42
+ description={prop.description}
43
+ required={required.includes(key)}
44
+ defaultValue={prop.default}
45
+ enumValues={prop.enum}
46
+ type={prop.type}
47
+ />
48
+ ))}
49
+ </Box>
50
+ </>
51
+ );
52
+ }
53
+
54
+ function OutputSchemaView({ schema }: { schema: unknown }) {
55
+ // biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
56
+ const jsonSchema = toJsonSchema(schema as any);
57
+ return (
58
+ <>
59
+ <Header>OUTPUT</Header>
60
+ <Box paddingLeft={2} marginBottom={1}>
61
+ <Text>{jsonSchema.description || "Output result"}</Text>
62
+ </Box>
63
+ </>
64
+ );
65
+ }
66
+
67
+ function WhichView({ item }: WhichViewProps) {
68
+ return (
69
+ <VargBox title={item.name}>
70
+ <Box marginBottom={1}>
71
+ <Text>{item.description}</Text>
72
+ </Box>
73
+
74
+ <Header>TYPE</Header>
75
+ <Box paddingLeft={2} marginBottom={1}>
76
+ <Badge type={item.type} />
77
+ </Box>
78
+
79
+ {/* Providers for models */}
80
+ {item.type === "model" && (
81
+ <>
82
+ <Header>PROVIDERS</Header>
83
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
84
+ <Text>{(item as ModelDefinition).providers.join(", ")}</Text>
85
+ <Text dimColor>
86
+ default: {(item as ModelDefinition).defaultProvider}
87
+ </Text>
88
+ </Box>
89
+ </>
90
+ )}
91
+
92
+ {/* Routes for actions */}
93
+ {item.type === "action" &&
94
+ (item as ActionDefinition).routes.length > 0 && (
95
+ <>
96
+ <Header>ROUTES</Header>
97
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
98
+ {(item as ActionDefinition).routes.map((route) => (
99
+ <Box key={route.target}>
100
+ <Text>
101
+ {"\u2192"} {route.target}
102
+ </Text>
103
+ {route.when && (
104
+ <Text dimColor> when {JSON.stringify(route.when)}</Text>
105
+ )}
106
+ </Box>
107
+ ))}
108
+ </Box>
109
+ </>
110
+ )}
111
+
112
+ {/* Steps for skills */}
113
+ {item.type === "skill" && (
114
+ <>
115
+ <Header>STEPS</Header>
116
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
117
+ {(item as SkillDefinition).steps.map((step, index) => (
118
+ <Box key={step.name}>
119
+ <Text>
120
+ {index + 1}. {step.name} {"\u2192"} {step.run}
121
+ </Text>
122
+ </Box>
123
+ ))}
124
+ </Box>
125
+ </>
126
+ )}
127
+
128
+ {/* Input schema */}
129
+ <InputSchemaView schema={item.schema.input} />
130
+
131
+ {/* Output */}
132
+ <OutputSchemaView schema={item.schema.output} />
133
+
134
+ <Separator />
135
+ <Box marginTop={1}>
136
+ <Text dimColor>run with </Text>
137
+ <Text color={theme.colors.accent}>varg run {item.name} [options]</Text>
138
+ </Box>
139
+ </VargBox>
140
+ );
141
+ }
142
+
143
+ function NotFoundView({
144
+ name,
145
+ suggestions,
146
+ }: {
147
+ name: string;
148
+ suggestions?: string[];
149
+ }) {
150
+ return (
151
+ <Box flexDirection="column" padding={1}>
152
+ <VargText variant="error">not found: '{name}'</VargText>
153
+ {suggestions && suggestions.length > 0 && (
154
+ <Box marginTop={1}>
155
+ <Text>did you mean: {suggestions.slice(0, 3).join(", ")}?</Text>
156
+ </Box>
157
+ )}
158
+ </Box>
159
+ );
160
+ }
161
+
162
+ /** Help view for which command */
163
+ function WhichHelpView() {
164
+ return (
165
+ <VargBox title="varg which">
166
+ <Box marginBottom={1}>
167
+ <Text>inspect a model, action, or skill</Text>
168
+ </Box>
169
+
170
+ <Header>USAGE</Header>
171
+ <Box paddingLeft={2} marginBottom={1}>
172
+ <VargText variant="accent">varg which {"<name>"} [--json]</VargText>
173
+ </Box>
174
+
175
+ <Header>ARGUMENTS</Header>
176
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
177
+ <Text>name name of item to inspect</Text>
178
+ </Box>
179
+
180
+ <Header>OPTIONS</Header>
181
+ <Box flexDirection="column" paddingLeft={2} marginBottom={1}>
182
+ <Text>--json output as json</Text>
183
+ </Box>
184
+
185
+ <Header>EXAMPLES</Header>
186
+ <Box flexDirection="column" paddingLeft={2}>
187
+ <Box flexDirection="column" marginBottom={1}>
188
+ <Text dimColor># inspect video action</Text>
189
+ <VargText variant="accent">varg which video</VargText>
190
+ </Box>
191
+ <Box flexDirection="column">
192
+ <Text dimColor># get json schema</Text>
193
+ <VargText variant="accent">varg which flux --json</VargText>
194
+ </Box>
195
+ </Box>
196
+ </VargBox>
197
+ );
198
+ }
199
+
200
+ /** Show which command help */
201
+ export function showWhichHelp() {
202
+ renderStatic(<WhichHelpView />);
203
+ }
204
+
205
+ export const whichCmd = defineCommand({
206
+ meta: {
207
+ name: "which",
208
+ description: "inspect a model, action, or skill",
209
+ },
210
+ args: {
211
+ name: {
212
+ type: "positional",
213
+ description: "name to inspect",
214
+ required: false,
215
+ },
216
+ json: {
217
+ type: "boolean",
218
+ description: "output as json",
219
+ },
220
+ },
221
+ async run({ args, rawArgs }) {
222
+ // Handle --help
223
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
224
+ showWhichHelp();
225
+ return;
226
+ }
227
+
228
+ const name = args.name as string;
229
+
230
+ if (!name) {
231
+ showWhichHelp();
232
+ return;
233
+ }
234
+
235
+ const result = resolve(name, { fuzzy: true });
236
+
237
+ if (!result.definition) {
238
+ renderStatic(
239
+ <NotFoundView name={name} suggestions={result.suggestions} />,
240
+ );
241
+ process.exit(1);
242
+ }
243
+
244
+ const item = result.definition;
245
+
246
+ if (args.json) {
247
+ console.log(JSON.stringify(item, null, 2));
248
+ return;
249
+ }
250
+
251
+ renderStatic(<WhichView item={item} />);
252
+ },
253
+ });
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * varg cli
5
+ * ai video infrastructure from your terminal
6
+ */
7
+
8
+ // Must be first import to suppress logs before other modules load
9
+ import "./quiet";
10
+
11
+ import { defineCommand, runMain } from "citty";
12
+ import { registry } from "../core/registry";
13
+ import { allDefinitions } from "../definitions";
14
+ import {
15
+ findCmd,
16
+ helpCmd,
17
+ listCmd,
18
+ runCmd,
19
+ showFindHelp,
20
+ showHelp,
21
+ showListHelp,
22
+ showRunHelp,
23
+ showTargetHelp,
24
+ showWhichHelp,
25
+ whichCmd,
26
+ } from "./commands";
27
+
28
+ // Register all providers
29
+ import "../providers"; // Side effect: registers providers to base registry
30
+ import { providers } from "../providers/base";
31
+
32
+ // Register all definitions
33
+ for (const definition of allDefinitions) {
34
+ registry.register(definition);
35
+ }
36
+
37
+ // Also register providers to core registry
38
+ for (const provider of providers.all()) {
39
+ registry.registerProvider(provider);
40
+ }
41
+
42
+ // Intercept --help and -h to use our custom help views
43
+ const args = process.argv.slice(2);
44
+ const hasHelp = args.includes("--help") || args.includes("-h");
45
+
46
+ // Map subcommands to their help functions
47
+ const subcommandHelp: Record<string, () => void> = {
48
+ run: showRunHelp,
49
+ list: showListHelp,
50
+ ls: showListHelp,
51
+ find: showFindHelp,
52
+ search: showFindHelp,
53
+ which: showWhichHelp,
54
+ inspect: showWhichHelp,
55
+ };
56
+
57
+ // Handle help for root or subcommands
58
+ if (args.length === 0 || args[0] === "help") {
59
+ showHelp();
60
+ process.exit(0);
61
+ }
62
+
63
+ if (hasHelp) {
64
+ const subcommand = args[0];
65
+ // Root --help
66
+ if (subcommand === "--help" || subcommand === "-h") {
67
+ showHelp();
68
+ process.exit(0);
69
+ }
70
+
71
+ // Handle subcommand help
72
+ if (subcommand && subcommand in subcommandHelp) {
73
+ const nonHelpArgs = args.filter((a) => a !== "--help" && a !== "-h");
74
+
75
+ // "varg run --help" - show run help
76
+ if (nonHelpArgs.length === 1) {
77
+ const helpFn = subcommandHelp[subcommand];
78
+ if (helpFn) {
79
+ helpFn();
80
+ process.exit(0);
81
+ }
82
+ }
83
+
84
+ // "varg run <target> --help" - show target-specific help
85
+ if (subcommand === "run" && nonHelpArgs.length === 2) {
86
+ const target = nonHelpArgs[1];
87
+ if (target && showTargetHelp(target)) {
88
+ process.exit(0);
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ const main = defineCommand({
95
+ meta: {
96
+ name: "varg",
97
+ version: "0.3.0",
98
+ description: "ai video infrastructure from your terminal",
99
+ },
100
+ subCommands: {
101
+ run: runCmd,
102
+ list: listCmd,
103
+ ls: listCmd,
104
+ find: findCmd,
105
+ search: findCmd,
106
+ which: whichCmd,
107
+ inspect: whichCmd,
108
+ help: helpCmd,
109
+ },
110
+ });
111
+
112
+ runMain(main);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Quiet mode setup - must be imported first
3
+ * Suppresses console.log for --json and --quiet modes
4
+ */
5
+
6
+ const args = process.argv.slice(2);
7
+ export const isQuietMode = args.includes("--json") || args.includes("--quiet");
8
+
9
+ // Store original write function
10
+ const _originalWrite = process.stdout.write.bind(process.stdout);
11
+
12
+ if (isQuietMode) {
13
+ // Override process.stdout.write to filter non-JSON output
14
+ process.stdout.write = ((
15
+ // biome-ignore lint/suspicious/noExplicitAny: complex overload signature
16
+ chunk: any,
17
+ // biome-ignore lint/suspicious/noExplicitAny: complex overload signature
18
+ encoding?: any,
19
+ // biome-ignore lint/suspicious/noExplicitAny: complex overload signature
20
+ callback?: any,
21
+ ): boolean => {
22
+ const str = typeof chunk === "string" ? chunk : chunk.toString();
23
+ const trimmed = str.trim();
24
+
25
+ // Only allow JSON output (starts with { or [) or empty lines
26
+ if (trimmed === "" || trimmed.startsWith("{") || trimmed.startsWith("[")) {
27
+ return _originalWrite(chunk, encoding, callback);
28
+ }
29
+
30
+ // Suppress non-JSON output, still call callback if provided
31
+ if (typeof encoding === "function") {
32
+ encoding();
33
+ } else if (callback) {
34
+ callback();
35
+ }
36
+ return true;
37
+ }) as typeof process.stdout.write;
38
+ }
39
+
40
+ // Export for direct output when needed (bypasses quiet mode)
41
+ export function rawLog(...args: unknown[]): void {
42
+ const message = `${args.map(String).join(" ")}\n`;
43
+ _originalWrite(message);
44
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * CLI type definitions
3
+ */
4
+
5
+ export interface NotFoundOptions {
6
+ /** Suggested alternatives */
7
+ suggestions?: string[];
8
+ /** Max number of suggestions to show */
9
+ maxSuggestions?: number;
10
+ /** Custom hint message */
11
+ hint?: string;
12
+ /** Color function for error text */
13
+ errorColorFn?: (s: string) => string;
14
+ /** Color function for hints */
15
+ hintColorFn?: (s: string) => string;
16
+ /** Whether to exit process (default: true) */
17
+ exit?: boolean;
18
+ }
19
+
20
+ export interface JsonSchemaProperty {
21
+ type?: string;
22
+ description?: string;
23
+ default?: unknown;
24
+ enum?: string[];
25
+ }
26
+
27
+ export interface JsonSchema {
28
+ type?: string;
29
+ properties?: Record<string, JsonSchemaProperty>;
30
+ required?: string[];
31
+ description?: string;
32
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Badge - Type indicator badge
3
+ * Shows [model] [action] [skill] with appropriate styling
4
+ */
5
+
6
+ import { Text } from "ink";
7
+ import { theme } from "../theme.ts";
8
+
9
+ type BadgeType = "model" | "action" | "skill";
10
+
11
+ interface BadgeProps {
12
+ type: BadgeType;
13
+ }
14
+
15
+ const badgeColors: Record<BadgeType, string> = {
16
+ model: theme.colors.accent,
17
+ action: theme.colors.success,
18
+ skill: theme.colors.warning,
19
+ };
20
+
21
+ export function Badge({ type }: BadgeProps) {
22
+ return (
23
+ <Text color={badgeColors[type]} dimColor>
24
+ [{type}]
25
+ </Text>
26
+ );
27
+ }
28
+
29
+ export default Badge;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * DataTable - Table display component
3
+ * Clean text-based table for listing items
4
+ */
5
+
6
+ import { Box, Text } from "ink";
7
+ import { Badge } from "./Badge.tsx";
8
+ import { VargText } from "./VargText.tsx";
9
+
10
+ interface TableRow {
11
+ name: string;
12
+ description: string;
13
+ type?: "model" | "action" | "skill";
14
+ }
15
+
16
+ interface DataTableProps {
17
+ rows: TableRow[];
18
+ showType?: boolean;
19
+ }
20
+
21
+ export function DataTable({ rows, showType = false }: DataTableProps) {
22
+ if (rows.length === 0) {
23
+ return (
24
+ <Box paddingLeft={2}>
25
+ <VargText variant="muted">no items</VargText>
26
+ </Box>
27
+ );
28
+ }
29
+
30
+ // Calculate max name width for alignment
31
+ const maxNameWidth = Math.max(...rows.map((r) => r.name.length), 12);
32
+
33
+ return (
34
+ <Box flexDirection="column">
35
+ {rows.map((row) => (
36
+ <Box key={row.name} paddingLeft={2}>
37
+ {showType && row.type && (
38
+ <>
39
+ <Badge type={row.type} />
40
+ <Text> </Text>
41
+ </>
42
+ )}
43
+ <Text bold>{row.name.padEnd(maxNameWidth)}</Text>
44
+ <VargText variant="muted"> {row.description}</VargText>
45
+ </Box>
46
+ ))}
47
+ </Box>
48
+ );
49
+ }
50
+
51
+ export default DataTable;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Header - Section header component
3
+ * Bold dimmed text for section titles
4
+ */
5
+
6
+ import { Box, Text } from "ink";
7
+ import type { ReactNode } from "react";
8
+
9
+ interface HeaderProps {
10
+ children: ReactNode;
11
+ }
12
+
13
+ export function Header({ children }: HeaderProps) {
14
+ return (
15
+ <Box>
16
+ <Text bold dimColor>
17
+ {children}
18
+ </Text>
19
+ </Box>
20
+ );
21
+ }
22
+
23
+ export default Header;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * HelpBlock - Command help and examples
3
+ * Displays usage patterns and command examples
4
+ */
5
+
6
+ import { Box, Text } from "ink";
7
+ import { VargText } from "./VargText.tsx";
8
+
9
+ interface HelpBlockProps {
10
+ usage?: string;
11
+ examples?: Array<{ command: string; description?: string }>;
12
+ }
13
+
14
+ export function HelpBlock({ usage, examples }: HelpBlockProps) {
15
+ return (
16
+ <Box flexDirection="column">
17
+ {usage && (
18
+ <Box marginBottom={1}>
19
+ <VargText variant="muted">usage: </VargText>
20
+ <VargText variant="accent">{usage}</VargText>
21
+ </Box>
22
+ )}
23
+
24
+ {examples && examples.length > 0 && (
25
+ <Box flexDirection="column">
26
+ {examples.map((ex) => (
27
+ <Box key={ex.command} flexDirection="column" marginBottom={1}>
28
+ {ex.description && (
29
+ <Box paddingLeft={2}>
30
+ <Text dimColor># {ex.description}</Text>
31
+ </Box>
32
+ )}
33
+ <Box paddingLeft={2}>
34
+ <VargText variant="accent">{ex.command}</VargText>
35
+ </Box>
36
+ </Box>
37
+ ))}
38
+ </Box>
39
+ )}
40
+ </Box>
41
+ );
42
+ }
43
+
44
+ export default HelpBlock;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * KeyValue - Label-value pair display
3
+ * Aligned key-value pairs with optional required marker
4
+ */
5
+
6
+ import { Box, Text } from "ink";
7
+ import { theme } from "../theme.ts";
8
+
9
+ interface KeyValueProps {
10
+ label: string;
11
+ value: string;
12
+ labelWidth?: number;
13
+ required?: boolean;
14
+ }
15
+
16
+ export function KeyValue({
17
+ label,
18
+ value,
19
+ labelWidth = theme.layout.optionNameWidth,
20
+ required = false,
21
+ }: KeyValueProps) {
22
+ const paddedLabel = label.padEnd(labelWidth);
23
+
24
+ return (
25
+ <Box>
26
+ <Text dimColor>{paddedLabel}</Text>
27
+ {required && <Text color={theme.colors.warning}>* </Text>}
28
+ <Text>{value}</Text>
29
+ </Box>
30
+ );
31
+ }
32
+
33
+ export default KeyValue;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * OptionRow - CLI option display with better spacing
3
+ * Shows option name, description, type hints, defaults, and enums
4
+ */
5
+
6
+ import { Box, Text } from "ink";
7
+ import { theme } from "../theme.ts";
8
+
9
+ interface OptionRowProps {
10
+ name: string;
11
+ description?: string;
12
+ required?: boolean;
13
+ defaultValue?: unknown;
14
+ enumValues?: (string | number)[];
15
+ type?: string;
16
+ nameWidth?: number;
17
+ }
18
+
19
+ export function OptionRow({
20
+ name,
21
+ description,
22
+ required = false,
23
+ defaultValue,
24
+ enumValues,
25
+ type,
26
+ nameWidth = theme.layout.optionNameWidth,
27
+ }: OptionRowProps) {
28
+ const paddedName = `--${name}`.padEnd(nameWidth);
29
+ const hasDefault = defaultValue !== undefined;
30
+ const hasEnum = enumValues && enumValues.length > 0;
31
+
32
+ // Format enum values - if many values, show abbreviated
33
+ const formatEnums = (values: (string | number)[]) => {
34
+ const stringValues = values.map(String);
35
+ const joined = stringValues.join(", ");
36
+ if (joined.length > 50) {
37
+ // Show first few and count
38
+ const shown = stringValues.slice(0, 4).join(", ");
39
+ return `${shown}, ... (${values.length} options)`;
40
+ }
41
+ return joined;
42
+ };
43
+
44
+ return (
45
+ <Box flexDirection="column" marginBottom={1}>
46
+ {/* Main line: name + description + required */}
47
+ <Box>
48
+ <Text color={theme.colors.accent}>{paddedName}</Text>
49
+ <Text>{description || ""}</Text>
50
+ {required && <Text color={theme.colors.warning}> (required)</Text>}
51
+ </Box>
52
+
53
+ {/* Type and default on same line if compact */}
54
+ {(type || hasDefault) && (
55
+ <Box paddingLeft={nameWidth}>
56
+ {type && (
57
+ <Text dimColor>
58
+ {"<"}
59
+ {type}
60
+ {">"}
61
+ </Text>
62
+ )}
63
+ {hasDefault && (
64
+ <Text dimColor>
65
+ {type ? " " : ""}default: {String(defaultValue)}
66
+ </Text>
67
+ )}
68
+ </Box>
69
+ )}
70
+
71
+ {/* Enum values on separate line for readability */}
72
+ {hasEnum && (
73
+ <Box paddingLeft={nameWidth}>
74
+ <Text dimColor>[{formatEnums(enumValues)}]</Text>
75
+ </Box>
76
+ )}
77
+ </Box>
78
+ );
79
+ }
80
+
81
+ export default OptionRow;