ai-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/package.json +1 -1
- package/src/cli.test.ts +1 -26
- package/src/commands/image.ts +62 -5
- package/src/commands/models.ts +36 -47
- package/src/commands/text.ts +7 -2
- package/src/commands/video.ts +7 -2
- package/src/index.ts +0 -2
- package/src/lib/models.test.ts +236 -126
- package/src/lib/models.ts +128 -118
- package/src/commands/completions.ts +0 -296
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# ai
|
|
2
|
+
|
|
3
|
+
A tiny, agent-native CLI for generating images, video and text with dead-simple commands, stdin support and predictable artifact outputs. Uses [Vercel AI SDK](https://sdk.vercel.ai) and [AI Gateway](https://vercel.com/docs/ai-gateway) for unified access to hundreds of models.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g ai-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires an [AI Gateway](https://vercel.com/docs/ai-gateway) API key or a provider-specific key (e.g. `OPENAI_API_KEY`).
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ai image "a cute dog"
|
|
17
|
+
ai video "a spinning triangle"
|
|
18
|
+
ai text "explain quantum computing"
|
|
19
|
+
ai models # list available models
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Piping
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
ai image "a dragon" | ai video "animate this"
|
|
26
|
+
cat notes.txt | ai text "summarize this"
|
|
27
|
+
git diff | ai text "explain these changes"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Common Options
|
|
31
|
+
|
|
32
|
+
All commands support:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
-m, --model <id> Model ID (creator/model-name), comma-separated for multi-model
|
|
36
|
+
-o, --output <path> Output file path or directory
|
|
37
|
+
-n, --count <n> Number of generations per model (default: 1)
|
|
38
|
+
-p, --concurrency <n> Max parallel generations (default: 4, video: 2)
|
|
39
|
+
-q, --quiet Suppress progress output
|
|
40
|
+
--json Output metadata as JSON
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Model IDs can be specified as `creator/model-name` or just `model-name` (resolved against models fetched from the gateway):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ai text -m gpt-5.5 "hello" # resolves to openai/gpt-5.5
|
|
47
|
+
ai image -m flux-2-pro "a sunset" # resolves to bfl/flux-2-pro
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### image
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
--size <WxH> Image size (e.g. 1024x1024)
|
|
54
|
+
--aspect-ratio <W:H> Aspect ratio (e.g. 16:9)
|
|
55
|
+
--quality <level> Quality (standard, hd)
|
|
56
|
+
--style <style> Style (vivid, natural)
|
|
57
|
+
--no-preview Disable inline image preview
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### video
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
--aspect-ratio <W:H> Aspect ratio (e.g. 16:9)
|
|
64
|
+
--duration <seconds> Duration in seconds
|
|
65
|
+
--no-preview Disable inline video frame preview
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### text
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
-f, --format <fmt> Output format: md, txt (default: md)
|
|
72
|
+
-s, --system <prompt> System prompt
|
|
73
|
+
--max-tokens <n> Maximum tokens to generate
|
|
74
|
+
-t, --temperature <n> Temperature (0-2)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### models
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
--type <type> Filter by type: text, image, video
|
|
81
|
+
--creator <name> Filter by creator (e.g. openai, google)
|
|
82
|
+
--json Output as JSON (includes descriptions)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
All model types (text, image, video) are fetched live from the AI Gateway.
|
|
86
|
+
|
|
87
|
+
### Multi-Model Comparison
|
|
88
|
+
|
|
89
|
+
Generate with multiple models by comma-separating `-m`:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ai image "a sunset" -m "openai/gpt-image-1,xai/grok-imagine-image,bfl/flux-2-pro"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Combine with `-n` to generate multiple per model:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ai image "a sunset" -n 2 -m "openai/gpt-image-1,bfl/flux-2-pro" # 4 images total
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Inline Preview
|
|
102
|
+
|
|
103
|
+
When running in a terminal that supports the [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) (Kitty, Ghostty, WezTerm, Warp, iTerm2), generated images and videos are displayed inline automatically. Video previews decode an H.264 keyframe from the midpoint of the video using [openh264](https://github.com/cisco/openh264) compiled to WebAssembly — no native dependencies required. Use `--no-preview` to disable this, or set `AI_CLI_PREVIEW=1` to force it on in undetected terminals.
|
|
104
|
+
|
|
105
|
+
### Output Behavior
|
|
106
|
+
|
|
107
|
+
- **text**: saves to `output.md` (interactive), stdout when piped
|
|
108
|
+
- **image/video**: saves to file (interactive), raw binary stdout when piped
|
|
109
|
+
- **`-o <dir>`**: saves inside the directory with auto-generated names
|
|
110
|
+
|
|
111
|
+
### Environment Variables
|
|
112
|
+
|
|
113
|
+
| Variable | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `AI_GATEWAY_API_KEY` | AI Gateway authentication key |
|
|
116
|
+
| `OPENAI_API_KEY` | Provider-specific key (or other provider keys) |
|
|
117
|
+
| `AI_CLI_TEXT_MODEL` | Default text model (overrides `openai/gpt-5.5`) |
|
|
118
|
+
| `AI_CLI_IMAGE_MODEL` | Default image model (overrides `openai/gpt-image-2`) |
|
|
119
|
+
| `AI_CLI_VIDEO_MODEL` | Default video model (overrides `bytedance/seedance-2.0`) |
|
|
120
|
+
| `AI_CLI_OUTPUT_DIR` | Default output directory for generated files |
|
|
121
|
+
| `AI_CLI_PREVIEW` | Set to `1` to force inline image preview, `0` to disable |
|
|
122
|
+
| `NO_COLOR` | Disable ANSI color output |
|
|
123
|
+
| `FORCE_COLOR` | Force color output even when not a TTY |
|
|
124
|
+
|
|
125
|
+
The `-m` flag always takes priority over `AI_CLI_*_MODEL` env vars. The `-o` flag always takes priority over `AI_CLI_OUTPUT_DIR`.
|
|
126
|
+
|
|
127
|
+
### Timeouts
|
|
128
|
+
|
|
129
|
+
Requests that exceed the timeout are aborted automatically:
|
|
130
|
+
|
|
131
|
+
| Command | Timeout |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `text` | 120 seconds |
|
|
134
|
+
| `image` | 120 seconds |
|
|
135
|
+
| `video` | 300 seconds |
|
|
136
|
+
|
|
137
|
+
### Exit Codes
|
|
138
|
+
|
|
139
|
+
| Code | Meaning |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `0` | Success |
|
|
142
|
+
| `1` | All generations failed |
|
|
143
|
+
| `2` | Partial failure (some succeeded, some failed) |
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
[Apache-2.0](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A tiny, agent-native CLI for generating images, video and text with dead-simple commands, stdin support and predictable artifact outputs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/cli.test.ts
CHANGED
|
@@ -22,7 +22,7 @@ describe("cli integration", () => {
|
|
|
22
22
|
test("--help exits 0 and lists subcommands", async () => {
|
|
23
23
|
const { exitCode, stdout } = await run("--help");
|
|
24
24
|
expect(exitCode).toBe(0);
|
|
25
|
-
for (const sub of ["text", "image", "video", "models"
|
|
25
|
+
for (const sub of ["text", "image", "video", "models"]) {
|
|
26
26
|
expect(stdout).toContain(sub);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
@@ -33,31 +33,6 @@ describe("cli integration", () => {
|
|
|
33
33
|
expect(stdout.trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test("completions zsh exits 0 with valid output", async () => {
|
|
37
|
-
const { exitCode, stdout } = await run("completions", "zsh");
|
|
38
|
-
expect(exitCode).toBe(0);
|
|
39
|
-
expect(stdout).toContain("#compdef ai");
|
|
40
|
-
expect(stdout).toContain("--no-preview");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("completions bash exits 0 with valid output", async () => {
|
|
44
|
-
const { exitCode, stdout } = await run("completions", "bash");
|
|
45
|
-
expect(exitCode).toBe(0);
|
|
46
|
-
expect(stdout).toContain("complete -F");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("completions fish exits 0 with valid output", async () => {
|
|
50
|
-
const { exitCode, stdout } = await run("completions", "fish");
|
|
51
|
-
expect(exitCode).toBe(0);
|
|
52
|
-
expect(stdout).toContain("complete -c ai");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("completions with invalid shell exits 1", async () => {
|
|
56
|
-
const { exitCode, stderr } = await run("completions", "powershell");
|
|
57
|
-
expect(exitCode).toBe(1);
|
|
58
|
-
expect(stderr).toContain("Unknown shell");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
36
|
test("text with no prompt and no stdin exits 1", async () => {
|
|
62
37
|
const { exitCode, stderr } = await run("text");
|
|
63
38
|
expect(exitCode).toBe(1);
|
package/src/commands/image.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { generateImage, gateway } from "ai";
|
|
1
|
+
import { generateImage, generateText, gateway } from "ai";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
import { buildJobs, runJobs } from "../lib/jobs.js";
|
|
5
|
-
import { resolveModels } from "../lib/models.js";
|
|
5
|
+
import { fetchGatewayModels, resolveModels } from "../lib/models.js";
|
|
6
6
|
import { parsePositiveInt, parseSize, parseAspectRatio } from "../lib/parse.js";
|
|
7
7
|
import { readStdin } from "../lib/stdin.js";
|
|
8
8
|
|
|
@@ -57,15 +57,17 @@ export function registerImageCommand(program: Command) {
|
|
|
57
57
|
);
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
|
-
let imagePrompt: string | { images: Uint8Array[]; text?: string }
|
|
61
|
-
prompt!;
|
|
60
|
+
let imagePrompt: string | { images: Uint8Array[]; text?: string };
|
|
62
61
|
if (stdin) {
|
|
63
62
|
imagePrompt = prompt
|
|
64
63
|
? { images: [new Uint8Array(stdin)], text: prompt }
|
|
65
64
|
: { images: [new Uint8Array(stdin)] };
|
|
65
|
+
} else {
|
|
66
|
+
imagePrompt = prompt!;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
const gatewayModels = await fetchGatewayModels();
|
|
70
|
+
const models = resolveModels("image", opts.model, gatewayModels.image);
|
|
69
71
|
const countPerModel = opts.count
|
|
70
72
|
? parsePositiveInt(opts.count, "count")
|
|
71
73
|
: 1;
|
|
@@ -90,7 +92,62 @@ export function registerImageCommand(program: Command) {
|
|
|
90
92
|
jobs,
|
|
91
93
|
async (modelId) => {
|
|
92
94
|
const abort = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
95
|
+
|
|
96
|
+
if (gatewayModels.languageImageModelIds.has(modelId)) {
|
|
97
|
+
const messageContent: Array<
|
|
98
|
+
| { type: "text"; text: string }
|
|
99
|
+
| { type: "image"; image: Uint8Array }
|
|
100
|
+
> = [];
|
|
101
|
+
if (typeof imagePrompt === "string") {
|
|
102
|
+
messageContent.push({ type: "text", text: imagePrompt });
|
|
103
|
+
} else {
|
|
104
|
+
for (const img of imagePrompt.images) {
|
|
105
|
+
messageContent.push({ type: "image", image: img });
|
|
106
|
+
}
|
|
107
|
+
if (imagePrompt.text) {
|
|
108
|
+
messageContent.push({
|
|
109
|
+
type: "text",
|
|
110
|
+
text: imagePrompt.text,
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
messageContent.push({
|
|
114
|
+
type: "text",
|
|
115
|
+
text: "Generate an image",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const creator = gatewayModels.all.find(
|
|
120
|
+
(m) => m.id === modelId
|
|
121
|
+
)?.creator;
|
|
122
|
+
const result = await generateText({
|
|
123
|
+
headers: {
|
|
124
|
+
"http-referer": "https://github.com/vercel-labs/ai-cli",
|
|
125
|
+
"x-title": "ai-cli",
|
|
126
|
+
},
|
|
127
|
+
model: gateway(modelId),
|
|
128
|
+
messages: [{ role: "user", content: messageContent }],
|
|
129
|
+
abortSignal: abort,
|
|
130
|
+
providerOptions:
|
|
131
|
+
creator === "google"
|
|
132
|
+
? { google: { responseModalities: ["IMAGE", "TEXT"] } }
|
|
133
|
+
: undefined,
|
|
134
|
+
});
|
|
135
|
+
const imageFile = result.files?.find((f) =>
|
|
136
|
+
f.mediaType.startsWith("image/")
|
|
137
|
+
);
|
|
138
|
+
if (!imageFile) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Model ${modelId} did not return an image in the response`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return Buffer.from(imageFile.uint8Array);
|
|
144
|
+
}
|
|
145
|
+
|
|
93
146
|
const result = await generateImage({
|
|
147
|
+
headers: {
|
|
148
|
+
"http-referer": "https://github.com/vercel-labs/ai-cli",
|
|
149
|
+
"x-title": "ai-cli",
|
|
150
|
+
},
|
|
94
151
|
model: gateway.image(modelId),
|
|
95
152
|
prompt: imagePrompt,
|
|
96
153
|
abortSignal: abort,
|
package/src/commands/models.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fetchGatewayModels,
|
|
5
|
+
type Modality,
|
|
6
|
+
type ModelEntry,
|
|
7
|
+
} from "../lib/models.js";
|
|
4
8
|
|
|
5
|
-
function
|
|
9
|
+
function groupByCreator(models: ModelEntry[]): Map<string, ModelEntry[]> {
|
|
6
10
|
const groups = new Map<string, ModelEntry[]>();
|
|
7
11
|
for (const m of models) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!groups.has(provider)) groups.set(provider, []);
|
|
11
|
-
groups.get(provider)!.push(m);
|
|
12
|
+
if (!groups.has(m.creator)) groups.set(m.creator, []);
|
|
13
|
+
groups.get(m.creator)!.push(m);
|
|
12
14
|
}
|
|
13
15
|
return new Map(
|
|
14
16
|
[...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]))
|
|
@@ -25,61 +27,42 @@ export function registerModelsCommand(program: Command) {
|
|
|
25
27
|
.command("models")
|
|
26
28
|
.description("List available models from AI Gateway")
|
|
27
29
|
.option("--type <type>", "Filter by type: text, image, video")
|
|
28
|
-
.option("--
|
|
30
|
+
.option("--creator <name>", "Filter by creator (e.g. openai, google)")
|
|
29
31
|
.option("--json", "Output as JSON (includes descriptions)")
|
|
30
32
|
.action(
|
|
31
|
-
async (opts: { type?: string;
|
|
33
|
+
async (opts: { type?: string; creator?: string; json?: boolean }) => {
|
|
32
34
|
const validTypes = ["text", "image", "video"];
|
|
33
|
-
const filterType = opts.type?.toLowerCase();
|
|
35
|
+
const filterType = opts.type?.toLowerCase() as Modality | undefined;
|
|
34
36
|
if (filterType && !validTypes.includes(filterType)) {
|
|
35
37
|
process.stderr.write(
|
|
36
38
|
`Error: --type must be one of: ${validTypes.join(", ")} (got "${opts.type}")\n`
|
|
37
39
|
);
|
|
38
40
|
process.exit(1);
|
|
39
41
|
}
|
|
40
|
-
const
|
|
42
|
+
const filterCreator = opts.creator?.toLowerCase();
|
|
41
43
|
|
|
42
44
|
const gatewayModels = await fetchGatewayModels();
|
|
43
45
|
|
|
44
|
-
const filterGrouped = (grouped: Map<string, ModelEntry[]>) => {
|
|
45
|
-
if (!filterProvider) return grouped;
|
|
46
|
-
const filtered = new Map<string, ModelEntry[]>();
|
|
47
|
-
for (const [provider, models] of grouped) {
|
|
48
|
-
if (provider.toLowerCase() === filterProvider) {
|
|
49
|
-
filtered.set(provider, models);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return filtered;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
46
|
if (opts.json) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...(m.description ? { description: m.description } : {}),
|
|
61
|
-
});
|
|
62
|
-
if (!filterType || filterType === "text") {
|
|
63
|
-
output.text = Object.fromEntries(
|
|
64
|
-
[...filterGrouped(groupByProvider(gatewayModels.text))].map(
|
|
65
|
-
([provider, models]) => [provider, models.map(jsonMapper)]
|
|
66
|
-
)
|
|
47
|
+
let entries = gatewayModels.all;
|
|
48
|
+
if (filterType) {
|
|
49
|
+
entries = entries.filter((m) =>
|
|
50
|
+
m.capabilities.includes(filterType)
|
|
67
51
|
);
|
|
68
52
|
}
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
([provider, models]) => [provider, models.map(jsonMapper)]
|
|
73
|
-
)
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
if (!filterType || filterType === "video") {
|
|
77
|
-
output.video = Object.fromEntries(
|
|
78
|
-
[...filterGrouped(groupByProvider(gatewayModels.video))].map(
|
|
79
|
-
([provider, models]) => [provider, models.map(jsonMapper)]
|
|
80
|
-
)
|
|
53
|
+
if (filterCreator) {
|
|
54
|
+
entries = entries.filter(
|
|
55
|
+
(m) => m.creator.toLowerCase() === filterCreator
|
|
81
56
|
);
|
|
82
57
|
}
|
|
58
|
+
const output = entries.map((m) => ({
|
|
59
|
+
id: m.id,
|
|
60
|
+
...(m.name ? { name: m.name } : {}),
|
|
61
|
+
...(m.description ? { description: m.description } : {}),
|
|
62
|
+
creator: m.creator,
|
|
63
|
+
capabilities: m.capabilities,
|
|
64
|
+
...(m.pricing ? { pricing: m.pricing } : {}),
|
|
65
|
+
}));
|
|
83
66
|
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
84
67
|
return;
|
|
85
68
|
}
|
|
@@ -94,13 +77,19 @@ export function registerModelsCommand(program: Command) {
|
|
|
94
77
|
|
|
95
78
|
let totalCount = 0;
|
|
96
79
|
for (const section of sections) {
|
|
97
|
-
|
|
80
|
+
let entries = section.entries;
|
|
81
|
+
if (filterCreator) {
|
|
82
|
+
entries = entries.filter(
|
|
83
|
+
(m) => m.creator.toLowerCase() === filterCreator
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const grouped = groupByCreator(entries);
|
|
98
87
|
const count = [...grouped.values()].reduce((s, m) => s + m.length, 0);
|
|
99
88
|
if (count === 0) continue;
|
|
100
89
|
totalCount += count;
|
|
101
90
|
process.stdout.write(`\n${section.title} models (${count}):\n`);
|
|
102
|
-
for (const [
|
|
103
|
-
process.stdout.write(`\n ${
|
|
91
|
+
for (const [creator, models] of grouped) {
|
|
92
|
+
process.stdout.write(`\n ${creator}\n`);
|
|
104
93
|
for (const m of models) {
|
|
105
94
|
process.stdout.write(` ${modelName(m.id)}\n`);
|
|
106
95
|
}
|
package/src/commands/text.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { generateText, gateway } from "ai";
|
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
import { buildJobs, runJobs } from "../lib/jobs.js";
|
|
5
|
-
import { resolveModels } from "../lib/models.js";
|
|
5
|
+
import { fetchGatewayModels, resolveModels } from "../lib/models.js";
|
|
6
6
|
import type { OutputFormat } from "../lib/output.js";
|
|
7
7
|
import { parsePositiveInt, parseTemperature } from "../lib/parse.js";
|
|
8
8
|
import { readStdin, stdinAsText } from "../lib/stdin.js";
|
|
@@ -69,7 +69,8 @@ export function registerTextCommand(program: Command) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const format = resolveFormat(opts.format);
|
|
72
|
-
const
|
|
72
|
+
const gatewayModels = await fetchGatewayModels();
|
|
73
|
+
const models = resolveModels("text", opts.model, gatewayModels.text);
|
|
73
74
|
const countPerModel = opts.count
|
|
74
75
|
? parsePositiveInt(opts.count, "count")
|
|
75
76
|
: 1;
|
|
@@ -87,6 +88,10 @@ export function registerTextCommand(program: Command) {
|
|
|
87
88
|
async (modelId) => {
|
|
88
89
|
const abort = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
89
90
|
const result = await generateText({
|
|
91
|
+
headers: {
|
|
92
|
+
"http-referer": "https://github.com/vercel-labs/ai-cli",
|
|
93
|
+
"x-title": "ai-cli",
|
|
94
|
+
},
|
|
90
95
|
model: gateway(modelId),
|
|
91
96
|
prompt: fullPrompt,
|
|
92
97
|
system: opts.system,
|
package/src/commands/video.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { experimental_generateVideo as generateVideo, gateway } from "ai";
|
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
import { buildJobs, runJobs } from "../lib/jobs.js";
|
|
5
|
-
import { resolveModels } from "../lib/models.js";
|
|
5
|
+
import { fetchGatewayModels, resolveModels } from "../lib/models.js";
|
|
6
6
|
import {
|
|
7
7
|
parsePositiveInt,
|
|
8
8
|
parseAspectRatio,
|
|
@@ -65,7 +65,8 @@ export function registerVideoCommand(program: Command) {
|
|
|
65
65
|
: { image: new Uint8Array(stdin) };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const
|
|
68
|
+
const gatewayModels = await fetchGatewayModels();
|
|
69
|
+
const models = resolveModels("video", opts.model, gatewayModels.video);
|
|
69
70
|
const countPerModel = opts.count
|
|
70
71
|
? parsePositiveInt(opts.count, "count")
|
|
71
72
|
: 1;
|
|
@@ -83,6 +84,10 @@ export function registerVideoCommand(program: Command) {
|
|
|
83
84
|
async (modelId) => {
|
|
84
85
|
const abort = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
85
86
|
const result = await generateVideo({
|
|
87
|
+
headers: {
|
|
88
|
+
"http-referer": "https://github.com/vercel-labs/ai-cli",
|
|
89
|
+
"x-title": "ai-cli",
|
|
90
|
+
},
|
|
86
91
|
model: gateway.video(modelId),
|
|
87
92
|
prompt: videoPrompt,
|
|
88
93
|
abortSignal: abort,
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
import pkg from "../package.json";
|
|
5
|
-
import { registerCompletionsCommand } from "./commands/completions.js";
|
|
6
5
|
import { registerImageCommand } from "./commands/image.js";
|
|
7
6
|
import { registerModelsCommand } from "./commands/models.js";
|
|
8
7
|
import { registerTextCommand } from "./commands/text.js";
|
|
@@ -21,7 +20,6 @@ registerTextCommand(program);
|
|
|
21
20
|
registerImageCommand(program);
|
|
22
21
|
registerVideoCommand(program);
|
|
23
22
|
registerModelsCommand(program);
|
|
24
|
-
registerCompletionsCommand(program);
|
|
25
23
|
|
|
26
24
|
program.parseAsync(process.argv).catch((err: unknown) => {
|
|
27
25
|
const msg = err instanceof Error ? err.message : String(err);
|