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.
- package/action/captions/index.ts +50 -108
- package/action/edit/index.ts +64 -120
- package/action/image/index.ts +37 -44
- package/action/sync/index.ts +50 -92
- package/action/transcribe/index.ts +44 -61
- package/action/video/index.ts +44 -54
- package/action/voice/index.ts +40 -105
- package/cli/commands/find.ts +58 -0
- package/cli/commands/help.ts +70 -0
- package/cli/commands/list.ts +49 -0
- package/cli/commands/run.ts +237 -0
- package/cli/commands/which.ts +66 -0
- package/cli/discover.ts +66 -0
- package/cli/index.ts +33 -0
- package/cli/runner.ts +65 -0
- package/cli/types.ts +49 -0
- package/cli/ui.ts +185 -0
- package/index.ts +45 -8
- package/lib/fal.ts +0 -11
- package/package.json +8 -1
|
@@ -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
|
+
});
|
package/cli/discover.ts
ADDED
|
@@ -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
|
+
}
|