varg.ai-sdk 0.1.0 → 0.1.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.
@@ -0,0 +1,237 @@
1
+ /**
2
+ * varg run command
3
+ * execute actions by scanning filesystem
4
+ */
5
+
6
+ import { existsSync } from "node:fs";
7
+ import { defineCommand } from "citty";
8
+ import { resolve } from "../discover";
9
+ import type { Meta } from "../types";
10
+ import { box, c, runningBox } from "../ui";
11
+
12
+ interface RunOptions {
13
+ [key: string]: string | boolean | undefined;
14
+ info?: boolean;
15
+ schema?: boolean;
16
+ json?: boolean;
17
+ quiet?: boolean;
18
+ }
19
+
20
+ function parseArgs(args: string[]): { target: string; options: RunOptions } {
21
+ const options: RunOptions = {};
22
+ let target = "";
23
+
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (!arg) continue;
27
+
28
+ if (arg.startsWith("--")) {
29
+ const key = arg.slice(2);
30
+ if (
31
+ key === "info" ||
32
+ key === "schema" ||
33
+ key === "json" ||
34
+ key === "quiet"
35
+ ) {
36
+ options[key] = true;
37
+ } else {
38
+ const value = args[++i];
39
+ if (value) options[key] = value;
40
+ }
41
+ } else if (!target) {
42
+ target = arg;
43
+ } else {
44
+ // positional args - check if it looks like a file
45
+ if (existsSync(arg) || arg.startsWith("./") || arg.startsWith("/")) {
46
+ if (!options.image && !options.audio) {
47
+ options.image = arg;
48
+ }
49
+ } else if (!options.prompt && !options.text) {
50
+ options.prompt = arg;
51
+ }
52
+ }
53
+ }
54
+
55
+ return { target, options };
56
+ }
57
+
58
+ function showHelp(item: Meta) {
59
+ const content: string[] = [];
60
+ content.push("");
61
+ content.push(` ${item.description}`);
62
+ content.push("");
63
+ content.push(c.bold(c.dim(" USAGE")));
64
+ content.push("");
65
+
66
+ const required = item.schema.input.required;
67
+ const reqArgs = required.map((r) => `--${r} <${r}>`).join(" ");
68
+ content.push(` varg run ${item.name} ${reqArgs} [options]`);
69
+
70
+ content.push("");
71
+ content.push(c.bold(c.dim(" OPTIONS")));
72
+ content.push("");
73
+
74
+ for (const [key, prop] of Object.entries(item.schema.input.properties)) {
75
+ const req = item.schema.input.required.includes(key);
76
+ const reqTag = req ? c.yellow(" (required)") : "";
77
+ const defaultVal =
78
+ prop.default !== undefined ? c.dim(` default: ${prop.default}`) : "";
79
+ const enumVals = prop.enum ? c.dim(` [${prop.enum.join(", ")}]`) : "";
80
+
81
+ content.push(
82
+ ` --${key.padEnd(12)}${prop.description}${reqTag}${defaultVal}${enumVals}`,
83
+ );
84
+ }
85
+
86
+ content.push("");
87
+ content.push(` --json output result as json`);
88
+ content.push(` --quiet minimal output`);
89
+ content.push(` --info show this help`);
90
+ content.push("");
91
+
92
+ console.log(box(`action: ${item.name}`, content));
93
+ }
94
+
95
+ function showSchema(item: Meta) {
96
+ const schema = {
97
+ name: item.name,
98
+ type: item.type,
99
+ description: item.description,
100
+ input: item.schema.input,
101
+ output: item.schema.output,
102
+ };
103
+
104
+ console.log(JSON.stringify(schema, null, 2));
105
+ }
106
+
107
+ export const runCmd = defineCommand({
108
+ meta: {
109
+ name: "run",
110
+ description: "run a model or action",
111
+ },
112
+ args: {
113
+ target: {
114
+ type: "positional",
115
+ description: "action to run",
116
+ required: false,
117
+ },
118
+ info: {
119
+ type: "boolean",
120
+ description: "show action info",
121
+ },
122
+ schema: {
123
+ type: "boolean",
124
+ description: "show action schema as json",
125
+ },
126
+ json: {
127
+ type: "boolean",
128
+ description: "output result as json",
129
+ },
130
+ quiet: {
131
+ type: "boolean",
132
+ description: "minimal output",
133
+ },
134
+ },
135
+ async run({ rawArgs }) {
136
+ // use rawArgs for dynamic action options
137
+ const { target, options } = parseArgs(rawArgs);
138
+
139
+ if (!target) {
140
+ console.error(`${c.red("error:")} target required`);
141
+ console.log(`\nusage: ${c.cyan("varg run <action> [options]")}`);
142
+ console.log(`\nrun ${c.cyan("varg list")} to see available actions`);
143
+ process.exit(1);
144
+ }
145
+
146
+ const item = await resolve(target);
147
+
148
+ if (!item) {
149
+ console.error(`${c.red("error:")} '${target}' not found`);
150
+ console.log(`\nrun ${c.cyan("varg list")} to see available actions`);
151
+ process.exit(1);
152
+ }
153
+
154
+ if (options.info) {
155
+ showHelp(item);
156
+ return;
157
+ }
158
+
159
+ if (options.schema) {
160
+ showSchema(item);
161
+ return;
162
+ }
163
+
164
+ // validate required args
165
+ for (const req of item.schema.input.required) {
166
+ if (!options[req]) {
167
+ console.error(`${c.red("error:")} --${req} is required`);
168
+ console.log(`\nrun ${c.cyan(`varg run ${target} --info`)} for usage`);
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ // build params for display
174
+ const params: Record<string, string> = {};
175
+ for (const key of Object.keys(item.schema.input.properties)) {
176
+ if (options[key] && typeof options[key] === "string") {
177
+ params[key] = options[key] as string;
178
+ }
179
+ }
180
+
181
+ if (!options.quiet && !options.json) {
182
+ console.log(runningBox(target, params, "running"));
183
+ }
184
+
185
+ const startTime = Date.now();
186
+
187
+ try {
188
+ const result = await item.run(options);
189
+ const elapsed = Date.now() - startTime;
190
+
191
+ if (options.json) {
192
+ console.log(JSON.stringify({ success: true, result, time: elapsed }));
193
+ } else if (options.quiet) {
194
+ console.log(JSON.stringify(result));
195
+ } else {
196
+ // extract url from result if present
197
+ const resultObj = result as Record<string, unknown> | null;
198
+ const url =
199
+ resultObj?.imageUrl || resultObj?.videoUrl || resultObj?.url || null;
200
+
201
+ console.log("\x1b[2J\x1b[H");
202
+ console.log(
203
+ runningBox(target, params, "done", {
204
+ output: url ? "saved" : "done",
205
+ time: elapsed,
206
+ }),
207
+ );
208
+
209
+ // print url outside box so it's clickable
210
+ if (url) {
211
+ console.log(`\n ${c.cyan("url")} ${url}\n`);
212
+ }
213
+ }
214
+ } catch (err) {
215
+ const elapsed = Date.now() - startTime;
216
+ const errorMsg = err instanceof Error ? err.message : String(err);
217
+
218
+ if (options.json) {
219
+ console.log(
220
+ JSON.stringify({ success: false, error: errorMsg, time: elapsed }),
221
+ );
222
+ } else if (options.quiet) {
223
+ console.error(errorMsg);
224
+ } else {
225
+ console.log("\x1b[2J\x1b[H");
226
+ console.log(
227
+ runningBox(target, params, "error", {
228
+ error: errorMsg,
229
+ time: elapsed,
230
+ }),
231
+ );
232
+ }
233
+
234
+ process.exit(1);
235
+ }
236
+ },
237
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * varg which command
3
+ * inspect an action by scanning filesystem
4
+ */
5
+
6
+ import { defineCommand } from "citty";
7
+ import { resolve } from "../discover";
8
+ import { box, c, header, separator } from "../ui";
9
+
10
+ export const whichCmd = defineCommand({
11
+ meta: {
12
+ name: "which",
13
+ description: "inspect what's behind an action",
14
+ },
15
+ args: {
16
+ name: {
17
+ type: "positional",
18
+ description: "action name",
19
+ required: true,
20
+ },
21
+ },
22
+ async run({ args }) {
23
+ const name = args.name;
24
+
25
+ if (!name) {
26
+ console.error(`${c.red("error:")} action name required`);
27
+ console.log(`\nusage: ${c.cyan("varg which <name>")}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ const item = await resolve(name);
32
+
33
+ if (!item) {
34
+ console.error(`${c.red("error:")} '${name}' not found`);
35
+ console.log(`\nrun ${c.cyan("varg list")} to see available actions`);
36
+ process.exit(1);
37
+ }
38
+
39
+ const content: string[] = [];
40
+ content.push("");
41
+ content.push(` ${item.description}`);
42
+ content.push("");
43
+
44
+ content.push(header("SCHEMA"));
45
+ content.push("");
46
+
47
+ for (const [key, prop] of Object.entries(item.schema.input.properties)) {
48
+ const required = item.schema.input.required.includes(key);
49
+ const reqTag = required ? c.yellow("*") : " ";
50
+ const defaultVal = prop.default
51
+ ? c.dim(` (default: ${prop.default})`)
52
+ : "";
53
+ content.push(
54
+ ` ${reqTag}${c.cyan(key.padEnd(12))}${prop.description}${defaultVal}`,
55
+ );
56
+ }
57
+
58
+ content.push("");
59
+ content.push(separator());
60
+ content.push("");
61
+ content.push(` run ${c.cyan(`varg run ${name} --info`)} for full options`);
62
+ content.push("");
63
+
64
+ console.log(box(`action: ${name}`, content));
65
+ },
66
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * filesystem-based discovery for varg cli
3
+ * scans action/ directory for modules with meta exports
4
+ */
5
+
6
+ import { readdir } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import type { ActionMeta, Meta } from "./types";
9
+
10
+ const ACTION_DIR = join(import.meta.dir, "..", "action");
11
+
12
+ let cachedActions: ActionMeta[] | null = null;
13
+
14
+ export async function discoverActions(): Promise<ActionMeta[]> {
15
+ if (cachedActions) return cachedActions;
16
+
17
+ const actions: ActionMeta[] = [];
18
+ const dirs = await readdir(ACTION_DIR);
19
+
20
+ for (const dir of dirs) {
21
+ try {
22
+ const mod = await import(`../action/${dir}`);
23
+ if (mod.meta && mod.meta.type === "action") {
24
+ actions.push(mod.meta);
25
+ }
26
+ } catch {
27
+ // skip directories without valid modules
28
+ }
29
+ }
30
+
31
+ cachedActions = actions;
32
+ return actions;
33
+ }
34
+
35
+ export async function resolve(name: string): Promise<Meta | null> {
36
+ const actions = await discoverActions();
37
+
38
+ // check explicit namespace
39
+ if (name.startsWith("action/")) {
40
+ const actionName = name.slice(7);
41
+ return actions.find((a) => a.name === actionName) || null;
42
+ }
43
+
44
+ // check actions
45
+ const action = actions.find((a) => a.name === name);
46
+ if (action) return action;
47
+
48
+ return null;
49
+ }
50
+
51
+ export async function search(query: string): Promise<Meta[]> {
52
+ const actions = await discoverActions();
53
+ const q = query.toLowerCase();
54
+
55
+ return actions.filter(
56
+ (a) =>
57
+ a.name.toLowerCase().includes(q) ||
58
+ a.description.toLowerCase().includes(q) ||
59
+ a.inputType.toLowerCase().includes(q) ||
60
+ a.outputType.toLowerCase().includes(q),
61
+ );
62
+ }
63
+
64
+ export async function list(): Promise<Meta[]> {
65
+ return discoverActions();
66
+ }
package/cli/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * varg cli
5
+ * ai video infrastructure from your terminal
6
+ */
7
+
8
+ import { defineCommand, runMain } from "citty";
9
+ import { findCmd } from "./commands/find";
10
+ import { helpCmd } from "./commands/help";
11
+ import { listCmd } from "./commands/list";
12
+ import { runCmd } from "./commands/run";
13
+ import { whichCmd } from "./commands/which";
14
+
15
+ const main = defineCommand({
16
+ meta: {
17
+ name: "varg",
18
+ version: "0.1.0",
19
+ description: "ai video infrastructure from your terminal",
20
+ },
21
+ subCommands: {
22
+ run: runCmd,
23
+ list: listCmd,
24
+ ls: listCmd,
25
+ find: findCmd,
26
+ search: findCmd,
27
+ which: whichCmd,
28
+ inspect: whichCmd,
29
+ help: helpCmd,
30
+ },
31
+ });
32
+
33
+ runMain(main);
package/cli/runner.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * shared cli runner for actions
3
+ * parses args and calls meta.run()
4
+ */
5
+
6
+ import type { Meta } from "./types";
7
+
8
+ export async function runCli(meta: Meta) {
9
+ const args = process.argv.slice(2);
10
+
11
+ if (args[0] === "help" || args[0] === "--help" || args.length === 0) {
12
+ console.log(`
13
+ ${meta.name} - ${meta.description}
14
+
15
+ usage:
16
+ bun run action/${meta.name} [options]
17
+
18
+ options:`);
19
+
20
+ for (const [key, prop] of Object.entries(meta.schema.input.properties)) {
21
+ const req = meta.schema.input.required.includes(key) ? " (required)" : "";
22
+ const def =
23
+ prop.default !== undefined ? ` [default: ${prop.default}]` : "";
24
+ console.log(` --${key.padEnd(14)} ${prop.description}${req}${def}`);
25
+ }
26
+
27
+ console.log(`
28
+ examples:
29
+ bun run action/${meta.name} --${meta.schema.input.required[0]} "value"
30
+ `);
31
+ return;
32
+ }
33
+
34
+ // parse args
35
+ const options: Record<string, unknown> = {};
36
+ const firstRequired = meta.schema.input.required[0];
37
+
38
+ for (let i = 0; i < args.length; i++) {
39
+ const arg = args[i];
40
+ if (arg?.startsWith("--")) {
41
+ const key = arg.slice(2);
42
+ const value = args[++i];
43
+ options[key] = value;
44
+ } else if (arg && firstRequired && !options[firstRequired]) {
45
+ // first positional arg goes to first required field
46
+ options[firstRequired] = arg;
47
+ }
48
+ }
49
+
50
+ // validate required
51
+ for (const req of meta.schema.input.required) {
52
+ if (!options[req]) {
53
+ console.error(`error: --${req} is required`);
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ try {
59
+ const result = await meta.run(options);
60
+ console.log(JSON.stringify(result, null, 2));
61
+ } catch (err) {
62
+ console.error(`error: ${err instanceof Error ? err.message : err}`);
63
+ process.exit(1);
64
+ }
65
+ }
package/cli/types.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * shared types for varg cli
3
+ * actions and models export meta objects conforming to these types
4
+ */
5
+
6
+ export interface SchemaProperty {
7
+ type: string;
8
+ description: string;
9
+ enum?: (string | number)[];
10
+ default?: unknown;
11
+ format?: string;
12
+ }
13
+
14
+ export interface Schema {
15
+ input: {
16
+ type: "object";
17
+ required: string[];
18
+ properties: Record<string, SchemaProperty>;
19
+ };
20
+ output: {
21
+ type: string;
22
+ format?: string;
23
+ description: string;
24
+ };
25
+ }
26
+
27
+ export interface ActionMeta {
28
+ name: string;
29
+ type: "action";
30
+ description: string;
31
+ inputType: string;
32
+ outputType: string;
33
+ schema: Schema;
34
+ run: (options: Record<string, unknown>) => Promise<unknown>;
35
+ }
36
+
37
+ export interface ModelMeta {
38
+ name: string;
39
+ type: "model";
40
+ description: string;
41
+ inputType: string;
42
+ outputType: string;
43
+ providers: string[];
44
+ defaultProvider: string;
45
+ schema: Schema;
46
+ run: (options: Record<string, unknown>) => Promise<unknown>;
47
+ }
48
+
49
+ export type Meta = ActionMeta | ModelMeta;
package/cli/ui.ts ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * terminal ui helpers for varg cli
3
+ * beautiful boxes and formatting
4
+ */
5
+
6
+ const COLORS = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ green: "\x1b[32m",
11
+ yellow: "\x1b[33m",
12
+ blue: "\x1b[34m",
13
+ magenta: "\x1b[35m",
14
+ cyan: "\x1b[36m",
15
+ red: "\x1b[31m",
16
+ gray: "\x1b[90m",
17
+ };
18
+
19
+ export const c = {
20
+ reset: (s: string) => `${COLORS.reset}${s}${COLORS.reset}`,
21
+ bold: (s: string) => `${COLORS.bold}${s}${COLORS.reset}`,
22
+ dim: (s: string) => `${COLORS.dim}${s}${COLORS.reset}`,
23
+ green: (s: string) => `${COLORS.green}${s}${COLORS.reset}`,
24
+ yellow: (s: string) => `${COLORS.yellow}${s}${COLORS.reset}`,
25
+ blue: (s: string) => `${COLORS.blue}${s}${COLORS.reset}`,
26
+ magenta: (s: string) => `${COLORS.magenta}${s}${COLORS.reset}`,
27
+ cyan: (s: string) => `${COLORS.cyan}${s}${COLORS.reset}`,
28
+ red: (s: string) => `${COLORS.red}${s}${COLORS.reset}`,
29
+ gray: (s: string) => `${COLORS.gray}${s}${COLORS.reset}`,
30
+ };
31
+
32
+ // strip ansi codes for length calculation
33
+ // biome-ignore lint/complexity/useRegexLiterals: literal triggers noControlCharactersInRegex
34
+ const ANSI_REGEX = new RegExp("\x1b\\[[0-9;]*m", "g");
35
+ function stripAnsi(s: string): string {
36
+ return s.replace(ANSI_REGEX, "");
37
+ }
38
+
39
+ // box drawing characters
40
+ const BOX = {
41
+ topLeft: "┌",
42
+ topRight: "┐",
43
+ bottomLeft: "└",
44
+ bottomRight: "┘",
45
+ horizontal: "─",
46
+ vertical: "│",
47
+ line: "─",
48
+ };
49
+
50
+ export const WIDTH = 71;
51
+
52
+ export function box(title: string, content: string[]): string {
53
+ const lines: string[] = [];
54
+
55
+ // top border with title
56
+ const titlePart = title ? `${BOX.line} ${title} ` : "";
57
+ const remainingWidth = WIDTH - 2 - stripAnsi(titlePart).length;
58
+ lines.push(
59
+ `${BOX.topLeft}${titlePart}${BOX.horizontal.repeat(remainingWidth)}${BOX.topRight}`,
60
+ );
61
+
62
+ // content lines
63
+ for (const line of content) {
64
+ const stripped = stripAnsi(line);
65
+ const padding = WIDTH - 2 - stripped.length;
66
+ if (padding >= 0) {
67
+ lines.push(`${BOX.vertical}${line}${" ".repeat(padding)}${BOX.vertical}`);
68
+ } else {
69
+ // truncate if too long
70
+ lines.push(
71
+ `${BOX.vertical}${line.slice(0, WIDTH - 5)}...${BOX.vertical}`,
72
+ );
73
+ }
74
+ }
75
+
76
+ // bottom border
77
+ lines.push(
78
+ `${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}`,
79
+ );
80
+
81
+ return lines.join("\n");
82
+ }
83
+
84
+ export function separator(): string {
85
+ return ` ${c.dim(BOX.horizontal.repeat(WIDTH - 4))}`;
86
+ }
87
+
88
+ export function header(text: string): string {
89
+ return c.bold(c.dim(` ${text}`));
90
+ }
91
+
92
+ export function row(label: string, value: string, indent = 2): string {
93
+ const spaces = " ".repeat(indent);
94
+ const labelWidth = 14;
95
+ const paddedLabel = label.padEnd(labelWidth);
96
+ return `${spaces}${c.dim(paddedLabel)}${value}`;
97
+ }
98
+
99
+ export function success(message: string): string {
100
+ return ` ${c.green("✓")} ${message}`;
101
+ }
102
+
103
+ export function error(message: string): string {
104
+ return ` ${c.red("✗")} ${message}`;
105
+ }
106
+
107
+ export function spinner(message: string): string {
108
+ return ` ${c.cyan("◐")} ${message}`;
109
+ }
110
+
111
+ export function formatDuration(ms: number): string {
112
+ if (ms < 1000) return `${ms}ms`;
113
+ const s = Math.round(ms / 1000);
114
+ return `${s}s`;
115
+ }
116
+
117
+ export function formatCost(dollars: number): string {
118
+ return `$${dollars.toFixed(3)}`;
119
+ }
120
+
121
+ // table formatting for list command
122
+ export interface TableRow {
123
+ name: string;
124
+ description: string;
125
+ providers?: string;
126
+ }
127
+
128
+ export function table(rows: TableRow[], nameWidth = 16): string[] {
129
+ const lines: string[] = [];
130
+
131
+ for (const row of rows) {
132
+ const name = row.name.padEnd(nameWidth);
133
+ const desc = row.description;
134
+ const providers = row.providers ? c.dim(row.providers) : "";
135
+
136
+ if (providers) {
137
+ lines.push(` ${c.cyan(name)}${desc.padEnd(30)}${providers}`);
138
+ } else {
139
+ lines.push(` ${c.cyan(name)}${desc}`);
140
+ }
141
+ }
142
+
143
+ return lines;
144
+ }
145
+
146
+ // progress output for running commands
147
+ export function runningBox(
148
+ name: string,
149
+ params: Record<string, string>,
150
+ status: "running" | "done" | "error",
151
+ result?: { output?: string; cost?: number; error?: string; time?: number },
152
+ ): string {
153
+ const content: string[] = [""];
154
+
155
+ // params
156
+ for (const [key, value] of Object.entries(params)) {
157
+ const displayValue =
158
+ value.length > 40 ? `"${value.slice(0, 37)}..."` : `"${value}"`;
159
+ content.push(row(key, displayValue));
160
+ }
161
+
162
+ content.push("");
163
+
164
+ // status
165
+ if (status === "running") {
166
+ content.push(spinner("generating..."));
167
+ } else if (status === "done" && result) {
168
+ content.push(success(`done in ${formatDuration(result.time || 0)}`));
169
+ content.push("");
170
+ if (result.output) {
171
+ content.push(row("output", result.output));
172
+ }
173
+ if (result.cost) {
174
+ content.push(row("cost", formatCost(result.cost)));
175
+ }
176
+ } else if (status === "error" && result?.error) {
177
+ content.push(error("failed"));
178
+ content.push("");
179
+ content.push(row("error", result.error));
180
+ }
181
+
182
+ content.push("");
183
+
184
+ return box(name, content);
185
+ }