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.
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { type Ttl } from "../config.js";
3
+ export declare const videoGenerateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ prompt: z.ZodString;
5
+ width: z.ZodOptional<z.ZodNumber>;
6
+ height: z.ZodOptional<z.ZodNumber>;
7
+ numFrames: z.ZodOptional<z.ZodNumber>;
8
+ frameRate: z.ZodOptional<z.ZodNumber>;
9
+ seed: z.ZodOptional<z.ZodNumber>;
10
+ negativePrompt: z.ZodOptional<z.ZodString>;
11
+ mode: z.ZodLiteral<"text2video">;
12
+ }, z.core.$strip>, z.ZodObject<{
13
+ prompt: z.ZodString;
14
+ width: z.ZodOptional<z.ZodNumber>;
15
+ height: z.ZodOptional<z.ZodNumber>;
16
+ numFrames: z.ZodOptional<z.ZodNumber>;
17
+ frameRate: z.ZodOptional<z.ZodNumber>;
18
+ seed: z.ZodOptional<z.ZodNumber>;
19
+ negativePrompt: z.ZodOptional<z.ZodString>;
20
+ mode: z.ZodLiteral<"img2video">;
21
+ image: z.ZodString;
22
+ ttl: z.ZodOptional<z.ZodEnum<{
23
+ "1h": "1h";
24
+ "12h": "12h";
25
+ "24h": "24h";
26
+ "72h": "72h";
27
+ }>>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ prompt: z.ZodString;
30
+ width: z.ZodOptional<z.ZodNumber>;
31
+ height: z.ZodOptional<z.ZodNumber>;
32
+ numFrames: z.ZodOptional<z.ZodNumber>;
33
+ frameRate: z.ZodOptional<z.ZodNumber>;
34
+ seed: z.ZodOptional<z.ZodNumber>;
35
+ negativePrompt: z.ZodOptional<z.ZodString>;
36
+ mode: z.ZodLiteral<"multivideo">;
37
+ images: z.ZodArray<z.ZodString>;
38
+ ttl: z.ZodOptional<z.ZodEnum<{
39
+ "1h": "1h";
40
+ "12h": "12h";
41
+ "24h": "24h";
42
+ "72h": "72h";
43
+ }>>;
44
+ }, z.core.$strip>, z.ZodObject<{
45
+ prompt: z.ZodString;
46
+ width: z.ZodOptional<z.ZodNumber>;
47
+ height: z.ZodOptional<z.ZodNumber>;
48
+ numFrames: z.ZodOptional<z.ZodNumber>;
49
+ frameRate: z.ZodOptional<z.ZodNumber>;
50
+ seed: z.ZodOptional<z.ZodNumber>;
51
+ negativePrompt: z.ZodOptional<z.ZodString>;
52
+ mode: z.ZodLiteral<"keyframes">;
53
+ images: z.ZodArray<z.ZodString>;
54
+ ttl: z.ZodOptional<z.ZodEnum<{
55
+ "1h": "1h";
56
+ "12h": "12h";
57
+ "24h": "24h";
58
+ "72h": "72h";
59
+ }>>;
60
+ }, z.core.$strip>], "mode">;
61
+ export type VideoGenerateOptions = z.infer<typeof videoGenerateSchema>;
62
+ export declare function collectVideoInputs(options: VideoGenerateOptions): {
63
+ inputs: string[];
64
+ ttl?: Ttl;
65
+ };
66
+ export declare function normalizeVideoRequest(options: VideoGenerateOptions, resolvedImages: string[]): Record<string, unknown>;
67
+ export declare function validateVideoSettings({ numFrames, frameRate, mode, imageCount, }: {
68
+ numFrames: number;
69
+ frameRate: number;
70
+ mode: VideoGenerateOptions["mode"];
71
+ imageCount: number;
72
+ }): void;
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ import { DEFAULT_VIDEO_DIMENSIONS, DEFAULT_VIDEO_TEMPORAL } from "../config.js";
3
+ import { AgnesCliError } from "../errors.js";
4
+ const shared = {
5
+ prompt: z.string().min(1),
6
+ width: z.number().int().positive().optional(),
7
+ height: z.number().int().positive().optional(),
8
+ numFrames: z.number().int().positive().optional(),
9
+ frameRate: z.number().int().positive().optional(),
10
+ seed: z.number().int().optional(),
11
+ negativePrompt: z.string().min(1).optional(),
12
+ };
13
+ export const videoGenerateSchema = z.discriminatedUnion("mode", [
14
+ z.object({ mode: z.literal("text2video"), ...shared }),
15
+ z.object({ mode: z.literal("img2video"), image: z.string().min(1), ttl: z.enum(["1h", "12h", "24h", "72h"]).optional(), ...shared }),
16
+ z.object({ mode: z.literal("multivideo"), images: z.array(z.string().min(1)).min(2), ttl: z.enum(["1h", "12h", "24h", "72h"]).optional(), ...shared }),
17
+ z.object({ mode: z.literal("keyframes"), images: z.array(z.string().min(1)).min(2), ttl: z.enum(["1h", "12h", "24h", "72h"]).optional(), ...shared }),
18
+ ]);
19
+ export function collectVideoInputs(options) {
20
+ switch (options.mode) {
21
+ case "text2video":
22
+ return { inputs: [] };
23
+ case "img2video":
24
+ return { inputs: [options.image], ttl: options.ttl };
25
+ case "multivideo":
26
+ case "keyframes":
27
+ return { inputs: options.images, ttl: options.ttl };
28
+ default:
29
+ throw new AgnesCliError("INVALID_VIDEO_MODE", "Unsupported video generation mode.");
30
+ }
31
+ }
32
+ export function normalizeVideoRequest(options, resolvedImages) {
33
+ const width = options.width ?? DEFAULT_VIDEO_DIMENSIONS.width;
34
+ const height = options.height ?? DEFAULT_VIDEO_DIMENSIONS.height;
35
+ const numFrames = options.numFrames ?? DEFAULT_VIDEO_TEMPORAL.numFrames;
36
+ const frameRate = options.frameRate ?? DEFAULT_VIDEO_TEMPORAL.frameRate;
37
+ validateVideoSettings({ numFrames, frameRate, mode: options.mode, imageCount: resolvedImages.length });
38
+ const payload = {
39
+ model: "agnes-video-v2.0",
40
+ prompt: options.prompt,
41
+ width,
42
+ height,
43
+ num_frames: numFrames,
44
+ frame_rate: frameRate,
45
+ };
46
+ if (options.seed !== undefined)
47
+ payload.seed = options.seed;
48
+ if (options.negativePrompt)
49
+ payload.negative_prompt = options.negativePrompt;
50
+ switch (options.mode) {
51
+ case "text2video":
52
+ break;
53
+ case "img2video":
54
+ payload.image = resolvedImages[0];
55
+ break;
56
+ case "multivideo":
57
+ payload.extra_body = { image: resolvedImages };
58
+ break;
59
+ case "keyframes":
60
+ payload.extra_body = { image: resolvedImages, mode: "keyframes" };
61
+ break;
62
+ default:
63
+ throw new AgnesCliError("INVALID_VIDEO_MODE", "Unsupported video generation mode.");
64
+ }
65
+ return payload;
66
+ }
67
+ export function validateVideoSettings({ numFrames, frameRate, mode, imageCount, }) {
68
+ if (numFrames > 441) {
69
+ throw new AgnesCliError("INVALID_VIDEO_SETTINGS", "numFrames must be <= 441.");
70
+ }
71
+ if ((numFrames - 1) % 8 !== 0) {
72
+ throw new AgnesCliError("INVALID_VIDEO_SETTINGS", "numFrames must satisfy 8n + 1.");
73
+ }
74
+ if (frameRate < 1 || frameRate > 60) {
75
+ throw new AgnesCliError("INVALID_VIDEO_SETTINGS", "frameRate must be between 1 and 60.");
76
+ }
77
+ if (mode === "img2video" && imageCount !== 1) {
78
+ throw new AgnesCliError("INVALID_VIDEO_SETTINGS", "img2video requires exactly one image.");
79
+ }
80
+ if ((mode === "multivideo" || mode === "keyframes") && imageCount < 2) {
81
+ throw new AgnesCliError("INVALID_VIDEO_SETTINGS", `${mode} requires at least two images.`);
82
+ }
83
+ }
@@ -0,0 +1,7 @@
1
+ import type { AgnesClientConfig, NormalizedVideoResult } from "../config.js";
2
+ export interface PollVideoOptions {
3
+ intervalSeconds?: number;
4
+ timeoutSeconds?: number;
5
+ }
6
+ export declare function pollVideo(taskId: string, options?: PollVideoOptions, config?: AgnesClientConfig): Promise<NormalizedVideoResult>;
7
+ export declare function normalizeVideoResult(raw: unknown): NormalizedVideoResult;
@@ -0,0 +1,83 @@
1
+ import { resolveConfig } from "../config.js";
2
+ import { AgnesCliError } from "../errors.js";
3
+ import { requestJson } from "../http/requestJson.js";
4
+ import { extractTaskId, normalizeStatus } from "./generateVideo.js";
5
+ export async function pollVideo(taskId, options = {}, config = {}) {
6
+ const resolved = resolveConfig(config);
7
+ if (!resolved.apiKey) {
8
+ throw new AgnesCliError("AUTH_MISSING", "AGNES_API_KEY is required for Agnes requests.");
9
+ }
10
+ const intervalMs = (options.intervalSeconds ?? 3) * 1000;
11
+ const timeoutMs = (options.timeoutSeconds ?? 600) * 1000;
12
+ const started = Date.now();
13
+ while (true) {
14
+ if (Date.now() - started > timeoutMs) {
15
+ throw new AgnesCliError("POLL_TIMEOUT", "Agnes video polling timed out.", { taskId, status: "timed_out" });
16
+ }
17
+ const { response, raw } = await requestJson(resolved.fetchImpl, `${resolved.baseUrl}/videos/${taskId}`, {
18
+ headers: {
19
+ Authorization: `Bearer ${resolved.apiKey}`,
20
+ },
21
+ }, {
22
+ networkMessage: "Agnes video poll request failed before a response was received.",
23
+ });
24
+ if (response.status === 404) {
25
+ throw new AgnesCliError("TASK_NOT_FOUND", "Agnes video task was not found.", { taskId, status: "failed", raw });
26
+ }
27
+ if (response.status === 503) {
28
+ throw new AgnesCliError("SERVICE_BUSY", "Agnes video service is busy.", { taskId, status: "queued", raw });
29
+ }
30
+ if (!response.ok) {
31
+ throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes video poll failed with HTTP ${response.status}.`, { taskId, status: "failed", raw });
32
+ }
33
+ const result = normalizeVideoResult(raw);
34
+ if (result.status === "completed")
35
+ return result;
36
+ if (result.status === "failed") {
37
+ throw new AgnesCliError("TASK_FAILED", "Agnes video task failed.", result);
38
+ }
39
+ await sleep(intervalMs);
40
+ }
41
+ }
42
+ export function normalizeVideoResult(raw) {
43
+ if (!raw || typeof raw !== "object") {
44
+ throw new AgnesCliError("INVALID_RESPONSE", "Agnes video poll response was not an object.", raw);
45
+ }
46
+ const record = raw;
47
+ const taskId = extractTaskId(record);
48
+ const rawStatus = typeof record.status === "string" ? record.status : "queued";
49
+ const status = normalizeStatus(rawStatus);
50
+ const videoUrl = extractVideoUrl(record);
51
+ if (status === "completed" && !videoUrl) {
52
+ throw new AgnesCliError("VIDEO_URL_MISSING", "Completed Agnes video task did not include a video URL.", raw);
53
+ }
54
+ return {
55
+ ok: true,
56
+ taskId,
57
+ status: status,
58
+ rawStatus,
59
+ model: typeof record.model === "string" ? record.model : "agnes-video-v2.0",
60
+ videoUrl: videoUrl ?? "",
61
+ seconds: typeof record.seconds === "number" ? record.seconds : undefined,
62
+ size: typeof record.size === "string" ? record.size : undefined,
63
+ raw,
64
+ };
65
+ }
66
+ function extractVideoUrl(record) {
67
+ const candidates = [
68
+ record.video_url,
69
+ record.url,
70
+ record.output_url,
71
+ record.remixed_from_video_id,
72
+ typeof record.result === "object" && record.result ? record.result.video_url : undefined,
73
+ ];
74
+ for (const candidate of candidates) {
75
+ if (typeof candidate === "string" && /^https?:\/\//.test(candidate)) {
76
+ return candidate;
77
+ }
78
+ }
79
+ return undefined;
80
+ }
81
+ function sleep(ms) {
82
+ return new Promise((resolve) => setTimeout(resolve, ms));
83
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "agnes-ai-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI and JS API for Agnes text, image, and video workflows.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agnes": "bin/agnes.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "bin",
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "prepare": "npm run build",
28
+ "test": "npm run build && node --test",
29
+ "lint": "npm run build",
30
+ "prepublishOnly": "npm test"
31
+ },
32
+ "keywords": [
33
+ "agnes",
34
+ "ai",
35
+ "cli",
36
+ "image-generation",
37
+ "video-generation"
38
+ ],
39
+ "license": "MIT",
40
+ "dependencies": {
41
+ "commander": "^15.0.0",
42
+ "zod": "^4.1.12"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^24.10.1",
46
+ "typescript": "^5.9.3"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/jomeswang/agnes-ai-cli.git"
51
+ },
52
+ "homepage": "https://github.com/jomeswang/agnes-ai-cli#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/jomeswang/agnes-ai-cli/issues"
55
+ },
56
+ "author": "jomeswang"
57
+ }