agnes-ai-cli 0.1.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/LICENSE +21 -0
- package/README.md +60 -0
- package/bin/agnes.js +5 -0
- package/dist/auth/check.d.ts +7 -0
- package/dist/auth/check.js +10 -0
- package/dist/auth/saveKey.d.ts +12 -0
- package/dist/auth/saveKey.js +43 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +431 -0
- package/dist/config.d.ts +70 -0
- package/dist/config.js +24 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +15 -0
- package/dist/http/requestJson.d.ts +8 -0
- package/dist/http/requestJson.js +26 -0
- package/dist/image/generateImage.d.ts +3 -0
- package/dist/image/generateImage.js +62 -0
- package/dist/image/normalizeImageRequest.d.ts +53 -0
- package/dist/image/normalizeImageRequest.js +67 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +29 -0
- package/dist/media/litterbox.d.ts +9 -0
- package/dist/media/litterbox.js +69 -0
- package/dist/media/toPublicUrl.d.ts +4 -0
- package/dist/media/toPublicUrl.js +21 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.js +9 -0
- package/dist/text/complete.d.ts +16 -0
- package/dist/text/complete.js +58 -0
- package/dist/video/generateVideo.d.ts +6 -0
- package/dist/video/generateVideo.js +77 -0
- package/dist/video/normalizeVideoRequest.d.ts +72 -0
- package/dist/video/normalizeVideoRequest.js +83 -0
- package/dist/video/pollVideo.d.ts +7 -0
- package/dist/video/pollVideo.js +83 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jomeswang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# agnes-ai-cli
|
|
2
|
+
|
|
3
|
+
CLI and JS API for Agnes text, image, and video workflows.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Current verified distribution path:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g github:jomeswang/agnes-ai-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Ad hoc execution without a global install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y github:jomeswang/agnes-ai-cli --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Planned npm package name:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
agnes-ai-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Once the npm publish path is cleared, the package will also be installable as:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g agnes-ai-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## CLI
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
agnes --help
|
|
35
|
+
agnes auth check
|
|
36
|
+
agnes media url ./input.png
|
|
37
|
+
agnes text chat --prompt "say pong"
|
|
38
|
+
agnes image text2img --prompt "a polished product photo of a perfume bottle"
|
|
39
|
+
agnes image img2img --image ./input.png --prompt "turn this into a clean editorial poster"
|
|
40
|
+
agnes video text2video --prompt "a cinematic drone shot of waves at dusk"
|
|
41
|
+
agnes video img2video --image ./frame.png --prompt "animate subtle rain and drifting fog"
|
|
42
|
+
agnes video multivideo --image ./frame-a.png --image ./frame-b.png --prompt "blend these references into one motion concept"
|
|
43
|
+
agnes video keyframes --image ./frame-a.png --image ./frame-b.png --prompt "morph between the two scenes"
|
|
44
|
+
agnes video poll <task-id>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## JS API
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { createAgnesClient } from "agnes-ai-cli";
|
|
51
|
+
|
|
52
|
+
const agnes = createAgnesClient({ apiKey: process.env.AGNES_API_KEY });
|
|
53
|
+
|
|
54
|
+
const image = await agnes.image.generate({
|
|
55
|
+
mode: "text2img",
|
|
56
|
+
prompt: "A cinematic product shot of a silver watch on black stone."
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(image.url);
|
|
60
|
+
```
|
package/bin/agnes.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { resolveConfig } from "../config.js";
|
|
2
|
+
export function checkAuth(config) {
|
|
3
|
+
const resolved = resolveConfig({ env: config?.env });
|
|
4
|
+
const configured = Boolean(resolved.apiKey);
|
|
5
|
+
return {
|
|
6
|
+
ok: configured,
|
|
7
|
+
configured,
|
|
8
|
+
source: configured ? "env" : "missing",
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SaveKeyOptions {
|
|
2
|
+
shell?: string;
|
|
3
|
+
homeDir?: string;
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
}
|
|
6
|
+
export interface SaveKeyResult {
|
|
7
|
+
ok: true;
|
|
8
|
+
rcFile: string;
|
|
9
|
+
variable: "AGNES_API_KEY";
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveRcFile(shellName: string | undefined, homeDir: string): string;
|
|
12
|
+
export declare function saveKeyToShell(key: string, options?: SaveKeyOptions): Promise<SaveKeyResult>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { AgnesCliError } from "../errors.js";
|
|
4
|
+
export function resolveRcFile(shellName, homeDir) {
|
|
5
|
+
if (shellName === "zsh")
|
|
6
|
+
return join(homeDir, ".zshrc");
|
|
7
|
+
if (shellName === "bash")
|
|
8
|
+
return join(homeDir, ".bashrc");
|
|
9
|
+
return join(homeDir, ".profile");
|
|
10
|
+
}
|
|
11
|
+
export async function saveKeyToShell(key, options = {}) {
|
|
12
|
+
if (!key || !key.trim()) {
|
|
13
|
+
throw new AgnesCliError("INVALID_KEY", "Agnes API key is required.");
|
|
14
|
+
}
|
|
15
|
+
const env = options.env ?? process.env;
|
|
16
|
+
const shellName = options.shell ?? (env.SHELL ? env.SHELL.split("/").pop() : undefined);
|
|
17
|
+
const homeDir = options.homeDir ?? env.HOME;
|
|
18
|
+
if (!homeDir) {
|
|
19
|
+
throw new AgnesCliError("HOME_MISSING", "HOME is not available, so the shell rc file cannot be resolved.");
|
|
20
|
+
}
|
|
21
|
+
const rcFile = resolveRcFile(shellName, homeDir);
|
|
22
|
+
let existing = "";
|
|
23
|
+
try {
|
|
24
|
+
existing = await fs.readFile(rcFile, "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== "ENOENT")
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const filtered = existing
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((line) => !line.startsWith("export AGNES_API_KEY="))
|
|
33
|
+
.join("\n")
|
|
34
|
+
.replace(/\n*$/, "\n");
|
|
35
|
+
const escaped = shellEscape(key);
|
|
36
|
+
const next = `${filtered}\nexport AGNES_API_KEY=${escaped}\n`;
|
|
37
|
+
await fs.writeFile(rcFile, next, "utf8");
|
|
38
|
+
process.env.AGNES_API_KEY = key;
|
|
39
|
+
return { ok: true, rcFile, variable: "AGNES_API_KEY" };
|
|
40
|
+
}
|
|
41
|
+
function shellEscape(value) {
|
|
42
|
+
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
43
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCli(argv?: string[]): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { saveKeyToShell } from "./auth/saveKey.js";
|
|
5
|
+
import { createAgnesClient } from "./index.js";
|
|
6
|
+
import { isAgnesCliError } from "./errors.js";
|
|
7
|
+
import { printJson, printLines } from "./output.js";
|
|
8
|
+
export async function runCli(argv = process.argv) {
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name("agnes")
|
|
12
|
+
.description("CLI for Agnes AI text, image, and video workflows, with auth and media URL bridging")
|
|
13
|
+
.version("0.1.0")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Examples:
|
|
16
|
+
agnes auth check
|
|
17
|
+
agnes media url ./input.png
|
|
18
|
+
agnes image img2img --image ./input.png --prompt "Turn this into an editorial campaign"
|
|
19
|
+
agnes video keyframes --image ./a.png --image ./b.png --prompt "Transition between these frames"
|
|
20
|
+
|
|
21
|
+
Use this CLI when you want explicit Agnes request-shape handling, local-file URL bridging, or stable JSON output for agents.
|
|
22
|
+
`);
|
|
23
|
+
const client = createAgnesClient();
|
|
24
|
+
const auth = program.command("auth").description("Inspect and configure Agnes auth");
|
|
25
|
+
auth.addHelpText("after", `
|
|
26
|
+
Examples:
|
|
27
|
+
agnes auth check
|
|
28
|
+
agnes auth save-key --key sk-...
|
|
29
|
+
|
|
30
|
+
Use this group to verify AGNES_API_KEY or persist it into the active shell rc file.
|
|
31
|
+
`);
|
|
32
|
+
auth
|
|
33
|
+
.command("check")
|
|
34
|
+
.description("Check whether AGNES_API_KEY is configured")
|
|
35
|
+
.option("--json", "Output JSON")
|
|
36
|
+
.action((options) => {
|
|
37
|
+
const result = client.auth.check();
|
|
38
|
+
if (options.json) {
|
|
39
|
+
printJson(result);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
printLines([
|
|
43
|
+
result.configured ? "AGNES_API_KEY is configured." : "AGNES_API_KEY is missing.",
|
|
44
|
+
`source: ${result.source}`,
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
auth
|
|
48
|
+
.command("save-key")
|
|
49
|
+
.description("Persist AGNES_API_KEY into the current shell rc file")
|
|
50
|
+
.requiredOption("--key <value>", "Agnes API key to persist")
|
|
51
|
+
.option("--json", "Output JSON")
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
const result = await saveKeyToShell(options.key);
|
|
54
|
+
if (options.json) {
|
|
55
|
+
printJson(result);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
printLines([
|
|
59
|
+
"AGNES_API_KEY saved.",
|
|
60
|
+
`rc file: ${result.rcFile}`,
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
const media = program.command("media").description("Resolve Agnes media inputs");
|
|
64
|
+
media.addHelpText("after", `
|
|
65
|
+
Examples:
|
|
66
|
+
agnes media url ./frame.png
|
|
67
|
+
agnes media url https://example.com/frame.png
|
|
68
|
+
|
|
69
|
+
Use this group when Agnes requires a public image URL and your source asset is a local file path.
|
|
70
|
+
`);
|
|
71
|
+
media
|
|
72
|
+
.command("url")
|
|
73
|
+
.description("Return a public URL for a local file or pass through an existing URL")
|
|
74
|
+
.argument("<file-or-url>", "Local file path or http(s) URL")
|
|
75
|
+
.option("--ttl <ttl>", "Litterbox TTL (1h, 12h, 24h, 72h)", "1h")
|
|
76
|
+
.option("--json", "Output JSON")
|
|
77
|
+
.action(async (input, options) => {
|
|
78
|
+
const result = await client.media.toPublicUrl(input, { ttl: ttlSchema.parse(options.ttl) });
|
|
79
|
+
if (options.json) {
|
|
80
|
+
printJson(result);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(result.url);
|
|
84
|
+
});
|
|
85
|
+
const text = program.command("text").description("Run Agnes text workflows");
|
|
86
|
+
text.addHelpText("after", `
|
|
87
|
+
Examples:
|
|
88
|
+
agnes text chat --prompt "Reply with exactly pong."
|
|
89
|
+
|
|
90
|
+
Use this group for Agnes chat/completions request shapes when you want a minimal one-shot text call through the CLI.
|
|
91
|
+
`);
|
|
92
|
+
text
|
|
93
|
+
.command("chat")
|
|
94
|
+
.description("Run a one-shot Agnes chat completion")
|
|
95
|
+
.requiredOption("--prompt <text>", "Prompt text")
|
|
96
|
+
.option("--model <model>", "Agnes text model", "agnes-2.0-flash")
|
|
97
|
+
.option("--json", "Output JSON")
|
|
98
|
+
.addHelpText("after", `
|
|
99
|
+
Example:
|
|
100
|
+
agnes text chat --prompt "Reply with exactly pong."
|
|
101
|
+
|
|
102
|
+
Request shape:
|
|
103
|
+
Sends a single-user-message Agnes /chat/completions request with agnes-2.0-flash by default.
|
|
104
|
+
`)
|
|
105
|
+
.action(async (options) => {
|
|
106
|
+
const result = await client.text.complete({
|
|
107
|
+
prompt: options.prompt,
|
|
108
|
+
model: options.model,
|
|
109
|
+
});
|
|
110
|
+
if (options.json) {
|
|
111
|
+
printJson(result);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
console.log(result.text);
|
|
115
|
+
});
|
|
116
|
+
const image = program.command("image").description("Generate Agnes images");
|
|
117
|
+
image.addHelpText("after", `
|
|
118
|
+
Examples:
|
|
119
|
+
agnes image text2img --prompt "A luminous city at sunrise"
|
|
120
|
+
agnes image img2img --image ./input.png --prompt "Restyle this frame for a premium campaign"
|
|
121
|
+
agnes image compose --image ./a.png --image ./b.png --prompt "Blend these references"
|
|
122
|
+
|
|
123
|
+
Use this group for /images/generations request shapes. text2img sends prompt-only payloads. img2img sends a single image URL. compose sends an image URL array.
|
|
124
|
+
`);
|
|
125
|
+
image
|
|
126
|
+
.command("text2img")
|
|
127
|
+
.description("Generate an image from text")
|
|
128
|
+
.requiredOption("--prompt <text>", "Prompt text")
|
|
129
|
+
.option("--model <model>", "Agnes image model")
|
|
130
|
+
.option("--size <size>", "Image size, e.g. 1024x768")
|
|
131
|
+
.option("--seed <number>", "Seed", parseInteger)
|
|
132
|
+
.option("--json", "Output JSON")
|
|
133
|
+
.addHelpText("after", `
|
|
134
|
+
Example:
|
|
135
|
+
agnes image text2img --prompt "A luminous city at sunrise" --size 1024x1024
|
|
136
|
+
|
|
137
|
+
Request shape:
|
|
138
|
+
Sends prompt-only generation to Agnes /images/generations with agnes-image-2.1-flash by default.
|
|
139
|
+
`)
|
|
140
|
+
.action(async (options) => {
|
|
141
|
+
const result = await client.image.generate({
|
|
142
|
+
mode: "text2img",
|
|
143
|
+
prompt: options.prompt,
|
|
144
|
+
model: options.model,
|
|
145
|
+
size: options.size,
|
|
146
|
+
seed: options.seed,
|
|
147
|
+
});
|
|
148
|
+
if (options.json) {
|
|
149
|
+
printJson(result);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
console.log(result.url);
|
|
153
|
+
});
|
|
154
|
+
image
|
|
155
|
+
.command("img2img")
|
|
156
|
+
.description("Generate an image from one input image")
|
|
157
|
+
.requiredOption("--image <path-or-url>", "Input image path or URL")
|
|
158
|
+
.requiredOption("--prompt <text>", "Prompt text")
|
|
159
|
+
.option("--model <model>", "Agnes image model")
|
|
160
|
+
.option("--size <size>", "Image size, e.g. 1024x768")
|
|
161
|
+
.option("--seed <number>", "Seed", parseInteger)
|
|
162
|
+
.option("--ttl <ttl>", "Temporary upload TTL", "1h")
|
|
163
|
+
.option("--json", "Output JSON")
|
|
164
|
+
.addHelpText("after", `
|
|
165
|
+
Example:
|
|
166
|
+
agnes image img2img --image ./input.png --prompt "Turn this into an editorial travel poster"
|
|
167
|
+
|
|
168
|
+
Request shape:
|
|
169
|
+
Resolves the local file to a public URL when needed, then sends a single Agnes image URL for image-to-image generation.
|
|
170
|
+
`)
|
|
171
|
+
.action(async (options) => {
|
|
172
|
+
const result = await client.image.generate({
|
|
173
|
+
mode: "img2img",
|
|
174
|
+
image: options.image,
|
|
175
|
+
prompt: options.prompt,
|
|
176
|
+
model: options.model,
|
|
177
|
+
size: options.size,
|
|
178
|
+
seed: options.seed,
|
|
179
|
+
ttl: ttlSchema.parse(options.ttl),
|
|
180
|
+
});
|
|
181
|
+
if (options.json) {
|
|
182
|
+
printJson(result);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
console.log(result.url);
|
|
186
|
+
});
|
|
187
|
+
image
|
|
188
|
+
.command("compose")
|
|
189
|
+
.description("Generate an image from multiple input images")
|
|
190
|
+
.requiredOption("--image <path-or-url...>", "Repeatable image path or URL")
|
|
191
|
+
.requiredOption("--prompt <text>", "Prompt text")
|
|
192
|
+
.option("--model <model>", "Agnes image model")
|
|
193
|
+
.option("--size <size>", "Image size, e.g. 1024x768")
|
|
194
|
+
.option("--seed <number>", "Seed", parseInteger)
|
|
195
|
+
.option("--ttl <ttl>", "Temporary upload TTL", "1h")
|
|
196
|
+
.option("--json", "Output JSON")
|
|
197
|
+
.addHelpText("after", `
|
|
198
|
+
Example:
|
|
199
|
+
agnes image compose --image ./a.png --image ./b.png --prompt "Blend these references into one campaign frame"
|
|
200
|
+
|
|
201
|
+
Request shape:
|
|
202
|
+
Resolves each input to a public URL, preserves image order, and sends an Agnes image URL array for multi-image composition.
|
|
203
|
+
`)
|
|
204
|
+
.action(async (options) => {
|
|
205
|
+
const images = toArray(options.image);
|
|
206
|
+
const result = await client.image.generate({
|
|
207
|
+
mode: "compose",
|
|
208
|
+
images,
|
|
209
|
+
prompt: options.prompt,
|
|
210
|
+
model: options.model,
|
|
211
|
+
size: options.size,
|
|
212
|
+
seed: options.seed,
|
|
213
|
+
ttl: ttlSchema.parse(options.ttl),
|
|
214
|
+
});
|
|
215
|
+
if (options.json) {
|
|
216
|
+
printJson(result);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
console.log(result.url);
|
|
220
|
+
});
|
|
221
|
+
const video = program.command("video").description("Generate Agnes videos");
|
|
222
|
+
video.addHelpText("after", `
|
|
223
|
+
Examples:
|
|
224
|
+
agnes video text2video --prompt "A cinematic beach scene at sunset"
|
|
225
|
+
agnes video img2video --image ./frame.png --prompt "Add gentle wind and a soft push-in"
|
|
226
|
+
agnes video keyframes --image ./a.png --image ./b.png --prompt "Transition between the frames"
|
|
227
|
+
agnes video poll task_123
|
|
228
|
+
|
|
229
|
+
Use this group for Agnes video task creation and polling. Video creation is asynchronous; poll returns the final result.
|
|
230
|
+
`);
|
|
231
|
+
buildVideoGenerateCommand(video, "text2video", "Generate a video from text", async (client, options) => client.video.generate({
|
|
232
|
+
mode: "text2video",
|
|
233
|
+
prompt: options.prompt,
|
|
234
|
+
width: options.width,
|
|
235
|
+
height: options.height,
|
|
236
|
+
numFrames: options.numFrames,
|
|
237
|
+
frameRate: options.frameRate,
|
|
238
|
+
seed: options.seed,
|
|
239
|
+
negativePrompt: options.negativePrompt,
|
|
240
|
+
}));
|
|
241
|
+
buildVideoGenerateCommand(video, "img2video", "Generate a video from one input image", async (client, options) => client.video.generate({
|
|
242
|
+
mode: "img2video",
|
|
243
|
+
image: Array.isArray(options.image) ? options.image[0] : options.image,
|
|
244
|
+
prompt: options.prompt,
|
|
245
|
+
width: options.width,
|
|
246
|
+
height: options.height,
|
|
247
|
+
numFrames: options.numFrames,
|
|
248
|
+
frameRate: options.frameRate,
|
|
249
|
+
seed: options.seed,
|
|
250
|
+
negativePrompt: options.negativePrompt,
|
|
251
|
+
ttl: ttlSchema.parse(options.ttl),
|
|
252
|
+
}), "single");
|
|
253
|
+
buildVideoGenerateCommand(video, "multivideo", "Generate a video from multiple input images", async (client, options) => client.video.generate({
|
|
254
|
+
mode: "multivideo",
|
|
255
|
+
images: toArray(options.image),
|
|
256
|
+
prompt: options.prompt,
|
|
257
|
+
width: options.width,
|
|
258
|
+
height: options.height,
|
|
259
|
+
numFrames: options.numFrames,
|
|
260
|
+
frameRate: options.frameRate,
|
|
261
|
+
seed: options.seed,
|
|
262
|
+
negativePrompt: options.negativePrompt,
|
|
263
|
+
ttl: ttlSchema.parse(options.ttl),
|
|
264
|
+
}), "multi");
|
|
265
|
+
buildVideoGenerateCommand(video, "keyframes", "Generate a keyframe video from multiple images", async (client, options) => client.video.generate({
|
|
266
|
+
mode: "keyframes",
|
|
267
|
+
images: toArray(options.image),
|
|
268
|
+
prompt: options.prompt,
|
|
269
|
+
width: options.width,
|
|
270
|
+
height: options.height,
|
|
271
|
+
numFrames: options.numFrames,
|
|
272
|
+
frameRate: options.frameRate,
|
|
273
|
+
seed: options.seed,
|
|
274
|
+
negativePrompt: options.negativePrompt,
|
|
275
|
+
ttl: ttlSchema.parse(options.ttl),
|
|
276
|
+
}), "multi");
|
|
277
|
+
video
|
|
278
|
+
.command("poll")
|
|
279
|
+
.description("Poll an Agnes video task until it finishes")
|
|
280
|
+
.argument("<task-id>", "Task id returned by Agnes video creation")
|
|
281
|
+
.option("--interval <seconds>", "Polling interval seconds", parseInteger, 3)
|
|
282
|
+
.option("--timeout <seconds>", "Polling timeout seconds", parseInteger, 600)
|
|
283
|
+
.option("--json", "Output JSON")
|
|
284
|
+
.addHelpText("after", `
|
|
285
|
+
Example:
|
|
286
|
+
agnes video poll task_123 --interval 3 --timeout 600
|
|
287
|
+
|
|
288
|
+
Request shape:
|
|
289
|
+
Polls Agnes /videos/{task_id} until the task completes, fails, or times out. Use this after any asynchronous video creation command.
|
|
290
|
+
`)
|
|
291
|
+
.action(async (taskId, options) => {
|
|
292
|
+
const result = await client.video.poll(taskId, {
|
|
293
|
+
intervalSeconds: options.interval,
|
|
294
|
+
timeoutSeconds: options.timeout,
|
|
295
|
+
});
|
|
296
|
+
if (options.json) {
|
|
297
|
+
printJson(result);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
printLines([
|
|
301
|
+
result.videoUrl,
|
|
302
|
+
`status: ${result.status}`,
|
|
303
|
+
result.seconds !== undefined ? `seconds: ${result.seconds}` : undefined,
|
|
304
|
+
result.size ? `size: ${result.size}` : undefined,
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
try {
|
|
308
|
+
await program.parseAsync(argv);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
handleCliError(error, argv);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function buildVideoGenerateCommand(video, name, description, fn, imageMode = false) {
|
|
315
|
+
let command = video
|
|
316
|
+
.command(name)
|
|
317
|
+
.description(description)
|
|
318
|
+
.requiredOption("--prompt <text>", "Prompt text")
|
|
319
|
+
.option("--width <number>", "Video width", parseInteger)
|
|
320
|
+
.option("--height <number>", "Video height", parseInteger)
|
|
321
|
+
.option("--num-frames <number>", "Video frame count, must satisfy 8n + 1", parseInteger)
|
|
322
|
+
.option("--frame-rate <number>", "Frame rate (1-60)", parseInteger)
|
|
323
|
+
.option("--seed <number>", "Seed", parseInteger)
|
|
324
|
+
.option("--negative-prompt <text>", "Negative prompt")
|
|
325
|
+
.option("--json", "Output JSON");
|
|
326
|
+
if (imageMode === "single") {
|
|
327
|
+
command = command
|
|
328
|
+
.requiredOption("--image <path-or-url>", "Input image path or URL")
|
|
329
|
+
.option("--ttl <ttl>", "Temporary upload TTL", "1h");
|
|
330
|
+
}
|
|
331
|
+
else if (imageMode === "multi") {
|
|
332
|
+
command = command
|
|
333
|
+
.requiredOption("--image <path-or-url...>", "Repeatable image path or URL")
|
|
334
|
+
.option("--ttl <ttl>", "Temporary upload TTL", "1h");
|
|
335
|
+
}
|
|
336
|
+
command.addHelpText("after", buildVideoHelp(name, imageMode));
|
|
337
|
+
command.action(async (options) => {
|
|
338
|
+
const client = createAgnesClient();
|
|
339
|
+
const result = await fn(client, options);
|
|
340
|
+
if (options.json) {
|
|
341
|
+
printJson(result);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const task = result;
|
|
345
|
+
printLines([
|
|
346
|
+
`taskId: ${task.taskId}`,
|
|
347
|
+
`status: ${task.status}`,
|
|
348
|
+
]);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function handleCliError(error, argv) {
|
|
352
|
+
const wantsJson = argv.includes("--json");
|
|
353
|
+
if (isAgnesCliError(error)) {
|
|
354
|
+
if (wantsJson) {
|
|
355
|
+
printJson({
|
|
356
|
+
ok: false,
|
|
357
|
+
code: error.code,
|
|
358
|
+
message: error.message,
|
|
359
|
+
...toJsonDetails(error.details),
|
|
360
|
+
});
|
|
361
|
+
process.exit(error.exitCode);
|
|
362
|
+
}
|
|
363
|
+
console.error(error.message);
|
|
364
|
+
process.exit(error.exitCode);
|
|
365
|
+
}
|
|
366
|
+
if (error instanceof z.ZodError) {
|
|
367
|
+
if (wantsJson) {
|
|
368
|
+
printJson({
|
|
369
|
+
ok: false,
|
|
370
|
+
code: "INVALID_ARGUMENTS",
|
|
371
|
+
message: error.issues.map((issue) => issue.message).join("\n"),
|
|
372
|
+
});
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
console.error(error.issues.map((issue) => issue.message).join("\n"));
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
if (wantsJson) {
|
|
379
|
+
printJson({
|
|
380
|
+
ok: false,
|
|
381
|
+
code: "UNEXPECTED_ERROR",
|
|
382
|
+
message: error instanceof Error ? error.message : String(error),
|
|
383
|
+
});
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
function parseInteger(value) {
|
|
390
|
+
return Number.parseInt(value, 10);
|
|
391
|
+
}
|
|
392
|
+
function toArray(value) {
|
|
393
|
+
return Array.isArray(value) ? value : [value];
|
|
394
|
+
}
|
|
395
|
+
const ttlSchema = z.enum(["1h", "12h", "24h", "72h"]);
|
|
396
|
+
function toJsonDetails(details) {
|
|
397
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) {
|
|
398
|
+
return {};
|
|
399
|
+
}
|
|
400
|
+
return details;
|
|
401
|
+
}
|
|
402
|
+
function buildVideoHelp(name, imageMode) {
|
|
403
|
+
const examples = {
|
|
404
|
+
text2video: 'agnes video text2video --prompt "A cinematic beach scene at sunset"',
|
|
405
|
+
img2video: 'agnes video img2video --image ./frame.png --prompt "Add gentle wind and a soft push-in"',
|
|
406
|
+
multivideo: 'agnes video multivideo --image ./a.png --image ./b.png --prompt "Blend these references into one motion concept"',
|
|
407
|
+
keyframes: 'agnes video keyframes --image ./a.png --image ./b.png --prompt "Transition between these frames"',
|
|
408
|
+
};
|
|
409
|
+
const shapeNotes = {
|
|
410
|
+
text2video: "Sends a prompt-only Agnes video task payload.",
|
|
411
|
+
img2video: "Resolves the input file to a public URL when needed, then sends a single top-level image field.",
|
|
412
|
+
multivideo: "Resolves each input to a public URL and sends an ordered Agnes image URL array in extra_body.image.",
|
|
413
|
+
keyframes: "Uses Agnes keyframes mode. Pass --image at least twice; each image keeps its original order and is sent in extra_body.image with extra_body.mode=keyframes.",
|
|
414
|
+
};
|
|
415
|
+
const imageNote = imageMode === "multi"
|
|
416
|
+
? "Required image input: repeat --image two or more times."
|
|
417
|
+
: imageMode === "single"
|
|
418
|
+
? "Required image input: provide exactly one --image."
|
|
419
|
+
: "No image input is required for this mode.";
|
|
420
|
+
return `
|
|
421
|
+
Example:
|
|
422
|
+
${examples[name] ?? `agnes video ${name} --help`}
|
|
423
|
+
|
|
424
|
+
Request shape:
|
|
425
|
+
${shapeNotes[name] ?? "Creates an Agnes video task."}
|
|
426
|
+
${imageNote}
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
430
|
+
void runCli(process.argv);
|
|
431
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type Ttl = "1h" | "12h" | "24h" | "72h";
|
|
2
|
+
export type FetchLike = typeof fetch;
|
|
3
|
+
export interface AgnesClientConfig {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
fetchImpl?: FetchLike;
|
|
7
|
+
defaultMediaTtl?: Ttl;
|
|
8
|
+
mediaProvider?: MediaUrlProvider;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
export interface MediaUrlProvider {
|
|
12
|
+
upload(localPath: string, options?: {
|
|
13
|
+
ttl?: Ttl;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedConfig {
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
fetchImpl: FetchLike;
|
|
20
|
+
defaultMediaTtl: Ttl;
|
|
21
|
+
mediaProvider?: MediaUrlProvider;
|
|
22
|
+
env: NodeJS.ProcessEnv;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_BASE_URL = "https://apihub.agnes-ai.com/v1";
|
|
25
|
+
export declare const DEFAULT_MEDIA_TTL: Ttl;
|
|
26
|
+
export declare const DEFAULT_VIDEO_DIMENSIONS: {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
export declare const DEFAULT_VIDEO_TEMPORAL: {
|
|
31
|
+
numFrames: number;
|
|
32
|
+
frameRate: number;
|
|
33
|
+
};
|
|
34
|
+
export declare function resolveConfig(config?: AgnesClientConfig): ResolvedConfig;
|
|
35
|
+
export interface AuthCheckResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
configured: boolean;
|
|
38
|
+
source: "env" | "missing";
|
|
39
|
+
}
|
|
40
|
+
export interface PublicUrlResult {
|
|
41
|
+
ok: true;
|
|
42
|
+
url: string;
|
|
43
|
+
source: "passthrough" | "litterbox" | "provider";
|
|
44
|
+
}
|
|
45
|
+
export type AgnesStatus = "queued" | "in_progress" | "completed" | "failed" | "timed_out";
|
|
46
|
+
export interface NormalizedVideoTask {
|
|
47
|
+
ok: true;
|
|
48
|
+
taskId: string;
|
|
49
|
+
status: AgnesStatus;
|
|
50
|
+
rawStatus?: string;
|
|
51
|
+
model: string;
|
|
52
|
+
raw: unknown;
|
|
53
|
+
}
|
|
54
|
+
export interface NormalizedVideoResult {
|
|
55
|
+
ok: true;
|
|
56
|
+
taskId: string;
|
|
57
|
+
status: AgnesStatus;
|
|
58
|
+
rawStatus?: string;
|
|
59
|
+
model: string;
|
|
60
|
+
videoUrl: string;
|
|
61
|
+
seconds?: number;
|
|
62
|
+
size?: string;
|
|
63
|
+
raw: unknown;
|
|
64
|
+
}
|
|
65
|
+
export interface ImageGenerationResult {
|
|
66
|
+
ok: true;
|
|
67
|
+
model: string;
|
|
68
|
+
url: string;
|
|
69
|
+
raw: unknown;
|
|
70
|
+
}
|