zerocut-cli 0.3.3 → 0.4.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 CHANGED
@@ -102,6 +102,7 @@ zerocut config --ott <token> --region <cn|us> # non-interactive
102
102
  - Options:
103
103
  - `--prompt <prompt>` (required)
104
104
  - `--model <model>` (seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan|wan-pro)
105
+ - `--type <type>` (default|storyboard|subject-turnaround, default: default)
105
106
  - `--aspectRatio <ratio>` (1:1|3:4|4:3|16:9|9:16|2:3|3:2|21:9|1:4|4:1|1:8|8:1)
106
107
  - `--resolution <resolution>` (1K|2K|4K)
107
108
  - `--refs <img1,img2,...>` (comma-separated paths/URLs)
@@ -158,7 +159,7 @@ zerocut config --ott <token> --region <cn|us> # non-interactive
158
159
 
159
160
  ```bash
160
161
  # Create an image (default action)
161
- npx zerocut-cli image --prompt "a cat" --model seedream --aspectRatio 1:1 --resolution 1K --refs ref1.png,ref2.jpg --output out.png
162
+ npx zerocut-cli image --prompt "a cat" --model seedream --type default --aspectRatio 1:1 --resolution 1K --refs ref1.png,ref2.jpg --output out.png
162
163
 
163
164
  # Create video (default action)
164
165
  npx zerocut-cli video --prompt "city night drive" --duration 12 --model vidu --refs frame1.png,frame2.png --resolution 720p --output movie.mp4
@@ -17,6 +17,29 @@ async function ask(question, defaults) {
17
17
  const trimmed = answer.trim();
18
18
  return trimmed.length > 0 ? trimmed : (defaults ?? "");
19
19
  }
20
+ async function exchangeOttAndSave(ott, region) {
21
+ const base = region === "cn" ? "https://api2.zerocut.cn" : "https://api2.zerocut.art";
22
+ const resp = await fetch(`${base}/api/open/ott/exchange`, {
23
+ method: "POST",
24
+ headers: { "content-type": "application/json" },
25
+ body: JSON.stringify({ ott }),
26
+ });
27
+ if (!resp.ok) {
28
+ process.stderr.write(`OTT exchange failed: HTTP ${resp.status}\n`);
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+ const json = (await resp.json());
33
+ const apiKey = json?.data?.apiKey;
34
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
35
+ process.stderr.write("OTT exchange failed: missing data.apiKey in response\n");
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ (0, config_1.setConfigValueSync)("apiKey", apiKey);
40
+ (0, config_1.setConfigValueSync)("region", region);
41
+ process.stdout.write("apiKey set via OTT\n");
42
+ }
20
43
  function register(program) {
21
44
  const parent = program
22
45
  .command("config")
@@ -24,37 +47,38 @@ function register(program) {
24
47
  .option("--ott <token>", "One-Time Token (OTT) for fetching API key")
25
48
  .option("--region <region>", "Region for OTT exchange: cn|us")
26
49
  .action(async function (opts) {
27
- const ott = typeof opts.ott === "string" ? opts.ott.trim() : "";
28
- const region = typeof opts.region === "string" ? opts.region.trim().toLowerCase() : "";
29
- if (!ott)
30
- return; // no quick params; fall through to subcommands normally
31
- if (region !== "cn" && region !== "us") {
32
- process.stderr.write("Invalid or missing --region. Allowed: cn|us\n");
50
+ const cfg = (0, config_1.readConfigSync)();
51
+ const regionInput = typeof opts.region === "string" ? opts.region.trim().toLowerCase() : "";
52
+ let region;
53
+ if (regionInput === "cn" || regionInput === "us") {
54
+ region = regionInput;
55
+ }
56
+ else if (regionInput.length > 0) {
57
+ process.stderr.write("Invalid --region. Allowed: cn|us\n");
33
58
  process.exitCode = 1;
34
59
  return;
35
60
  }
36
- try {
37
- const base = region === "cn" ? "https://api2.zerocut.cn" : "https://api2.zerocut.art";
38
- const resp = await fetch(`${base}/api/open/ott/exchange`, {
39
- method: "POST",
40
- headers: { "content-type": "application/json" },
41
- body: JSON.stringify({ ott }),
42
- });
43
- if (!resp.ok) {
44
- process.stderr.write(`OTT exchange failed: HTTP ${resp.status}\n`);
61
+ else {
62
+ const defaultRegion = cfg.region === "cn" || cfg.region === "us" ? cfg.region : "us";
63
+ const regionAnswer = (await ask("Choose region (cn/us)", defaultRegion))
64
+ .trim()
65
+ .toLowerCase();
66
+ if (regionAnswer !== "cn" && regionAnswer !== "us") {
67
+ process.stderr.write("Invalid region. Allowed: cn|us\n");
45
68
  process.exitCode = 1;
46
69
  return;
47
70
  }
48
- const json = (await resp.json());
49
- const apiKey = json?.data?.apiKey;
50
- if (typeof apiKey !== "string" || apiKey.length === 0) {
51
- process.stderr.write("OTT exchange failed: missing data.apiKey in response\n");
52
- process.exitCode = 1;
53
- return;
54
- }
55
- (0, config_1.setConfigValueSync)("apiKey", apiKey);
56
- (0, config_1.setConfigValueSync)("region", region);
57
- process.stdout.write("apiKey set via OTT\n");
71
+ region = regionAnswer;
72
+ }
73
+ const ott = typeof opts.ott === "string" ? opts.ott.trim() : "";
74
+ const ottValue = ott.length > 0 ? ott : (await ask("Enter One-Time Token (OTT)")).trim();
75
+ if (!ottValue) {
76
+ process.stderr.write("OTT is required\n");
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+ try {
81
+ await exchangeOttAndSave(ottValue, region);
58
82
  }
59
83
  catch (err) {
60
84
  process.stderr.write(`OTT exchange failed: ${err.message}\n`);
@@ -84,29 +108,7 @@ function register(program) {
84
108
  return;
85
109
  }
86
110
  try {
87
- const base = region === "cn" ? "https://api2.zerocut.cn" : "https://api2.zerocut.art";
88
- const resp = await fetch(`${base}/api/open/ott/exchange`, {
89
- method: "POST",
90
- headers: {
91
- "content-type": "application/json",
92
- },
93
- body: JSON.stringify({ ott }),
94
- });
95
- if (!resp.ok) {
96
- process.stderr.write(`OTT exchange failed: HTTP ${resp.status}\n`);
97
- process.exitCode = 1;
98
- return;
99
- }
100
- const json = (await resp.json());
101
- const apiKey = json?.data?.apiKey;
102
- if (typeof apiKey !== "string" || apiKey.length === 0) {
103
- process.stderr.write("OTT exchange failed: missing data.apiKey in response\n");
104
- process.exitCode = 1;
105
- return;
106
- }
107
- (0, config_1.setConfigValueSync)("apiKey", apiKey);
108
- (0, config_1.setConfigValueSync)("region", region);
109
- process.stdout.write("apiKey set via OTT\n");
111
+ await exchangeOttAndSave(ott, region);
110
112
  }
111
113
  catch (err) {
112
114
  process.stderr.write(`OTT exchange failed: ${err.message}\n`);
@@ -38,11 +38,13 @@ function register(program) {
38
38
  "8:1",
39
39
  ];
40
40
  const allowedResolutions = ["1K", "2K", "4K"];
41
- async function performImageGeneration(session, { prompt, model, aspectRatio, resolution, refsList, output, }) {
41
+ const allowedTypes = ["default", "storyboard", "subject-turnaround"];
42
+ async function performImageGeneration(session, { prompt, model, type, aspectRatio, resolution, refsList, output, }) {
42
43
  const referenceImages = await Promise.all(refsList.map(async (ref) => ({ url: await (0, cerevox_1.getMaterialUri)(session, ref) })));
43
44
  const onProgress = (0, progress_1.createProgressSpinner)("inferencing");
44
45
  const payload = {
45
46
  model: model || "seedream-5l",
47
+ type,
46
48
  prompt,
47
49
  aspect_ratio: aspectRatio,
48
50
  resolution,
@@ -95,6 +97,13 @@ function register(program) {
95
97
  return;
96
98
  }
97
99
  const modelArg = (model ?? undefined);
100
+ const type = typeof opts.type === "string" ? opts.type.trim() : "default";
101
+ if (!allowedTypes.includes(type)) {
102
+ process.stderr.write(`Invalid value for --type: ${type}. Allowed: ${allowedTypes.join("|")}\n`);
103
+ process.exitCode = 1;
104
+ return;
105
+ }
106
+ const typeArg = type;
98
107
  const aspectRatio = typeof opts.aspectRatio === "string"
99
108
  ? opts.aspectRatio.trim()
100
109
  : undefined;
@@ -121,6 +130,7 @@ function register(program) {
121
130
  await performImageGeneration(session, {
122
131
  prompt,
123
132
  model: modelArg,
133
+ type: typeArg,
124
134
  aspectRatio,
125
135
  resolution,
126
136
  refsList,
@@ -131,6 +141,7 @@ function register(program) {
131
141
  parent
132
142
  .option("--prompt <prompt>", "Text prompt for image generation (required)")
133
143
  .option("--model <model>", `Generator model: ${allowedModels.join("|")}`)
144
+ .option("--type <type>", `Image type: ${allowedTypes.join("|")}`)
134
145
  .option("--aspectRatio <ratio>", `Aspect ratio: ${allowedAspectRatios.join("|")}`)
135
146
  .option("--resolution <resolution>", `Resolution: ${allowedResolutions.join("|")}`)
136
147
  .option("--refs <refs>", "Comma-separated reference image paths/urls")
@@ -142,6 +153,7 @@ function register(program) {
142
153
  .description("Create a new image; requires --prompt")
143
154
  .option("--prompt <prompt>", "Text prompt for image generation (required)")
144
155
  .option("--model <model>", `Generator model: ${allowedModels.join("|")}`)
156
+ .option("--type <type>", `Image type: ${allowedTypes.join("|")}`)
145
157
  .option("--aspectRatio <ratio>", `Aspect ratio: ${allowedAspectRatios.join("|")}`)
146
158
  .option("--resolution <resolution>", `Resolution: ${allowedResolutions.join("|")}`)
147
159
  .option("--refs <refs>", "Comma-separated reference image paths/urls")
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.description = exports.name = void 0;
7
7
  exports.register = register;
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_os_1 = __importDefault(require("node:os"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  exports.name = "skill";
11
12
  exports.description = "Print built-in SKILL.md content";
@@ -16,13 +17,12 @@ function printSkill(relativePath) {
16
17
  if (!content.endsWith("\n")) {
17
18
  process.stdout.write("\n");
18
19
  }
19
- const outputPath = node_path_1.default.resolve(process.cwd(), "zerocut-cli/SKILL.md");
20
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
21
- node_fs_1.default.writeFileSync(outputPath, content, "utf8");
22
- process.stderr.write(`Saved skill markdown to ${outputPath}\n`);
23
20
  }
24
21
  function register(program) {
25
- const parent = program.command("skill").description("Print built-in skill markdown");
22
+ const parent = program
23
+ .command("skill")
24
+ .description("Print built-in skill markdown")
25
+ .option("--claude", "Append Claude skill save hint");
26
26
  parent
27
27
  .command("one-click-video")
28
28
  .description("Print one-click-video skill markdown")
@@ -35,7 +35,11 @@ function register(program) {
35
35
  .action(() => {
36
36
  printSkill("../skill/edit-video/SKILL.md");
37
37
  });
38
- parent.action(() => {
38
+ parent.action((opts) => {
39
39
  printSkill("../skill/SKILL.md");
40
+ if (opts.claude) {
41
+ const savePath = node_path_1.default.join(node_os_1.default.homedir(), ".claude", "skills", "zerocut", "SKILL.md");
42
+ process.stdout.write(`\n⚠️ Please save the markdown above to ${savePath} to create the skill.\n`);
43
+ }
40
44
  });
41
45
  }
@@ -18,6 +18,7 @@ export declare function syncToTOS(url: string): Promise<string>;
18
18
  export declare function runFFMpegCommand(session: Session, command: string, resources?: string[]): Promise<{
19
19
  exitCode: number;
20
20
  outputFilePath: string;
21
+ tosUrl: string | undefined;
21
22
  data: {
22
23
  stdout: string;
23
24
  stderr: string | undefined;
@@ -193,20 +193,23 @@ async function runFFMpegCommand(session, command, resources = []) {
193
193
  cwd: workDir,
194
194
  });
195
195
  const outputFilePath = trimmedCommand.startsWith("ffmpeg")
196
- ? finalCommand.split(" ").pop() || ""
196
+ ? (finalCommand.split(" ").pop() || "").replace(/^["']|["']$/g, "")
197
197
  : "";
198
198
  const sandboxFilePath = (0, node_path_2.join)(workDir, outputFilePath);
199
+ let tosUrl;
199
200
  // 等待命令完成
200
201
  const result = await response.json();
201
202
  if (result.exitCode === 0 && outputFilePath) {
202
203
  const savePath = (0, node_path_2.join)(process.cwd(), (0, node_path_1.basename)(outputFilePath));
203
- console.log(sandboxFilePath, savePath);
204
204
  const files = session.files;
205
205
  await files.download(sandboxFilePath, savePath);
206
+ const sandboxUrl = await getMaterialUri(session, savePath);
207
+ tosUrl = await syncToTOS(sandboxUrl);
206
208
  }
207
209
  return {
208
210
  exitCode: result.exitCode,
209
211
  outputFilePath,
212
+ tosUrl,
210
213
  data: {
211
214
  stdout: result.stdout || (!result.exitCode && result.stderr) || "",
212
215
  stderr: result.exitCode ? result.stderr : undefined,
@@ -49,6 +49,7 @@ const pandoc_1 = require("../commands/pandoc");
49
49
  const skill_1 = require("../commands/skill");
50
50
  const node_fs_1 = __importDefault(require("node:fs"));
51
51
  const node_path_1 = __importDefault(require("node:path"));
52
+ const node_url_1 = require("node:url");
52
53
  function loadBuiltInCommands(program) {
53
54
  (0, help_1.register)(program);
54
55
  (0, config_1.register)(program);
@@ -70,7 +71,8 @@ async function loadExternalCommandsAsync(program, dir) {
70
71
  for (const f of files) {
71
72
  const full = node_path_1.default.join(d, f);
72
73
  try {
73
- const mod = (await Promise.resolve(`${full}`).then(s => __importStar(require(s))));
74
+ const moduleSpecifier = (0, node_url_1.pathToFileURL)(full).href;
75
+ const mod = (await Promise.resolve(`${moduleSpecifier}`).then(s => __importStar(require(s))));
74
76
  const fn = mod.register ?? mod.default;
75
77
  if (typeof fn === "function")
76
78
  fn(program);
@@ -102,13 +102,14 @@ Default action: `create`
102
102
 
103
103
  ```bash
