zerocut-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/.eslintrc.js +11 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +6 -0
- package/README.md +205 -0
- package/dist/bin/zerocut.d.ts +2 -0
- package/dist/bin/zerocut.js +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +24 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +129 -0
- package/dist/commands/ffmpeg.d.ts +4 -0
- package/dist/commands/ffmpeg.js +32 -0
- package/dist/commands/foo.d.ts +4 -0
- package/dist/commands/foo.js +14 -0
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.js +14 -0
- package/dist/commands/image.d.ts +4 -0
- package/dist/commands/image.js +149 -0
- package/dist/commands/music.d.ts +4 -0
- package/dist/commands/music.js +74 -0
- package/dist/commands/pandoc.d.ts +4 -0
- package/dist/commands/pandoc.js +32 -0
- package/dist/commands/skill.d.ts +4 -0
- package/dist/commands/skill.js +24 -0
- package/dist/commands/tts.d.ts +4 -0
- package/dist/commands/tts.js +74 -0
- package/dist/commands/video.d.ts +4 -0
- package/dist/commands/video.js +166 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/services/cerevox.d.ts +34 -0
- package/dist/services/cerevox.js +256 -0
- package/dist/services/commandLoader.d.ts +3 -0
- package/dist/services/commandLoader.js +80 -0
- package/dist/services/config.d.ts +15 -0
- package/dist/services/config.js +235 -0
- package/dist/skill/SKILL.md +133 -0
- package/dist/types/command.d.ts +6 -0
- package/dist/types/command.js +2 -0
- package/dist/utils/progress.d.ts +1 -0
- package/dist/utils/progress.js +13 -0
- package/eslint.config.js +30 -0
- package/package.json +52 -0
- package/scripts/copy-skill-md.cjs +8 -0
- package/src/bin/zerocut.ts +3 -0
- package/src/cli.ts +25 -0
- package/src/commands/config.ts +130 -0
- package/src/commands/ffmpeg.ts +37 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +194 -0
- package/src/commands/music.ts +78 -0
- package/src/commands/pandoc.ts +37 -0
- package/src/commands/skill.ts +20 -0
- package/src/commands/tts.ts +80 -0
- package/src/commands/video.ts +202 -0
- package/src/index.ts +1 -0
- package/src/services/cerevox.ts +296 -0
- package/src/services/commandLoader.ts +42 -0
- package/src/services/config.ts +230 -0
- package/src/skill/SKILL.md +209 -0
- package/src/types/command.ts +7 -0
- package/src/utils/progress.ts +10 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "zerocut-cli-tools"
|
|
3
|
+
description: "Use ZeroCut CLI media and document tools. Invoke when user needs generate media, run ffmpeg/pandoc, sync resources, or save outputs."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ZeroCut CLI Tools
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
This skill provides a single reference for using ZeroCut CLI commands:
|
|
11
|
+
- image generation
|
|
12
|
+
- video generation
|
|
13
|
+
- music generation
|
|
14
|
+
- text-to-speech
|
|
15
|
+
- ffmpeg sandbox execution
|
|
16
|
+
- pandoc sandbox execution
|
|
17
|
+
|
|
18
|
+
## When To Invoke
|
|
19
|
+
|
|
20
|
+
Invoke this skill when the user asks to:
|
|
21
|
+
- generate image, video, music, or speech audio
|
|
22
|
+
- run ffmpeg or ffprobe command in sandbox
|
|
23
|
+
- run pandoc conversion in sandbox
|
|
24
|
+
- sync local/remote resources into sandbox
|
|
25
|
+
- save generated results to local output files
|
|
26
|
+
|
|
27
|
+
## Command Reference
|
|
28
|
+
|
|
29
|
+
### image
|
|
30
|
+
|
|
31
|
+
Default action: `create`
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
zerocut image --prompt "a cat on a bike" --output out.png
|
|
35
|
+
zerocut image create --prompt "a cat on a bike" --model seedream-5l --aspectRatio 1:1 --resolution 1K --refs ref1.png,ref2.jpg --output out.png
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
- `--prompt <prompt>` required
|
|
40
|
+
- `--model <model>`
|
|
41
|
+
- `--aspectRatio <ratio>`
|
|
42
|
+
- `--resolution <resolution>`
|
|
43
|
+
- `--refs <refs>` comma-separated local paths or URLs
|
|
44
|
+
- `--output <file>` save generated file
|
|
45
|
+
|
|
46
|
+
### video
|
|
47
|
+
|
|
48
|
+
Default action: `create`
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
zerocut video --prompt "city night drive" --video vidu --duration 8 --output out.mp4
|
|
52
|
+
zerocut video create --prompt "city night drive" --video vidu --aspectRatio 1:1 --refs ref1.png,ref2.png --output out.mp4
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
- `--prompt <prompt>` required
|
|
57
|
+
- `--video <model>`
|
|
58
|
+
- `--duration <seconds>`
|
|
59
|
+
- `--seed <seed>`
|
|
60
|
+
- `--firstFrame <image>`
|
|
61
|
+
- `--lastFrame <image>`
|
|
62
|
+
- `--refs <assets>`
|
|
63
|
+
- `--resolution <resolution>`
|
|
64
|
+
- `--aspectRatio <ratio>`
|
|
65
|
+
- `--withAudio`
|
|
66
|
+
- `--optimizeCameraMotion`
|
|
67
|
+
- `--output <file>`
|
|
68
|
+
|
|
69
|
+
### music
|
|
70
|
+
|
|
71
|
+
Default action: `create`
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
zerocut music --prompt "lofi beat" --output music.mp3
|
|
75
|
+
zerocut music create --prompt "lofi beat" --output music.mp3
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
- `--prompt <prompt>` required
|
|
80
|
+
- `--output <file>`
|
|
81
|
+
|
|
82
|
+
### tts
|
|
83
|
+
|
|
84
|
+
Default action: `create`
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
zerocut tts --text "你好,欢迎使用 ZeroCut" --voiceId voice_xxx --output speech.mp3
|
|
88
|
+
zerocut tts create --prompt "calm tone" --text "Hello world" --voiceId voice_xxx --output speech.mp3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Options:
|
|
92
|
+
- `--prompt <prompt>`
|
|
93
|
+
- `--text <text>` required
|
|
94
|
+
- `--voiceId <voiceId>`
|
|
95
|
+
- `--output <file>`
|
|
96
|
+
|
|
97
|
+
### ffmpeg
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
zerocut ffmpeg --args -i input.mp4 -vn output.mp3 --resources input.mp4
|
|
101
|
+
zerocut ffmpeg --args -i input.mp4 -vf scale=1280:720 output.mp4 --resources input.mp4
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Options:
|
|
105
|
+
- `--args <args...>` required, arguments appended after `ffmpeg`
|
|
106
|
+
- `--resources <resources...>` optional, files/URLs to sync into sandbox materials
|
|
107
|
+
|
|
108
|
+
Behavior:
|
|
109
|
+
- command is validated to only allow `ffmpeg` or `ffprobe`
|
|
110
|
+
- for `ffmpeg`, `-y` is auto-injected when absent
|
|
111
|
+
- output file is auto-downloaded from sandbox to local current directory
|
|
112
|
+
|
|
113
|
+
### pandoc
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
zerocut pandoc --args input.md -o output.pdf --resources input.md
|
|
117
|
+
zerocut pandoc --args input.md --output=output.docx --resources input.md template.docx
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Options:
|
|
121
|
+
- `--args <args...>` required, arguments appended after `pandoc`
|
|
122
|
+
- `--resources <resources...>` optional, files/URLs to sync into sandbox materials
|
|
123
|
+
|
|
124
|
+
Behavior:
|
|
125
|
+
- command is validated to only allow `pandoc`
|
|
126
|
+
- output file must be specified in args with `-o`, `--output`, or `--output=...`
|
|
127
|
+
- output file is auto-downloaded from sandbox to local current directory
|
|
128
|
+
|
|
129
|
+
## Output And Sync Rules
|
|
130
|
+
|
|
131
|
+
- Media URLs from generation are synced to TOS when available.
|
|
132
|
+
- `--output` saves files to an absolute path resolved from current working directory.
|
|
133
|
+
- Missing parent directories for `--output` are created automatically.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createProgressSpinner(label?: string): () => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProgressSpinner = createProgressSpinner;
|
|
4
|
+
function createProgressSpinner(label = "inferencing") {
|
|
5
|
+
const frames = ["|", "/", "-", "\\"];
|
|
6
|
+
let si = 0;
|
|
7
|
+
const t0 = Date.now();
|
|
8
|
+
return () => {
|
|
9
|
+
const f = frames[si++ % frames.length];
|
|
10
|
+
const sec = Math.floor((Date.now() - t0) / 1000);
|
|
11
|
+
process.stdout.write(`\r${f} ${label}... ${sec}s`);
|
|
12
|
+
};
|
|
13
|
+
}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
2
|
+
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
3
|
+
const prettierPlugin = require("eslint-plugin-prettier");
|
|
4
|
+
|
|
5
|
+
module.exports = [
|
|
6
|
+
{
|
|
7
|
+
ignores: ["dist", "node_modules"],
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
files: ["**/*.ts"],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
parser: tsParser,
|
|
13
|
+
parserOptions: {
|
|
14
|
+
ecmaVersion: 2020,
|
|
15
|
+
sourceType: "commonjs",
|
|
16
|
+
project: "./tsconfig.json",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
plugins: {
|
|
20
|
+
"@typescript-eslint": tsPlugin,
|
|
21
|
+
prettier: prettierPlugin,
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
...tsPlugin.configs.recommended.rules,
|
|
25
|
+
"@typescript-eslint/no-unused-vars": "warn",
|
|
26
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
27
|
+
"prettier/prettier": "error",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zerocut-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ZeroCut CLI: AI assistant CLI for creating and editing images/audio/video",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"zerocut": "dist/bin/zerocut.js",
|
|
9
|
+
"zerocut-cli": "dist/bin/zerocut.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"zerocut"
|
|
14
|
+
],
|
|
15
|
+
"author": "liubei-ai",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+ssh://git@github.com/liubei-ai/zerocut-cli.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/liubei-ai/zerocut-cli#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/liubei-ai/zerocut-cli/issues"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.2.0",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
31
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
32
|
+
"eslint": "^9.39.2",
|
|
33
|
+
"eslint-config-prettier": "^10.1.8",
|
|
34
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
35
|
+
"prettier": "^3.8.1",
|
|
36
|
+
"ts-node": "^10.9.2",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"typescript-eslint": "^8.54.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"cerevox": "^4.22.0",
|
|
42
|
+
"commander": "^14.0.3",
|
|
43
|
+
"inquirer": "^13.2.2"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc && node scripts/copy-skill-md.cjs",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "eslint . --ext .ts",
|
|
49
|
+
"format": "prettier --write .",
|
|
50
|
+
"dev": "ts-node src/bin/zerocut.ts"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const src = path.resolve(__dirname, "../src/skill/SKILL.md");
|
|
5
|
+
const dest = path.resolve(__dirname, "../dist/skill/SKILL.md");
|
|
6
|
+
|
|
7
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
8
|
+
fs.copyFileSync(src, dest);
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { loadBuiltInCommands, loadExternalCommandsAsync } from "./services/commandLoader";
|
|
3
|
+
import { applyConfigInterceptor } from "./services/config";
|
|
4
|
+
|
|
5
|
+
export function createProgram(): Command {
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program.name("zerocut").description("Zerocut CLI");
|
|
8
|
+
|
|
9
|
+
loadBuiltInCommands(program);
|
|
10
|
+
applyConfigInterceptor(program);
|
|
11
|
+
return program;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function run(argv: string[]): Promise<void> {
|
|
15
|
+
const args = argv.slice(2);
|
|
16
|
+
const program = createProgram();
|
|
17
|
+
await loadExternalCommandsAsync(program);
|
|
18
|
+
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
await program.parseAsync([argv[0] ?? "node", argv[1] ?? "zerocut", "help"]);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await program.parseAsync(argv);
|
|
25
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import { readConfigSync, setConfigValueSync } from "../services/config";
|
|
4
|
+
|
|
5
|
+
export const name = "config";
|
|
6
|
+
export const description = "Configuration management";
|
|
7
|
+
|
|
8
|
+
async function ask(question: string, defaults?: string): Promise<string> {
|
|
9
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
const q = defaults ? `${question} [${defaults}]: ` : `${question}: `;
|
|
11
|
+
const answer = await new Promise<string>((resolve) => rl.question(q, (ans) => resolve(ans)));
|
|
12
|
+
rl.close();
|
|
13
|
+
const trimmed = answer.trim();
|
|
14
|
+
return trimmed.length > 0 ? trimmed : (defaults ?? "");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function register(program: Command): void {
|
|
18
|
+
const parent = program
|
|
19
|
+
.command("config")
|
|
20
|
+
.description("Configuration management: set key, projectDir, and region")
|
|
21
|
+
.option("--ott <token>", "One-Time Token (OTT) for fetching API key")
|
|
22
|
+
.option("--region <region>", "Region for OTT exchange: cn|us")
|
|
23
|
+
.action(async function (this: Command, opts: { ott?: string; region?: string }) {
|
|
24
|
+
const ott = typeof opts.ott === "string" ? opts.ott.trim() : "";
|
|
25
|
+
const region = typeof opts.region === "string" ? opts.region.trim().toLowerCase() : "";
|
|
26
|
+
if (!ott) return; // no quick params; fall through to subcommands normally
|
|
27
|
+
if (region !== "cn" && region !== "us") {
|
|
28
|
+
process.stderr.write("Invalid or missing --region. Allowed: cn|us\n");
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const base = region === "cn" ? "https://api2.zerocut.cn" : "https://api2.zerocut.art";
|
|
34
|
+
const resp = await fetch(`${base}/api/open/ott/exchange`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "content-type": "application/json" },
|
|
37
|
+
body: JSON.stringify({ ott }),
|
|
38
|
+
});
|
|
39
|
+
if (!resp.ok) {
|
|
40
|
+
process.stderr.write(`OTT exchange failed: HTTP ${resp.status}\n`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const json = (await resp.json()) as { data?: { apiKey?: string } };
|
|
45
|
+
const apiKey = json?.data?.apiKey;
|
|
46
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
47
|
+
process.stderr.write("OTT exchange failed: missing data.apiKey in response\n");
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setConfigValueSync("apiKey", apiKey);
|
|
52
|
+
setConfigValueSync("region", region);
|
|
53
|
+
process.stdout.write("apiKey set via OTT\n");
|
|
54
|
+
} catch (err) {
|
|
55
|
+
process.stderr.write(`OTT exchange failed: ${(err as Error).message}\n`);
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
parent
|
|
61
|
+
.command("key [key]")
|
|
62
|
+
.description(
|
|
63
|
+
"Set API key. If omitted, exchange via One-Time Token (OTT) after choosing region."
|
|
64
|
+
)
|
|
65
|
+
.action(async (key?: string) => {
|
|
66
|
+
const direct = typeof key === "string" ? key.trim() : "";
|
|
67
|
+
if (direct.length > 0) {
|
|
68
|
+
setConfigValueSync("apiKey", direct);
|
|
69
|
+
process.stdout.write("apiKey set\n");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const region = (await ask("Choose region (cn/us)", "us")).trim().toLowerCase();
|
|
73
|
+
if (region !== "cn" && region !== "us") {
|
|
74
|
+
process.stderr.write("Invalid region. Allowed: cn|us\n");
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const ott = (await ask("Enter One-Time Token (OTT)")).trim();
|
|
79
|
+
if (!ott) {
|
|
80
|
+
process.stderr.write("OTT is required when no apiKey is provided\n");
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const base = region === "cn" ? "https://api2.zerocut.cn" : "https://api2.zerocut.art";
|
|
86
|
+
const resp = await fetch(`${base}/api/open/ott/exchange`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"content-type": "application/json",
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({ ott }),
|
|
92
|
+
});
|
|
93
|
+
if (!resp.ok) {
|
|
94
|
+
process.stderr.write(`OTT exchange failed: HTTP ${resp.status}\n`);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const json = (await resp.json()) as {
|
|
99
|
+
data?: { apiKey?: string };
|
|
100
|
+
[k: string]: unknown;
|
|
101
|
+
};
|
|
102
|
+
const apiKey = json?.data?.apiKey;
|
|
103
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
104
|
+
process.stderr.write("OTT exchange failed: missing data.apiKey in response\n");
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setConfigValueSync("apiKey", apiKey);
|
|
109
|
+
setConfigValueSync("region", region);
|
|
110
|
+
process.stdout.write("apiKey set via OTT\n");
|
|
111
|
+
} catch (err) {
|
|
112
|
+
process.stderr.write(`OTT exchange failed: ${(err as Error).message}\n`);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
parent
|
|
118
|
+
.command("list")
|
|
119
|
+
.description("List current configuration values")
|
|
120
|
+
.action(() => {
|
|
121
|
+
const cfg = readConfigSync() as Record<string, unknown>;
|
|
122
|
+
const masked = { ...cfg } as Record<string, unknown>;
|
|
123
|
+
const k = masked.apiKey;
|
|
124
|
+
if (typeof k === "string" && k.length > 0) {
|
|
125
|
+
const visible = k.slice(-4);
|
|
126
|
+
masked.apiKey = `${"*".repeat(Math.max(0, k.length - 4))}${visible}`;
|
|
127
|
+
}
|
|
128
|
+
process.stdout.write(`${JSON.stringify(masked, null, 2)}\n`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { getSessionFromCommand, runFFMpegCommand } from "../services/cerevox";
|
|
3
|
+
|
|
4
|
+
export const name = "ffmpeg";
|
|
5
|
+
export const description = "Run ffmpeg in sandbox";
|
|
6
|
+
|
|
7
|
+
export function register(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command("ffmpeg")
|
|
10
|
+
.description("Run ffmpeg command in sandbox")
|
|
11
|
+
.allowUnknownOption(true)
|
|
12
|
+
.option("--args <args...>", "Arguments passed to ffmpeg")
|
|
13
|
+
.option("--resources <resources...>", "Resource files/urls to sync into sandbox")
|
|
14
|
+
.action(async function (
|
|
15
|
+
this: Command,
|
|
16
|
+
opts: {
|
|
17
|
+
args?: string[];
|
|
18
|
+
resources?: string[];
|
|
19
|
+
}
|
|
20
|
+
) {
|
|
21
|
+
const session = getSessionFromCommand(this as unknown as Record<symbol, unknown>);
|
|
22
|
+
if (!session) {
|
|
23
|
+
process.stderr.write("No active session\n");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const args = Array.isArray(opts.args) ? opts.args : [];
|
|
27
|
+
if (args.length === 0) {
|
|
28
|
+
process.stderr.write("Missing required option: --args\n");
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const command = `ffmpeg ${args.join(" ")}`;
|
|
33
|
+
const resources = Array.isArray(opts.resources) ? opts.resources : [];
|
|
34
|
+
const res = await runFFMpegCommand(session, command, resources);
|
|
35
|
+
console.log(res);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
export const name = "help";
|
|
4
|
+
export const description = "Show help";
|
|
5
|
+
|
|
6
|
+
export function register(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command("help")
|
|
9
|
+
.description("Show help")
|
|
10
|
+
.action(() => {
|
|
11
|
+
program.outputHelp();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getMaterialUri, getSessionFromCommand, syncToTOS } from "../services/cerevox";
|
|
5
|
+
import type { Session } from "cerevox";
|
|
6
|
+
import { createProgressSpinner } from "../utils/progress";
|
|
7
|
+
|
|
8
|
+
export const name = "image";
|
|
9
|
+
export const description = "Image command: create image";
|
|
10
|
+
|
|
11
|
+
export function register(program: Command): void {
|
|
12
|
+
const parent = program.command("image").description("Create a new image; requires --prompt");
|
|
13
|
+
|
|
14
|
+
const allowedModels = [
|
|
15
|
+
"seedream",
|
|
16
|
+
"seedream-pro",
|
|
17
|
+
"seedream-5l",
|
|
18
|
+
"banana",
|
|
19
|
+
"banana2",
|
|
20
|
+
"banana-pro",
|
|
21
|
+
"wan",
|
|
22
|
+
] as const;
|
|
23
|
+
type AllowedModel = (typeof allowedModels)[number];
|
|
24
|
+
const allowedAspectRatios = [
|
|
25
|
+
"1:1",
|
|
26
|
+
"3:4",
|
|
27
|
+
"4:3",
|
|
28
|
+
"16:9",
|
|
29
|
+
"9:16",
|
|
30
|
+
"2:3",
|
|
31
|
+
"3:2",
|
|
32
|
+
"21:9",
|
|
33
|
+
"1:4",
|
|
34
|
+
"4:1",
|
|
35
|
+
"1:8",
|
|
36
|
+
"8:1",
|
|
37
|
+
] as const;
|
|
38
|
+
type AllowedAspectRatio = (typeof allowedAspectRatios)[number];
|
|
39
|
+
const allowedResolutions = ["1K", "2K", "4K"] as const;
|
|
40
|
+
type AllowedResolution = (typeof allowedResolutions)[number];
|
|
41
|
+
|
|
42
|
+
async function performImageGeneration(
|
|
43
|
+
session: Session,
|
|
44
|
+
{
|
|
45
|
+
prompt,
|
|
46
|
+
model,
|
|
47
|
+
aspectRatio,
|
|
48
|
+
resolution,
|
|
49
|
+
refsList,
|
|
50
|
+
output,
|
|
51
|
+
}: {
|
|
52
|
+
prompt: string;
|
|
53
|
+
model?: AllowedModel;
|
|
54
|
+
aspectRatio?: AllowedAspectRatio;
|
|
55
|
+
resolution?: AllowedResolution;
|
|
56
|
+
refsList: string[];
|
|
57
|
+
output?: string;
|
|
58
|
+
}
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const referenceImages = await Promise.all(
|
|
61
|
+
refsList.map(async (ref) => ({ url: await getMaterialUri(session, ref) }))
|
|
62
|
+
);
|
|
63
|
+
const onProgress = createProgressSpinner("inferencing");
|
|
64
|
+
const payload: Record<string, unknown> = {
|
|
65
|
+
model: model || "seedream-5l",
|
|
66
|
+
prompt,
|
|
67
|
+
aspect_ratio: aspectRatio,
|
|
68
|
+
resolution,
|
|
69
|
+
reference_images: referenceImages,
|
|
70
|
+
onProgress,
|
|
71
|
+
};
|
|
72
|
+
const res = await (session.ai.generateImage as (arg: Record<string, unknown>) => Promise<any>)(
|
|
73
|
+
payload
|
|
74
|
+
);
|
|
75
|
+
if (res?.urls && Array.isArray(res.urls) && res.urls.length > 0) {
|
|
76
|
+
try {
|
|
77
|
+
const tosUrl = await syncToTOS(res.urls[0]);
|
|
78
|
+
if (tosUrl) {
|
|
79
|
+
res.url = tosUrl;
|
|
80
|
+
res.urls[0] = tosUrl;
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
process.stdout.write("\n");
|
|
85
|
+
if (output) {
|
|
86
|
+
const dir = process.cwd();
|
|
87
|
+
const url = (res.url as string) ?? res.urls[0];
|
|
88
|
+
const response = await fetch(url);
|
|
89
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
90
|
+
const filePath = path.resolve(dir, output);
|
|
91
|
+
if (!fs.existsSync(path.dirname(filePath))) {
|
|
92
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(filePath, buffer);
|
|
95
|
+
res.output = filePath;
|
|
96
|
+
}
|
|
97
|
+
console.log(res);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function imageCreateAction(
|
|
101
|
+
this: Command,
|
|
102
|
+
opts: {
|
|
103
|
+
prompt?: string;
|
|
104
|
+
model?: string;
|
|
105
|
+
aspectRatio?: string;
|
|
106
|
+
resolution?: string;
|
|
107
|
+
refs?: string;
|
|
108
|
+
output?: string;
|
|
109
|
+
}
|
|
110
|
+
) {
|
|
111
|
+
const session = getSessionFromCommand(this as unknown as Record<symbol, unknown>);
|
|
112
|
+
if (!session) {
|
|
113
|
+
process.stderr.write("No active session\n");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const prompt = typeof opts.prompt === "string" ? opts.prompt : undefined;
|
|
117
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
118
|
+
process.stderr.write("Missing required option: --prompt\n");
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const model = typeof opts.model === "string" ? opts.model.trim() : undefined;
|
|
123
|
+
if (model && !(allowedModels as readonly string[]).includes(model)) {
|
|
124
|
+
process.stderr.write(
|
|
125
|
+
`Invalid value for --model: ${model}. Allowed: ${allowedModels.join("|")}\n`
|
|
126
|
+
);
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const modelArg: AllowedModel | undefined = (model ?? undefined) as AllowedModel | undefined;
|
|
131
|
+
const aspectRatio =
|
|
132
|
+
typeof opts.aspectRatio === "string"
|
|
133
|
+
? (opts.aspectRatio.trim() as AllowedAspectRatio)
|
|
134
|
+
: undefined;
|
|
135
|
+
if (aspectRatio && !(allowedAspectRatios as readonly string[]).includes(aspectRatio)) {
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
`Invalid value for --aspectRatio: ${aspectRatio}. Allowed: ${allowedAspectRatios.join("|")}\n`
|
|
138
|
+
);
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const resolution =
|
|
143
|
+
typeof opts.resolution === "string"
|
|
144
|
+
? (opts.resolution.trim() as AllowedResolution)
|
|
145
|
+
: undefined;
|
|
146
|
+
if (resolution && !(allowedResolutions as readonly string[]).includes(resolution)) {
|
|
147
|
+
process.stderr.write(
|
|
148
|
+
`Invalid value for --resolution: ${resolution}. Allowed: ${allowedResolutions.join("|")}\n`
|
|
149
|
+
);
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const refsList =
|
|
154
|
+
typeof opts.refs === "string" && opts.refs.length > 0
|
|
155
|
+
? opts.refs
|
|
156
|
+
.split(",")
|
|
157
|
+
.map((s) => s.trim())
|
|
158
|
+
.filter((s) => s.length > 0)
|
|
159
|
+
: [];
|
|
160
|
+
const output = typeof opts.output === "string" ? opts.output : undefined;
|
|
161
|
+
await performImageGeneration(session, {
|
|
162
|
+
prompt,
|
|
163
|
+
model: modelArg,
|
|
164
|
+
aspectRatio,
|
|
165
|
+
resolution,
|
|
166
|
+
refsList,
|
|
167
|
+
output,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// default action on `zerocut image`
|
|
172
|
+
parent
|
|
173
|
+
.option("--prompt <prompt>", "Text prompt for image generation (required)")
|
|
174
|
+
.option("--model <model>", `Generator model: ${allowedModels.join("|")}`)
|
|
175
|
+
.option("--aspectRatio <ratio>", `Aspect ratio: ${allowedAspectRatios.join("|")}`)
|
|
176
|
+
.option("--resolution <resolution>", `Resolution: ${allowedResolutions.join("|")}`)
|
|
177
|
+
.option("--refs <refs>", "Comma-separated reference image paths/urls")
|
|
178
|
+
.option("--output <file>", "Output file path")
|
|
179
|
+
.action(imageCreateAction);
|
|
180
|
+
|
|
181
|
+
// keep `image create` for compatibility
|
|
182
|
+
parent
|
|
183
|
+
.command("create")
|
|
184
|
+
.description("Create a new image; requires --prompt")
|
|
185
|
+
.option("--prompt <prompt>", "Text prompt for image generation (required)")
|
|
186
|
+
.option("--model <model>", `Generator model: ${allowedModels.join("|")}`)
|
|
187
|
+
.option("--aspectRatio <ratio>", `Aspect ratio: ${allowedAspectRatios.join("|")}`)
|
|
188
|
+
.option("--resolution <resolution>", `Resolution: ${allowedResolutions.join("|")}`)
|
|
189
|
+
.option("--refs <refs>", "Comma-separated reference image paths/urls")
|
|
190
|
+
.option("--output <file>", "Output file path")
|
|
191
|
+
.action(imageCreateAction);
|
|
192
|
+
|
|
193
|
+
// removed `image edit`
|
|
194
|
+
}
|