fix: Fix the issue of the watermark being always on and address the issue of paired flags

This commit is contained in:
qcq01083097
2026-06-04 17:07:11 +08:00
parent 14371a0647
commit 6436ca88eb
11 changed files with 168 additions and 71 deletions
+3 -1
View File
@@ -114,6 +114,8 @@ export default defineCommand({
}
}
const storedValue: string | number = resolvedKey === "timeout" ? Number(value) : value;
const format = detectOutputFormat(config.output);
if (config.dryRun) {
@@ -122,7 +124,7 @@ export default defineCommand({
}
const existing = readConfigFile() as Record<string, unknown>;
existing[resolvedKey] = resolvedKey === "timeout" ? Number(value) : value;
existing[resolvedKey] = storedValue;
await writeConfigFile(existing);
if (!config.quiet) {
+15 -13
View File
@@ -15,6 +15,8 @@ import {
type DashScopeImageSyncResponse,
ExitCode,
BailianError,
resolveBooleanFlag,
resolveWatermark,
} from "bailian-cli-core";
import { downloadFile } from "../../utils/download.ts";
import { runConcurrent, downloadParallel, getConcurrency } from "../../utils/concurrent.ts";
@@ -47,9 +49,14 @@ export default defineCommand({
flag: "--negative-prompt <text>",
description: "Negative prompt to exclude unwanted content",
},
{ flag: "--prompt-extend", description: "Enable prompt smart rewrite (default: true)" },
{ flag: "--no-prompt-extend", description: "Disable prompt extend" },
{ flag: "--watermark", description: "Add watermark to output images" },
{
flag: "--prompt-extend <bool>",
description: "Enable prompt extend (true/false). Default: true.",
},
{
flag: "--watermark <bool>",
description: "Enable watermark (true/false). Default: true.",
},
{ flag: "--out-dir <dir>", description: "Download images to directory" },
{ flag: "--out-prefix <prefix>", description: "Filename prefix (default: edited)" },
],
@@ -58,6 +65,7 @@ export default defineCommand({
'bl image edit --image https://example.com/logo.png --prompt "Change color to blue" --n 3',
'bl image edit --image ./a.png --image ./b.png --prompt "把两张图合并成一张拼图"',
'bl image edit --image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro',
'bl image edit --image ./photo.png --prompt "把背景换成海滩" --watermark false',
],
async run(config: Config, flags: GlobalFlags) {
// Normalize --image to string array (supports both single and repeated flags)
@@ -96,15 +104,7 @@ export default defineCommand({
);
const n = (flags.n as number) ?? 1;
// Determine prompt_extend
let promptExtend: boolean | undefined;
if (flags.noPromptExtend === true) {
promptExtend = false;
} else if (flags.promptExtend === true) {
promptExtend = true;
} else {
promptExtend = true; // default on for qwen-image
}
const promptExtend = resolveBooleanFlag(flags.promptExtend, true, "prompt-extend");
// Build content: all images first, then text prompt
const contentItems: Array<{ image?: string; text?: string }> = resolvedImages.map(
@@ -112,6 +112,8 @@ export default defineCommand({
);
contentItems.push({ text: prompt! });
const watermark = resolveWatermark(flags.watermark);
const body: DashScopeImageRequest = {
model,
input: {
@@ -127,7 +129,7 @@ export default defineCommand({
n,
seed: flags.seed as number | undefined,
prompt_extend: promptExtend,
watermark: flags.watermark === true ? true : undefined,
watermark,
negative_prompt: (flags.negativePrompt as string) || undefined,
},
};
+20 -15
View File
@@ -17,6 +17,8 @@ import {
type OutputFormat,
type DashScopeTaskResponse,
generateFilename,
resolveBooleanFlag,
resolveWatermark,
} from "bailian-cli-core";
import { poll } from "../../utils/polling.ts";
import { downloadFile } from "../../utils/download.ts";
@@ -57,11 +59,14 @@ export default defineCommand({
description: "Negative prompt to exclude unwanted content",
},
{
flag: "--prompt-extend",
description: "Automatically extend prompt for better results (default: true for qwen-image)",
flag: "--prompt-extend <bool>",
description:
"Enable prompt extend (true/false). Default: true for qwen-image sync models; omit for async.",
},
{
flag: "--watermark <bool>",
description: "Enable watermark (true/false). Default: true.",
},
{ flag: "--no-prompt-extend", description: "Disable prompt extend" },
{ flag: "--watermark", description: "Add watermark to generated images" },
{
flag: "--no-wait",
description: "Return task ID immediately without waiting (async models only)",
@@ -78,7 +83,9 @@ export default defineCommand({
'bl image generate --prompt "一只穿太空服的猫在火星上"',
'bl image generate --prompt "Logo design" --n 3 --out-dir ./generated/',
'bl image generate --prompt "Mountain landscape" --size 2688*1536',
'bl image generate --prompt "A castle" --seed 42 --no-prompt-extend',
'bl image generate --prompt "A castle" --seed 42 --prompt-extend false',
'bl image generate --prompt "Logo" --watermark false',
'bl image generate --prompt "An alien in the space" --watermark false',
'bl image generate --prompt "sunset" --model wan2.6-t2i --no-wait --quiet',
'bl image generate --prompt "Pro quality" --model qwen-image-2.0-pro',
'bl image generate --prompt "Product shots" --n 2 --concurrent 3 # 6 images in parallel',
@@ -111,15 +118,13 @@ export default defineCommand({
const n = (flags.n as number) ?? 1;
const concurrent = getConcurrency(flags);
// Determine prompt_extend: default true for qwen-image, undefined for others
let promptExtend: boolean | undefined;
if (flags.noPromptExtend === true) {
promptExtend = false;
} else if (flags.promptExtend === true) {
promptExtend = true;
} else if (useSync) {
promptExtend = true; // qwen-image default
}
const promptExtend = resolveBooleanFlag(
flags.promptExtend,
useSync ? true : undefined,
"prompt-extend",
);
const watermark = resolveWatermark(flags.watermark);
const body: DashScopeImageRequest = {
model,
@@ -131,7 +136,7 @@ export default defineCommand({
n,
seed: flags.seed as number | undefined,
prompt_extend: promptExtend,
watermark: flags.watermark === true ? true : undefined,
watermark,
negative_prompt: (flags.negativePrompt as string) || undefined,
},
};
+14 -6
View File
@@ -15,6 +15,8 @@ import {
resolveCredential,
BailianError,
ExitCode,
resolveBooleanFlag,
resolveWatermark,
} from "bailian-cli-core";
import { poll } from "../../utils/polling.ts";
import { downloadFile, formatBytes } from "../../utils/download.ts";
@@ -51,9 +53,14 @@ export default defineCommand({
flag: "--audio-setting <mode>",
description: "Audio: auto (default) or origin (keep original)",
},
{ flag: "--prompt-extend", description: "Enable prompt intelligent rewriting (default: true)" },
{ flag: "--no-prompt-extend", description: "Disable prompt intelligent rewriting" },
{ flag: "--watermark", description: 'Add "AI生成" watermark' },
{
flag: "--prompt-extend <bool>",
description: "Enable prompt extend (true/false). Omit to use API default.",
},
{
flag: "--watermark <bool>",
description: "Enable watermark (true/false). Default: true.",
},
{ flag: "--seed <n>", description: "Random seed for reproducible generation", type: "number" },
{ flag: "--download <path>", description: "Save video to file on completion" },
{ flag: "--no-wait", description: "Return task ID immediately without waiting" },
@@ -71,6 +78,7 @@ export default defineCommand({
'bl video edit --video https://example.com/input.mp4 --prompt "将整个画面转换为黏土风格"',
'bl video edit --video https://example.com/input.mp4 --prompt "替换衣服为图片中的款式" --ref-image https://example.com/clothes.png',
'bl video edit --video https://example.com/input.mp4 --prompt "Convert to anime style" --resolution 720P --download output.mp4',
'bl video edit --video https://example.com/input.mp4 --prompt "给视频里的小猫穿上衣服" --watermark false',
],
async run(config: Config, flags: GlobalFlags) {
// --- Validate video URL ---
@@ -127,8 +135,8 @@ export default defineCommand({
}
// --- Build request body ---
const promptExtend =
flags.noPromptExtend === true ? false : flags.promptExtend === true ? true : undefined;
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
const watermark = resolveWatermark(flags.watermark);
const body: DashScopeVideoEditRequest = {
model,
@@ -143,7 +151,7 @@ export default defineCommand({
duration: (flags.duration as number) || undefined,
audio_setting: (flags.audioSetting as "auto" | "origin") || undefined,
prompt_extend: promptExtend,
watermark: flags.watermark === true ? true : undefined,
watermark,
seed: flags.seed as number | undefined,
},
};
+16 -4
View File
@@ -15,6 +15,8 @@ import {
resolveCredential,
BailianError,
ExitCode,
resolveBooleanFlag,
resolveWatermark,
} from "bailian-cli-core";
import { poll } from "../../utils/polling.ts";
import { downloadFile, formatBytes } from "../../utils/download.ts";
@@ -58,8 +60,14 @@ export default defineCommand({
description: "Video duration in seconds (default: 5)",
type: "number",
},
{ flag: "--prompt-extend", description: "Automatically extend prompt for better results" },
{ flag: "--watermark", description: "Add watermark to generated video" },
{
flag: "--prompt-extend <bool>",
description: "Enable prompt extend (true/false). Omit to use API default.",
},
{
flag: "--watermark <bool>",
description: "Enable watermark (true/false). Default: true.",
},
{ flag: "--seed <n>", description: "Random seed for reproducible generation", type: "number" },
{ flag: "--download <path>", description: "Save video to file on completion" },
{ flag: "--no-wait", description: "Return task ID immediately without waiting" },
@@ -78,6 +86,7 @@ export default defineCommand({
'bl video generate --prompt "Ocean waves at sunset." --download sunset.mp4',
'bl video generate --image https://example.com/cat.png --prompt "让画面中的猫动起来"',
'bl video generate --prompt "Mountain landscape" --resolution 1280*720 --duration 5',
'bl video generate --prompt "A cat playing with a ball" --watermark false',
],
async run(config: Config, flags: GlobalFlags) {
let prompt = flags.prompt as string | undefined;
@@ -110,6 +119,9 @@ export default defineCommand({
resolvedImageUrl = await resolveFileUrl(imageUrl, credential.token, model);
}
const watermark = resolveWatermark(flags.watermark);
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
const body: DashScopeVideoRequest = {
model,
input: {
@@ -124,8 +136,8 @@ export default defineCommand({
resolution: normalizeResolution(flags.resolution as string) || undefined,
ratio: (flags.ratio as string) || undefined,
duration: (flags.duration as number) || undefined,
prompt_extend: flags.promptExtend === true ? true : undefined,
watermark: flags.watermark === true ? true : undefined,
prompt_extend: promptExtend,
watermark,
seed: flags.seed as number | undefined,
},
};
+14 -6
View File
@@ -15,6 +15,8 @@ import {
resolveCredential,
BailianError,
ExitCode,
resolveBooleanFlag,
resolveWatermark,
} from "bailian-cli-core";
import { poll } from "../../utils/polling.ts";
import { downloadFile, formatBytes } from "../../utils/download.ts";
@@ -61,9 +63,14 @@ export default defineCommand({
description: "Video duration in seconds (2-10, default: 5)",
type: "number",
},
{ flag: "--prompt-extend", description: "Enable prompt intelligent rewriting" },
{ flag: "--no-prompt-extend", description: "Disable prompt intelligent rewriting" },
{ flag: "--watermark", description: "Add watermark to generated video" },
{
flag: "--prompt-extend <bool>",
description: "Enable prompt extend (true/false). Omit to use API default.",
},
{
flag: "--watermark <bool>",
description: "Enable watermark (true/false). Default: true.",
},
{ flag: "--seed <n>", description: "Random seed for reproducible generation", type: "number" },
{ flag: "--download <path>", description: "Save video to file on completion" },
{ flag: "--no-wait", description: "Return task ID immediately without waiting" },
@@ -82,6 +89,7 @@ export default defineCommand({
'bl video ref --prompt "视频1在弹吉他,图1走过来" --ref-video scene.mp4 --image person.jpg',
'bl video ref --prompt "图1说话" --image person.jpg --image-voice voice.mp3 --resolution 1080P',
'bl video ref --prompt "图1和图2在对话" --image a.jpg --image b.jpg --image-voice va.mp3 --image-voice vb.mp3',
'bl video ref --prompt "图1在喝水" --image person.jpg --watermark false',
],
async run(config: Config, flags: GlobalFlags) {
// --- Validate prompt ---
@@ -157,8 +165,8 @@ export default defineCommand({
}
// --- Build request body ---
const promptExtend =
flags.noPromptExtend === true ? false : flags.promptExtend === true ? true : undefined;
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
const watermark = resolveWatermark(flags.watermark);
const body: DashScopeVideoRefRequest = {
model,
@@ -171,7 +179,7 @@ export default defineCommand({
ratio: (flags.ratio as string) || undefined,
duration: (flags.duration as number) || undefined,
prompt_extend: promptExtend,
watermark: flags.watermark === true ? true : undefined,
watermark,
seed: flags.seed as number | undefined,
},
};
+19 -25
View File
@@ -14,6 +14,8 @@ import {
resolveFileUrl,
resolveCredential,
stripUndefined,
resolveBooleanFlag,
resolveWatermark,
type Config,
type ChatRequest,
type ChatResponse,
@@ -163,8 +165,7 @@ export interface ImageGenerateInput {
n?: number;
seed?: number;
"negative-prompt"?: string;
"prompt-extend"?: boolean;
"no-prompt-extend"?: boolean;
"prompt-extend"?: boolean | string;
watermark?: boolean;
"out-dir"?: string;
"out-prefix"?: string;
@@ -185,14 +186,11 @@ export async function imageGenerate(
const useSync = isSyncImageModel(model);
const n = input.n ?? 1;
let promptExtend: boolean | undefined;
if (input["no-prompt-extend"]) {
promptExtend = false;
} else if (input["prompt-extend"]) {
promptExtend = true;
} else if (useSync) {
promptExtend = true;
}
const promptExtend = resolveBooleanFlag(
input["prompt-extend"],
useSync ? true : undefined,
"prompt-extend",
);
const body: DashScopeImageRequest = {
model,
@@ -204,7 +202,7 @@ export async function imageGenerate(
n,
seed: input.seed,
prompt_extend: promptExtend,
watermark: input.watermark === true ? true : undefined,
watermark: resolveWatermark(input.watermark),
negative_prompt: input["negative-prompt"] || undefined,
},
};
@@ -252,8 +250,7 @@ export interface ImageEditInput {
n?: number;
seed?: number;
"negative-prompt"?: string;
"prompt-extend"?: boolean;
"no-prompt-extend"?: boolean;
"prompt-extend"?: boolean | string;
watermark?: boolean;
"out-dir"?: string;
"out-prefix"?: string;
@@ -275,14 +272,11 @@ export async function imageEdit(
const useSync = isSyncImageModel(model);
const n = input.n ?? 1;
let promptExtend: boolean | undefined;
if (input["no-prompt-extend"]) {
promptExtend = false;
} else if (input["prompt-extend"]) {
promptExtend = true;
} else if (useSync) {
promptExtend = true;
}
const promptExtend = resolveBooleanFlag(
input["prompt-extend"],
useSync ? true : undefined,
"prompt-extend",
);
const content: Array<{ text?: string; image?: string }> = [];
for (const img of images) {
@@ -305,7 +299,7 @@ export async function imageEdit(
n,
seed: input.seed,
prompt_extend: promptExtend,
watermark: input.watermark === true ? true : undefined,
watermark: resolveWatermark(input.watermark),
negative_prompt: input["negative-prompt"] || undefined,
},
};
@@ -380,7 +374,7 @@ export interface VideoGenerateInput {
resolution?: string;
ratio?: string;
duration?: number;
"prompt-extend"?: boolean;
"prompt-extend"?: boolean | string;
watermark?: boolean;
seed?: number;
"poll-interval"?: number;
@@ -424,8 +418,8 @@ export async function videoGenerate(
resolution: input.resolution || undefined,
ratio: input.ratio || undefined,
duration: input.duration,
prompt_extend: input["prompt-extend"],
watermark: input.watermark,
prompt_extend: resolveBooleanFlag(input["prompt-extend"], undefined, "prompt-extend"),
watermark: resolveWatermark(input.watermark),
seed: input.seed,
},
};
-1
View File
@@ -67,7 +67,6 @@ const PARAM_ALLOWLIST = new Set([
"noWait",
"textOnly",
"promptExtend",
"noPromptExtend",
"enableSsml",
"watermark",
"hasThoughts",
+6
View File
@@ -5,3 +5,9 @@ export { maskToken } from "./token.ts";
export { isInteractive } from "./env.ts";
export { isCI } from "./env.ts";
export { stripUndefined } from "./object.ts";
export {
parseBooleanValue,
parseOptionalBooleanValue,
resolveBooleanFlag,
resolveWatermark,
} from "./watermark.ts";
+40
View File
@@ -0,0 +1,40 @@
import { BailianError } from "../errors/base.ts";
import { ExitCode } from "../errors/codes.ts";
/** Parse true/false from CLI flags (e.g. `--watermark`). */
export function parseBooleanValue(value: unknown, label = "boolean"): boolean {
if (typeof value === "boolean") return value;
if (typeof value === "string") {
const v = value.trim().toLowerCase();
if (v === "true") return true;
if (v === "false") return false;
}
throw new BailianError(
`Invalid ${label} value "${String(value)}". Use true or false.`,
ExitCode.USAGE,
);
}
export function parseOptionalBooleanValue(value: unknown, label = "boolean"): boolean | undefined {
if (value === undefined || value === null) return undefined;
return parseBooleanValue(value, label);
}
/**
* Resolve a tri-state boolean CLI flag (`--name <bool>`).
* Returns `defaultWhenUnset` when the flag is omitted.
*/
export function resolveBooleanFlag(
flagValue: unknown,
defaultWhenUnset: boolean | undefined,
label = "boolean",
): boolean | undefined {
const fromFlag = parseOptionalBooleanValue(flagValue, label);
if (fromFlag !== undefined) return fromFlag;
return defaultWhenUnset;
}
/** Resolve `--watermark` flag; default true when unset. */
export function resolveWatermark(flagValue: unknown): boolean {
return resolveBooleanFlag(flagValue, true, "watermark") ?? true;
}
+21
View File
@@ -2,6 +2,7 @@ import { expect, test } from "vite-plus/test";
import type { Config } from "../src/index.ts";
import { BailianError, ExitCode, McpClient, mapApiError, request } from "../src/index.ts";
import { parseConfigFile } from "../src/config/schema.ts";
import { parseBooleanValue, resolveBooleanFlag, resolveWatermark } from "../src/utils/watermark.ts";
function testConfig(overrides: Partial<Config> = {}): Config {
return {
@@ -175,6 +176,26 @@ test("McpClient uses injected client identity for initialize and User-Agent", as
});
});
test("resolveWatermark uses flag or defaults to true", () => {
expect(resolveWatermark("false")).toBe(false);
expect(resolveWatermark("true")).toBe(true);
expect(resolveWatermark(undefined)).toBe(true);
});
test("resolveBooleanFlag uses flag or defaultWhenUnset", () => {
expect(resolveBooleanFlag("false", true, "prompt-extend")).toBe(false);
expect(resolveBooleanFlag(undefined, true, "prompt-extend")).toBe(true);
expect(resolveBooleanFlag(undefined, undefined, "prompt-extend")).toBeUndefined();
});
test("parseBooleanValue accepts only true and false strings (case-insensitive)", () => {
expect(parseBooleanValue("true")).toBe(true);
expect(parseBooleanValue("FALSE")).toBe(false);
expect(() => parseBooleanValue("1")).toThrow(BailianError);
expect(() => parseBooleanValue("yes")).toThrow(BailianError);
expect(() => parseBooleanValue("maybe")).toThrow(BailianError);
});
test("parseConfigFile accepts only well-formed http(s) base_url / console_gateway_url", () => {
expect(parseConfigFile({ base_url: "https://dashscope.aliyuncs.com" }).base_url).toBe(
"https://dashscope.aliyuncs.com",