104
104
  npx zerocut-cli image --prompt "a cat on a bike" --output out.png
105
- npx zerocut-cli image create --prompt "a cat on a bike" --model seedream-5l --aspectRatio 1:1 --resolution 1K --refs ref1.png,ref2.jpg --output out.png
105
+ npx zerocut-cli image create --prompt "a cat on a bike" --model seedream-5l --type default --aspectRatio 1:1 --resolution 1K --refs ref1.png,ref2.jpg --output out.png
106
106
  ```
107
107
 
108
108
  Options:
109
109
 
110
110
  - `--prompt <prompt>` required
111
111
  - `--model <model>`
112
+ - `--type <type>`
112
113
  - `--aspectRatio <ratio>`
113
114
  - `--resolution <resolution>`
114
115
  - `--refs <refs>` comma-separated local paths or URLs
@@ -118,6 +119,8 @@ Validation rules:
118
119
 
119
120
  - `--prompt` must be non-empty
120
121
  - `--model` allowed: `seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan|wan-pro`
122
+ - `--type` allowed: `default|storyboard|subject-turnaround`
123
+ - unless user specifies type, default to `default`
121
124
  - `--aspectRatio` allowed: `1:1|3:4|4:3|16:9|9:16|2:3|3:2|21:9|1:4|4:1|1:8|8:1`
122
125
  - unless user specifies aspect ratio, default to `16:9`
123
126
  - `--resolution` allowed: `1K|2K|4K`