mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
refactor(flags): keyed type-inferred flag schema; option→flag rename
Replace the positional `OptionDef[]` array (key/type regex-parsed from "--x <v>" strings) with a keyed `FlagsDef` record whose `type` drives both runtime parsing and compile-time flag-type inference (`Flags<typeof DEF>`). GLOBAL_FLAGS becomes the single source; the hand-kept GlobalFlags interface (types/flags.ts) is deleted. - core: SwitchFlag|ValueFlag union, ParsedFlags/Flags inference, defineCommand infers F from spec.flags - runtime: parseFlags dispatches on def.type (switch/string/number/boolean/ array/choices) with declarative required-flag enforcement - commands: migrate every flag declaration to the keyed form - naming: option→flag throughout (OptionDef→FlagDef, OptionsDef→FlagsDef, GLOBAL_OPTIONS→GLOBAL_FLAGS, command field options→flags), plus user-facing "Options:"→"Flags:" in help and the regenerated skill reference docs Behavior-preserving aside from the intentional Options→Flags wording: vp check clean across all packages, 29 parser tests pass, reference regen byte-identical before the terminology swap.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Command } from "bailian-cli-core";
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import {
|
||||
authLogin,
|
||||
authStatus,
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
// ships no presets, so the map is spelled out here. Kept in its own module
|
||||
// (no side effects) so tools like generate-reference.ts can import it without
|
||||
// starting the CLI.
|
||||
export const commands: Record<string, Command> = {
|
||||
export const commands: Record<string, AnyCommand> = {
|
||||
"auth login": authLogin,
|
||||
"auth status": authStatus,
|
||||
"auth logout": authLogout,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
analyzeIntent,
|
||||
buildDocLink,
|
||||
type Config,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
type GetModelsOptions,
|
||||
type GlobalFlags,
|
||||
getModels,
|
||||
type IntentProfile,
|
||||
type PipelineStep,
|
||||
@@ -217,13 +215,14 @@ export default defineCommand({
|
||||
"Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--message <text> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--message <text>",
|
||||
flags: {
|
||||
message: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Describe your requirements",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
'--message "I need a visual-understanding chatbot"',
|
||||
'--message "Build an Agent that auto-generates animations"',
|
||||
@@ -231,8 +230,8 @@ export default defineCommand({
|
||||
'--message "Low-cost high-concurrency online customer service" --output json',
|
||||
'--message "Long document summarization" --dry-run',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const userInput = flags.message as string;
|
||||
async run(config, flags) {
|
||||
const userInput = flags.message;
|
||||
|
||||
const top = 3;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
appCompletionEndpoint,
|
||||
parseSSE,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type AppCompletionRequest,
|
||||
type AppStreamChunk,
|
||||
type AppCompletionResponse,
|
||||
@@ -17,22 +15,48 @@ export default defineCommand({
|
||||
description: "Call a Bailian application (agent or workflow)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--app-id <id> --prompt <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--app-id <id>", description: "Application ID (required)", required: true },
|
||||
{ flag: "--prompt <text>", description: "Input prompt text", required: true },
|
||||
{
|
||||
flag: "--image <url>",
|
||||
description: "Image URL(s) to pass to the app (repeatable)",
|
||||
type: "array",
|
||||
flags: {
|
||||
appId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Application ID (required)",
|
||||
required: true,
|
||||
},
|
||||
{ flag: "--file-id <id>", description: "Pre-uploaded file ID(s) (repeatable)", type: "array" },
|
||||
{ flag: "--session-id <id>", description: "Session ID for multi-turn conversation" },
|
||||
{ flag: "--stream", description: "Stream response (default: on in TTY)" },
|
||||
{ flag: "--pipeline-ids <ids>", description: "Knowledge base pipeline IDs (comma-separated)" },
|
||||
{ flag: "--memory-id <id>", description: "Memory ID for long-term memory" },
|
||||
{ flag: "--biz-params <json>", description: "Business parameters JSON (workflow variables)" },
|
||||
{ flag: "--has-thoughts", description: "Show agent thinking process" },
|
||||
],
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Input prompt text",
|
||||
required: true,
|
||||
},
|
||||
image: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Image URL(s) to pass to the app (repeatable)",
|
||||
},
|
||||
fileId: {
|
||||
type: "array",
|
||||
valueHint: "<id>",
|
||||
description: "Pre-uploaded file ID(s) (repeatable)",
|
||||
},
|
||||
sessionId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Session ID for multi-turn conversation",
|
||||
},
|
||||
stream: { type: "switch", description: "Stream response (default: on in TTY)" },
|
||||
pipelineIds: {
|
||||
type: "string",
|
||||
valueHint: "<ids>",
|
||||
description: "Knowledge base pipeline IDs (comma-separated)",
|
||||
},
|
||||
memoryId: { type: "string", valueHint: "<id>", description: "Memory ID for long-term memory" },
|
||||
bizParams: {
|
||||
type: "string",
|
||||
valueHint: "<json>",
|
||||
description: "Business parameters JSON (workflow variables)",
|
||||
},
|
||||
hasThoughts: { type: "switch", description: "Show agent thinking process" },
|
||||
},
|
||||
exampleArgs: [
|
||||
'--app-id abc123 --prompt "Hello"',
|
||||
'--app-id abc123 --prompt "Describe this image" --image https://example.com/photo.jpg',
|
||||
@@ -41,12 +65,11 @@ export default defineCommand({
|
||||
'--app-id abc123 --prompt "Search for materials" --pipeline-ids pipe1,pipe2',
|
||||
'--app-id abc123 --prompt "Start" --biz-params \'{"key":"value"}\'',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const appId = flags.appId as string;
|
||||
const prompt = flags.prompt as string;
|
||||
async run(config, flags) {
|
||||
const appId = flags.appId;
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const shouldStream =
|
||||
flags.stream === true || (flags.stream === undefined && process.stdout.isTTY);
|
||||
const shouldStream = flags.stream || process.stdout.isTTY;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const body: AppCompletionRequest = {
|
||||
@@ -57,17 +80,17 @@ export default defineCommand({
|
||||
};
|
||||
|
||||
if (flags.sessionId) {
|
||||
body.input.session_id = flags.sessionId as string;
|
||||
body.input.session_id = flags.sessionId;
|
||||
}
|
||||
|
||||
// Pass image URLs via image_list
|
||||
const imageUrls = flags.image as string[] | undefined;
|
||||
const imageUrls = flags.image;
|
||||
if (imageUrls && imageUrls.length > 0) {
|
||||
body.input.image_list = imageUrls;
|
||||
}
|
||||
|
||||
// Pass pre-uploaded file IDs
|
||||
const fileIds = flags.fileId as string[] | undefined;
|
||||
const fileIds = flags.fileId;
|
||||
if (fileIds && fileIds.length > 0) {
|
||||
body.input.file_ids = fileIds;
|
||||
}
|
||||
@@ -77,7 +100,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (flags.pipelineIds) {
|
||||
const ids = (flags.pipelineIds as string)
|
||||
const ids = flags.pipelineIds
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
@@ -85,12 +108,12 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (flags.memoryId) {
|
||||
body.parameters!.memory_id = flags.memoryId as string;
|
||||
body.parameters!.memory_id = flags.memoryId;
|
||||
}
|
||||
|
||||
if (flags.bizParams) {
|
||||
try {
|
||||
body.input.biz_params = JSON.parse(flags.bizParams as string);
|
||||
body.input.biz_params = JSON.parse(flags.bizParams);
|
||||
} catch {
|
||||
process.stderr.write("Error: --biz-params must be valid JSON\n");
|
||||
process.exit(1);
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
callConsoleGateway,
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -14,37 +12,35 @@ export default defineCommand({
|
||||
description: "List Bailian applications",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--name <name>",
|
||||
flags: {
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<name>",
|
||||
description: "Filter by app name (keyword search)",
|
||||
},
|
||||
{
|
||||
flag: "--page <n>",
|
||||
page: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Page number (default: 1)",
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
flag: "--page-size <n>",
|
||||
pageSize: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Results per page (default: 30)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: ["", "--name customer service", "--page 2 --page-size 10", "--output json"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const name = (flags.name as string) || "";
|
||||
const pageNo = (flags.page as number) || 1;
|
||||
const pageSize = (flags.pageSize as number) || 30;
|
||||
async run(config, flags) {
|
||||
const name = flags.name || "";
|
||||
const pageNo = flags.page || 1;
|
||||
const pageSize = flags.pageSize || 30;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const credential = await resolveConsoleGatewayCredential(config);
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
defineCommand,
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { defineCommand, readConfigFile, writeConfigFile } from "bailian-cli-core";
|
||||
import { printQuickStart } from "bailian-cli-runtime";
|
||||
import { emitBare } from "bailian-cli-runtime";
|
||||
import {
|
||||
@@ -17,21 +11,22 @@ export default defineCommand({
|
||||
description: "Authenticate with API key or console browser login (credentials can coexist)",
|
||||
auth: "none",
|
||||
usageArgs: "--api-key <key> | --console",
|
||||
options: [
|
||||
{ flag: "--api-key <key>", description: "DashScope API key to store" },
|
||||
{
|
||||
flag: "--base-url <url>",
|
||||
flags: {
|
||||
apiKey: { type: "string", valueHint: "<key>", description: "DashScope API key to store" },
|
||||
baseUrl: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "DashScope API base URL (used with --api-key for validation)",
|
||||
},
|
||||
{
|
||||
flag: "--console",
|
||||
console: {
|
||||
type: "switch",
|
||||
description:
|
||||
"Sign in via browser; use --console-site to choose domestic (default) or international",
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: ["--api-key sk-xxxxx", "--console"],
|
||||
validate: (f) => (!f.console && !f.apiKey ? "Provide --api-key or --console" : undefined),
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
if (flags.console) {
|
||||
if (config.dryRun) {
|
||||
emitBare(
|
||||
@@ -46,17 +41,16 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const envKey = process.env.DASHSCOPE_API_KEY;
|
||||
if (envKey && !flags.apiKey) {
|
||||
process.stderr.write(`Warning: DASHSCOPE_API_KEY is already set in environment.\n`);
|
||||
}
|
||||
// --api-key path; validate() guarantees apiKey on the non-console branch.
|
||||
if (flags.apiKey) {
|
||||
const key = flags.apiKey;
|
||||
const baseUrl = flags.baseUrl || undefined;
|
||||
const effectiveConfig = baseUrl ? { ...config, baseUrl } : config;
|
||||
|
||||
const key = flags.apiKey as string;
|
||||
|
||||
const baseUrl = (flags.baseUrl as string) || undefined;
|
||||
const effectiveConfig = baseUrl ? { ...config, baseUrl } : config;
|
||||
|
||||
if (!config.dryRun) {
|
||||
if (config.dryRun) {
|
||||
emitBare("Would validate and save API key.");
|
||||
return;
|
||||
}
|
||||
if (baseUrl) {
|
||||
const existing = readConfigFile() as Record<string, unknown>;
|
||||
existing.base_url = baseUrl;
|
||||
@@ -64,8 +58,6 @@ export default defineCommand({
|
||||
}
|
||||
await validateAndPersistApiKey(effectiveConfig, key, effectiveConfig.baseUrl);
|
||||
printQuickStart();
|
||||
} else {
|
||||
emitBare("Would validate and save API key.");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
getConfigPath,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare } from "bailian-cli-runtime";
|
||||
|
||||
@@ -21,16 +19,15 @@ export default defineCommand({
|
||||
description: "Clear stored credentials",
|
||||
auth: "none",
|
||||
usageArgs: "[--console] [--yes] [--dry-run]",
|
||||
options: [
|
||||
{
|
||||
flag: "--console",
|
||||
flags: {
|
||||
console: {
|
||||
type: "switch",
|
||||
description: "Only clear the console access_token, keep api_key intact",
|
||||
type: "boolean",
|
||||
},
|
||||
{ flag: "--yes", description: "Skip confirmation prompt" },
|
||||
],
|
||||
yes: { type: "switch", description: "Skip confirmation prompt" },
|
||||
},
|
||||
exampleArgs: ["", "--console", "--dry-run", "--yes"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
const file = readConfigFile();
|
||||
|
||||
if (flags.console) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
detectOutputFormat,
|
||||
maskToken,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type ResolvedCredential,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
@@ -141,20 +140,17 @@ function emitTextStatus(status: AuthStatusPayload, config: Config): void {
|
||||
export default defineCommand({
|
||||
description: "Show current authentication state",
|
||||
auth: "none",
|
||||
options: [
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
exampleArgs: ["", "--output json"],
|
||||
flags: {
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
exampleArgs: ["", "--output json"],
|
||||
async run(config: Config, _flags: GlobalFlags) {
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
async run(config, _flags) {
|
||||
const format = detectOutputFormat(config.output);
|
||||
const status = await buildStatus(config);
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
BailianError,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
@@ -53,23 +51,24 @@ export default defineCommand({
|
||||
description: "Set a config value",
|
||||
auth: "none",
|
||||
usageArgs: "--key <key> --value <value>",
|
||||
options: [
|
||||
{
|
||||
flag: "--key <key>",
|
||||
flags: {
|
||||
key: {
|
||||
type: "string",
|
||||
valueHint: "<key>",
|
||||
description:
|
||||
"Config key (base_url, output, output_dir, timeout, api_key, access_token, default_*_model, access_key_id, access_key_secret, workspace_id)",
|
||||
required: true,
|
||||
},
|
||||
{ flag: "--value <value>", description: "Value to set", required: true },
|
||||
],
|
||||
value: { type: "string", valueHint: "<value>", description: "Value to set", required: true },
|
||||
},
|
||||
exampleArgs: [
|
||||
"--key output --value json",
|
||||
"--key timeout --value 600",
|
||||
"--key base_url --value https://dashscope.aliyuncs.com",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const key = flags.key as string;
|
||||
const value = flags.value as string;
|
||||
async run(config, flags) {
|
||||
const key = flags.key;
|
||||
const value = flags.value;
|
||||
|
||||
// Resolve hyphen aliases to underscore keys
|
||||
const resolvedKey: string = KEY_ALIASES[key] || key;
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
getConfigPath,
|
||||
detectOutputFormat,
|
||||
maskToken,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -13,7 +11,7 @@ export default defineCommand({
|
||||
description: "Display current configuration",
|
||||
auth: "none",
|
||||
exampleArgs: ["", "--output json"],
|
||||
async run(config: Config, _flags: GlobalFlags) {
|
||||
async run(config, _flags) {
|
||||
const file = loadConfigFile();
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
CONSOLE_GATEWAY_NO_TOKEN_MESSAGE,
|
||||
BailianError,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -15,35 +13,34 @@ export default defineCommand({
|
||||
description: "Call a Bailian console API via the CLI gateway",
|
||||
auth: "console",
|
||||
usageArgs: "--api <api> --data <json> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--api <api>",
|
||||
flags: {
|
||||
api: {
|
||||
type: "string",
|
||||
valueHint: "<api>",
|
||||
description: "API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--data <json>",
|
||||
data: {
|
||||
type: "string",
|
||||
valueHint: "<json>",
|
||||
description: "Request data as JSON string",
|
||||
required: true,
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
`--api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`,
|
||||
`--api some.api.name --data '{"key":"value"}' --console-region cn-beijing`,
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const api = flags.api as string;
|
||||
const dataRaw = flags.data as string;
|
||||
async run(config, flags) {
|
||||
const api = flags.api;
|
||||
const dataRaw = flags.data;
|
||||
|
||||
let data: Record<string, unknown>;
|
||||
try {
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
import {
|
||||
defineCommand,
|
||||
resolveCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
uploadFile,
|
||||
} from "bailian-cli-core";
|
||||
import { defineCommand, resolveCredential, detectOutputFormat, uploadFile } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
export default defineCommand({
|
||||
description: "Upload a local file to DashScope temporary storage (48h)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--file <path> --model <model>",
|
||||
options: [
|
||||
{
|
||||
flag: "--file <path>",
|
||||
flags: {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Local file to upload (image, video, audio)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--model <model>",
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Target model name (file is bound to this model)",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
"--file photo.jpg --model qwen3-vl-plus",
|
||||
"--file video.mp4 --model wan2.1-t2v-plus",
|
||||
"--file audio.wav --model qwen3-asr-flash",
|
||||
"--file cat.png --model qwen-image-2.0",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const filePath = flags.file as string;
|
||||
const model = flags.model as string;
|
||||
async run(config, flags) {
|
||||
const filePath = flags.file;
|
||||
const model = flags.model;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
imageSyncEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
resolveCredential,
|
||||
resolveFileUrl,
|
||||
resolveOutputDir,
|
||||
@@ -28,38 +26,57 @@ export default defineCommand({
|
||||
description: "Edit an existing image with text instructions (Qwen-Image)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--image <url> --prompt <text> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--image <url>",
|
||||
flags: {
|
||||
image: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Source image URL or local file path (repeatable for multi-image merge)",
|
||||
required: true,
|
||||
type: "array",
|
||||
},
|
||||
{ flag: "--prompt <text>", description: "Edit instruction text", required: true },
|
||||
{ flag: "--model <model>", description: "Model ID (default: qwen-image-2.0)" },
|
||||
{
|
||||
flag: "--size <W*H>",
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Edit instruction text",
|
||||
required: true,
|
||||
},
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: qwen-image-2.0)",
|
||||
},
|
||||
size: {
|
||||
type: "string",
|
||||
valueHint: "<W*H>",
|
||||
description: "Output image size: ratio (3:4, 16:9) or pixels (2048*2048)",
|
||||
},
|
||||
{ flag: "--n <count>", description: "Number of images (default: 1, max: 6)", type: "number" },
|
||||
{ flag: "--seed <n>", description: "Random seed for reproducible results", type: "number" },
|
||||
{
|
||||
flag: "--negative-prompt <text>",
|
||||
n: {
|
||||
type: "number",
|
||||
valueHint: "<count>",
|
||||
description: "Number of images (default: 1, max: 6)",
|
||||
},
|
||||
seed: { type: "number", valueHint: "<n>", description: "Random seed for reproducible results" },
|
||||
negativePrompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Negative prompt to exclude unwanted content",
|
||||
},
|
||||
{
|
||||
flag: "--prompt-extend <bool>",
|
||||
promptExtend: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--watermark <bool>",
|
||||
watermark: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
type: "boolean",
|
||||
},
|
||||
{ flag: "--out-dir <dir>", description: "Download images to directory" },
|
||||
{ flag: "--out-prefix <prefix>", description: "Filename prefix (default: edited)" },
|
||||
],
|
||||
outDir: { type: "string", valueHint: "<dir>", description: "Download images to directory" },
|
||||
outPrefix: {
|
||||
type: "string",
|
||||
valueHint: "<prefix>",
|
||||
description: "Filename prefix (default: edited)",
|
||||
},
|
||||
},
|
||||
exampleArgs: [
|
||||
'--image ./photo.png --prompt "Replace the background with a beach"',
|
||||
'--image https://example.com/logo.png --prompt "Change color to blue" --n 3',
|
||||
@@ -67,24 +84,24 @@ export default defineCommand({
|
||||
'--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro',
|
||||
'--image ./photo.png --prompt "Replace the background with a beach" --watermark false',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
// Normalize --image to string array (supports both single and repeated flags)
|
||||
let rawImages: string[] = [];
|
||||
if (Array.isArray(flags.image)) {
|
||||
rawImages = flags.image as string[];
|
||||
rawImages = flags.image;
|
||||
} else if (typeof flags.image === "string") {
|
||||
rawImages = [flags.image];
|
||||
}
|
||||
const prompt = flags.prompt as string;
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model = (flags.model as string) || config.defaultImageModel || "qwen-image-2.0";
|
||||
const model = flags.model || config.defaultImageModel || "qwen-image-2.0";
|
||||
|
||||
// Auto-upload local files (resolve all images in parallel)
|
||||
const credential = await resolveCredential(config);
|
||||
const resolvedImages = await Promise.all(
|
||||
rawImages.map((img) => resolveFileUrl(img, credential.token, model)),
|
||||
);
|
||||
const n = (flags.n as number) ?? 1;
|
||||
const n = flags.n ?? 1;
|
||||
|
||||
const promptExtend = resolveBooleanFlag(flags.promptExtend, true, "prompt-extend");
|
||||
|
||||
@@ -92,7 +109,7 @@ export default defineCommand({
|
||||
const contentItems: Array<{ image?: string; text?: string }> = resolvedImages.map(
|
||||
(u: string) => ({ image: u }),
|
||||
);
|
||||
contentItems.push({ text: prompt! });
|
||||
contentItems.push({ text: prompt });
|
||||
|
||||
const watermark = resolveWatermark(flags.watermark);
|
||||
|
||||
@@ -107,12 +124,12 @@ export default defineCommand({
|
||||
],
|
||||
},
|
||||
parameters: {
|
||||
size: resolveImageSize(flags.size as string | undefined, true),
|
||||
size: resolveImageSize(flags.size, true),
|
||||
n,
|
||||
seed: flags.seed as number | undefined,
|
||||
seed: flags.seed,
|
||||
prompt_extend: promptExtend,
|
||||
watermark,
|
||||
negative_prompt: (flags.negativePrompt as string) || undefined,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -153,12 +170,11 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
const outDir = resolveOutputDir(config, {
|
||||
flagDir: flags.outDir as string | undefined,
|
||||
flagDir: flags.outDir,
|
||||
subDir: flags.outDir ? undefined : "images",
|
||||
});
|
||||
|
||||
const prefix =
|
||||
(flags.outPrefix as string) || generateFilename("edited", flags?.prompt as string);
|
||||
const prefix = flags.outPrefix || generateFilename("edited", flags.prompt);
|
||||
|
||||
// Parallel download all images
|
||||
const items =
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
resolveOutputDir,
|
||||
type DashScopeImageRequest,
|
||||
type DashScopeImageSyncResponse,
|
||||
@@ -35,49 +36,66 @@ function isSyncModel(model: string): boolean {
|
||||
return SYNC_MODEL_PREFIXES.some((p) => model.startsWith(p));
|
||||
}
|
||||
|
||||
const GENERATE_FLAGS = {
|
||||
prompt: { type: "string", valueHint: "<text>", description: "Image description", required: true },
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: qwen-image-2.0)",
|
||||
},
|
||||
size: {
|
||||
type: "string",
|
||||
valueHint: "<W*H>",
|
||||
description: "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)",
|
||||
},
|
||||
n: {
|
||||
type: "number",
|
||||
valueHint: "<count>",
|
||||
description: "Number of images per request (default: 1, max: 6)",
|
||||
},
|
||||
seed: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Random seed for reproducible generation",
|
||||
},
|
||||
negativePrompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Negative prompt to exclude unwanted content",
|
||||
},
|
||||
promptExtend: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE,
|
||||
},
|
||||
watermark: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
},
|
||||
noWait: {
|
||||
type: "switch",
|
||||
description: "Return task ID immediately without waiting (async models only)",
|
||||
},
|
||||
outDir: { type: "string", valueHint: "<dir>", description: "Download images to directory" },
|
||||
outPrefix: {
|
||||
type: "string",
|
||||
valueHint: "<prefix>",
|
||||
description: "Filename prefix (default: image)",
|
||||
},
|
||||
pollInterval: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Polling interval when waiting (default: 3)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type GenerateFlags = Flags<typeof GENERATE_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Generate images (Qwen-Image / wan2.x)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--prompt <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--prompt <text>", description: "Image description", required: true },
|
||||
{ flag: "--model <model>", description: "Model ID (default: qwen-image-2.0)" },
|
||||
{
|
||||
flag: "--size <W*H>",
|
||||
description: "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)",
|
||||
},
|
||||
{
|
||||
flag: "--n <count>",
|
||||
description: "Number of images per request (default: 1, max: 6)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--seed <n>", description: "Random seed for reproducible generation", type: "number" },
|
||||
{
|
||||
flag: "--negative-prompt <text>",
|
||||
description: "Negative prompt to exclude unwanted content",
|
||||
},
|
||||
{
|
||||
flag: "--prompt-extend <bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--watermark <bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--no-wait",
|
||||
description: "Return task ID immediately without waiting (async models only)",
|
||||
},
|
||||
{ flag: "--out-dir <dir>", description: "Download images to directory" },
|
||||
{ flag: "--out-prefix <prefix>", description: "Filename prefix (default: image)" },
|
||||
{
|
||||
flag: "--poll-interval <seconds>",
|
||||
description: "Polling interval when waiting (default: 3)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
flags: GENERATE_FLAGS,
|
||||
exampleArgs: [
|
||||
'--prompt "A cat in a spacesuit on Mars"',
|
||||
'--prompt "Logo design" --n 3 --out-dir ./generated/',
|
||||
@@ -89,15 +107,15 @@ export default defineCommand({
|
||||
'--prompt "Pro quality" --model qwen-image-2.0-pro',
|
||||
'--prompt "Product shots" --n 2 --concurrent 3 # 6 images in parallel',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const prompt = flags.prompt as string;
|
||||
async run(config, flags) {
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model = (flags.model as string) || config.defaultImageModel || "qwen-image-2.0";
|
||||
const model = flags.model || config.defaultImageModel || "qwen-image-2.0";
|
||||
const useSync = isSyncModel(model);
|
||||
const defaultSize = useSync ? "1:1" : "1:1";
|
||||
const sizeInput = (flags.size as string) || defaultSize;
|
||||
const sizeInput = flags.size || defaultSize;
|
||||
const size = resolveImageSize(sizeInput, useSync);
|
||||
const n = (flags.n as number) ?? 1;
|
||||
const n = flags.n ?? 1;
|
||||
const concurrent = getConcurrency(flags);
|
||||
|
||||
const promptExtend = resolveBooleanFlag(
|
||||
@@ -111,15 +129,15 @@ export default defineCommand({
|
||||
const body: DashScopeImageRequest = {
|
||||
model,
|
||||
input: {
|
||||
messages: [{ role: "user", content: [{ text: prompt! }] }],
|
||||
messages: [{ role: "user", content: [{ text: prompt }] }],
|
||||
},
|
||||
parameters: {
|
||||
size,
|
||||
n,
|
||||
seed: flags.seed as number | undefined,
|
||||
seed: flags.seed,
|
||||
prompt_extend: promptExtend,
|
||||
watermark,
|
||||
negative_prompt: (flags.negativePrompt as string) || undefined,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -148,7 +166,7 @@ async function handleSyncMode(
|
||||
config: Config,
|
||||
_model: string,
|
||||
body: DashScopeImageRequest,
|
||||
flags: GlobalFlags,
|
||||
flags: GenerateFlags,
|
||||
format: string,
|
||||
concurrent: number,
|
||||
): Promise<void> {
|
||||
@@ -177,7 +195,7 @@ async function handleAsyncMode(
|
||||
config: Config,
|
||||
_model: string,
|
||||
body: DashScopeImageRequest,
|
||||
flags: GlobalFlags,
|
||||
flags: GenerateFlags,
|
||||
format: string,
|
||||
concurrent: number,
|
||||
): Promise<void> {
|
||||
@@ -198,7 +216,7 @@ async function handleAsyncMode(
|
||||
}
|
||||
|
||||
// Poll all tasks concurrently
|
||||
const pollInterval = (flags.pollInterval as number) ?? 3;
|
||||
const pollInterval = flags.pollInterval ?? 3;
|
||||
|
||||
const pollPromises = taskIds.map((taskId) => {
|
||||
const pollUrl = taskEndpoint(config.baseUrl, taskId);
|
||||
@@ -253,19 +271,19 @@ async function handleAsyncMode(
|
||||
|
||||
async function saveImages(
|
||||
imageUrls: string[],
|
||||
flags: GlobalFlags,
|
||||
flags: GenerateFlags,
|
||||
config: Config,
|
||||
format: string,
|
||||
taskId?: string,
|
||||
taskIds?: string[],
|
||||
): Promise<void> {
|
||||
const outDir = resolveOutputDir(config, {
|
||||
flagDir: flags.outDir as string | undefined,
|
||||
flagDir: flags.outDir,
|
||||
subDir: flags.outDir ? undefined : "images",
|
||||
});
|
||||
|
||||
const promptText = (flags.prompt as string) || "";
|
||||
const prefix = (flags.outPrefix as string) || generateFilename("image", promptText);
|
||||
const promptText = flags.prompt || "";
|
||||
const prefix = flags.outPrefix || generateFilename("image", promptText);
|
||||
|
||||
// Parallel download all images
|
||||
const items =
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
resolveCredential,
|
||||
trackingHeaders,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type Flags,
|
||||
type FlagsDef,
|
||||
type KnowledgeRetrieveRequest,
|
||||
type KnowledgeRetrieveResponse,
|
||||
type DashScopeKnowledgeRetrieveRequest,
|
||||
@@ -21,55 +22,74 @@ import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const BAILIAN_HOST = "bailian.cn-beijing.aliyuncs.com";
|
||||
|
||||
const RETRIEVE_FLAGS = {
|
||||
indexId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Knowledge base index ID (required)",
|
||||
required: true,
|
||||
},
|
||||
query: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Search query (required)",
|
||||
required: true,
|
||||
},
|
||||
denseSimilarityTopK: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Dense retrieval top K",
|
||||
},
|
||||
sparseSimilarityTopK: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Sparse retrieval top K",
|
||||
},
|
||||
rerank: { type: "switch", description: "Enable reranking" },
|
||||
rerankTopN: { type: "number", valueHint: "<n>", description: "Rerank top N results" },
|
||||
rerankModel: {
|
||||
type: "string",
|
||||
valueHint: "<name>",
|
||||
description: "Rerank model, e.g. qwen3-rerank-hybrid",
|
||||
},
|
||||
rerankMode: {
|
||||
type: "string",
|
||||
valueHint: "<mode>",
|
||||
description: "Rerank mode: qa, similar, or custom",
|
||||
},
|
||||
rerankInstruct: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Custom rerank instruction, when mode=custom",
|
||||
},
|
||||
topK: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Number of results (deprecated, use --rerank-top-n)",
|
||||
},
|
||||
workspaceId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Bailian workspace ID (only needed for deprecated AK/SK auth)",
|
||||
},
|
||||
accessKeyId: {
|
||||
type: "string",
|
||||
valueHint: "<key>",
|
||||
description: "Deprecated: use global --api-key instead",
|
||||
},
|
||||
accessKeySecret: {
|
||||
type: "string",
|
||||
valueHint: "<key>",
|
||||
description: "Deprecated: use global --api-key instead",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type RetrieveFlags = Flags<typeof RETRIEVE_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Retrieve from a Bailian knowledge base",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--index-id <id> --query <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--index-id <id>", description: "Knowledge base index ID (required)", required: true },
|
||||
{ flag: "--query <text>", description: "Search query (required)", required: true },
|
||||
{
|
||||
flag: "--dense-similarity-top-k <n>",
|
||||
description: "Dense retrieval top K",
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
flag: "--sparse-similarity-top-k <n>",
|
||||
description: "Sparse retrieval top K",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--rerank", description: "Enable reranking" },
|
||||
{ flag: "--rerank-top-n <n>", description: "Rerank top N results", type: "number" },
|
||||
{
|
||||
flag: "--rerank-model <name>",
|
||||
description: "Rerank model, e.g. qwen3-rerank-hybrid",
|
||||
},
|
||||
{
|
||||
flag: "--rerank-mode <mode>",
|
||||
description: "Rerank mode: qa, similar, or custom",
|
||||
},
|
||||
{
|
||||
flag: "--rerank-instruct <text>",
|
||||
description: "Custom rerank instruction, when mode=custom",
|
||||
},
|
||||
{
|
||||
flag: "--top-k <n>",
|
||||
description: "Number of results (deprecated, use --rerank-top-n)",
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
flag: "--workspace-id <id>",
|
||||
description: "Bailian workspace ID (only needed for deprecated AK/SK auth)",
|
||||
},
|
||||
{
|
||||
flag: "--access-key-id <key>",
|
||||
description: "Deprecated: use global --api-key instead",
|
||||
},
|
||||
{
|
||||
flag: "--access-key-secret <key>",
|
||||
description: "Deprecated: use global --api-key instead",
|
||||
},
|
||||
],
|
||||
flags: RETRIEVE_FLAGS,
|
||||
notes: [
|
||||
"Authentication: pass `--api-key <key>`. AK/SK auth is deprecated and will be removed in a future version.",
|
||||
"`--workspace-id` is NOT required when using --api-key.",
|
||||
@@ -78,9 +98,9 @@ export default defineCommand({
|
||||
'--index-id idx_xxx --query "How to use Alibaba Cloud Bailian"',
|
||||
'--api-key $DASHSCOPE_API_KEY --index-id idx_xxx --query "RAG retrieval" --rerank --rerank-model qwen3-rerank-hybrid',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const indexId = flags.indexId as string;
|
||||
const query = flags.query as string;
|
||||
async run(config, flags) {
|
||||
const indexId = flags.indexId;
|
||||
const query = flags.query;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
@@ -113,7 +133,7 @@ export default defineCommand({
|
||||
|
||||
async function runWithApiKey(
|
||||
config: Config,
|
||||
flags: GlobalFlags,
|
||||
flags: RetrieveFlags,
|
||||
indexId: string,
|
||||
query: string,
|
||||
format: OutputFormat,
|
||||
@@ -130,18 +150,18 @@ async function runWithApiKey(
|
||||
};
|
||||
|
||||
if (flags.denseSimilarityTopK !== undefined)
|
||||
body.dense_similarity_top_k = flags.denseSimilarityTopK as number;
|
||||
body.dense_similarity_top_k = flags.denseSimilarityTopK;
|
||||
if (flags.sparseSimilarityTopK !== undefined)
|
||||
body.sparse_similarity_top_k = flags.sparseSimilarityTopK as number;
|
||||
body.sparse_similarity_top_k = flags.sparseSimilarityTopK;
|
||||
if (flags.rerank) body.enable_reranking = true;
|
||||
if (flags.rerankTopN !== undefined) body.rerank_top_n = flags.rerankTopN as number;
|
||||
if (flags.rerankTopN !== undefined) body.rerank_top_n = flags.rerankTopN;
|
||||
|
||||
if (flags.rerankModel) {
|
||||
const rerankEntry: { model_name: string; rerank_mode?: string; rerank_instruct?: string } = {
|
||||
model_name: flags.rerankModel as string,
|
||||
model_name: flags.rerankModel,
|
||||
};
|
||||
if (flags.rerankMode) rerankEntry.rerank_mode = flags.rerankMode as string;
|
||||
if (flags.rerankInstruct) rerankEntry.rerank_instruct = flags.rerankInstruct as string;
|
||||
if (flags.rerankMode) rerankEntry.rerank_mode = flags.rerankMode;
|
||||
if (flags.rerankInstruct) rerankEntry.rerank_instruct = flags.rerankInstruct;
|
||||
body.rerank = [rerankEntry];
|
||||
}
|
||||
|
||||
@@ -170,14 +190,14 @@ async function runWithApiKey(
|
||||
|
||||
async function runWithAkSk(
|
||||
config: Config,
|
||||
flags: GlobalFlags,
|
||||
flags: RetrieveFlags,
|
||||
indexId: string,
|
||||
query: string,
|
||||
format: OutputFormat,
|
||||
): Promise<void> {
|
||||
const accessKeyId = (flags.accessKeyId as string) || config.accessKeyId;
|
||||
const accessKeySecret = (flags.accessKeySecret as string) || config.accessKeySecret;
|
||||
const workspaceId = (flags.workspaceId as string) || config.workspaceId;
|
||||
const accessKeyId = flags.accessKeyId || config.accessKeyId;
|
||||
const accessKeySecret = flags.accessKeySecret || config.accessKeySecret;
|
||||
const workspaceId = flags.workspaceId || config.workspaceId;
|
||||
|
||||
if (!accessKeyId || !accessKeySecret) {
|
||||
throw new BailianError(
|
||||
@@ -211,18 +231,17 @@ async function runWithAkSk(
|
||||
}
|
||||
|
||||
if (flags.rerank) body.EnableReranking = true;
|
||||
if (flags.rerankTopN !== undefined) body.RerankTopN = flags.rerankTopN as number;
|
||||
if (flags.denseSimilarityTopK !== undefined)
|
||||
body.DenseSimilarityTopK = flags.denseSimilarityTopK as number;
|
||||
if (flags.rerankTopN !== undefined) body.RerankTopN = flags.rerankTopN;
|
||||
if (flags.denseSimilarityTopK !== undefined) body.DenseSimilarityTopK = flags.denseSimilarityTopK;
|
||||
if (flags.sparseSimilarityTopK !== undefined)
|
||||
body.SparseSimilarityTopK = flags.sparseSimilarityTopK as number;
|
||||
body.SparseSimilarityTopK = flags.sparseSimilarityTopK;
|
||||
|
||||
if (flags.rerankModel) {
|
||||
const rerank: { ModelName: string; RerankMode?: string; RerankInstruct?: string } = {
|
||||
ModelName: flags.rerankModel as string,
|
||||
ModelName: flags.rerankModel,
|
||||
};
|
||||
if (flags.rerankMode) rerank.RerankMode = flags.rerankMode as string;
|
||||
if (flags.rerankInstruct) rerank.RerankInstruct = flags.rerankInstruct as string;
|
||||
if (flags.rerankMode) rerank.RerankMode = flags.rerankMode;
|
||||
if (flags.rerankInstruct) rerank.RerankInstruct = flags.rerankInstruct;
|
||||
body.Rerank = [rerank];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
defineCommand,
|
||||
McpClient,
|
||||
bailianMcpUrl,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { defineCommand, McpClient, bailianMcpUrl, detectOutputFormat } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ensureApiKey } from "bailian-cli-runtime";
|
||||
|
||||
@@ -32,35 +25,42 @@ export default defineCommand({
|
||||
description: "Call a tool on an MCP server (tools/call)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--target <server.tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
|
||||
options: [
|
||||
{
|
||||
flag: "--target <server.tool>",
|
||||
flags: {
|
||||
target: {
|
||||
type: "string",
|
||||
valueHint: "<server.tool>",
|
||||
description:
|
||||
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--arg <kv>",
|
||||
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
|
||||
arg: {
|
||||
type: "array",
|
||||
valueHint: "<kv>",
|
||||
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
|
||||
},
|
||||
{
|
||||
flag: "--json <obj>",
|
||||
json: {
|
||||
type: "string",
|
||||
valueHint: "<obj>",
|
||||
description: "Full arguments object as JSON; merged with --arg (arg wins).",
|
||||
},
|
||||
{
|
||||
flag: "--query <text>",
|
||||
query: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
|
||||
},
|
||||
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
|
||||
],
|
||||
url: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Override the MCP endpoint URL (for non-Bailian servers)",
|
||||
},
|
||||
},
|
||||
exampleArgs: [
|
||||
'--target market-cmapi00073529.SmartStockSelection --query "Screen consumer stocks with ROE > 15%"',
|
||||
'--target market-cmapi00073529.FinQuery --json \'{"q":"Guizhou Maotai","limit":5}\'',
|
||||
"--target market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const target = flags.target as string;
|
||||
async run(config, flags) {
|
||||
const target = flags.target;
|
||||
|
||||
const dot = target.indexOf(".");
|
||||
if (dot <= 0 || dot === target.length - 1) {
|
||||
@@ -73,7 +73,7 @@ export default defineCommand({
|
||||
let toolArgs: Record<string, unknown> = {};
|
||||
if (flags.json) {
|
||||
try {
|
||||
const parsed = JSON.parse(flags.json as string);
|
||||
const parsed = JSON.parse(flags.json);
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
process.stderr.write("Error: --json must decode to an object.\n");
|
||||
process.exit(1);
|
||||
@@ -84,10 +84,10 @@ export default defineCommand({
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
|
||||
Object.assign(toolArgs, parseArgFlags(flags.arg ?? []));
|
||||
if (flags.query !== undefined) toolArgs.query = flags.query;
|
||||
|
||||
const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
|
||||
const url = flags.url || bailianMcpUrl(config.baseUrl, serverCode);
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
if (config.dryRun) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
detectOutputFormat,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -28,31 +26,33 @@ export default defineCommand({
|
||||
description: "List MCP servers activated under your Bailian account",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
options: [
|
||||
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
|
||||
{
|
||||
flag: "--type <type>",
|
||||
flags: {
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Filter by server name (substring match)",
|
||||
},
|
||||
type: {
|
||||
type: "string",
|
||||
valueHint: "<type>",
|
||||
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
|
||||
},
|
||||
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
|
||||
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
pageSize: { type: "number", valueHint: "<n>", description: "Results per page (default: 30)" },
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: ["", "--name finance", "--output json"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const serverName = (flags.name as string) || "";
|
||||
const type = (flags.type as string) || "OFFICIAL";
|
||||
const pageNo = (flags.page as number) || 1;
|
||||
const pageSize = (flags.pageSize as number) || 30;
|
||||
async run(config, flags) {
|
||||
const serverName = flags.name || "";
|
||||
const type = flags.type || "OFFICIAL";
|
||||
const pageNo = flags.page || 1;
|
||||
const pageSize = flags.pageSize || 30;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const data = {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
defineCommand,
|
||||
McpClient,
|
||||
bailianMcpUrl,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { defineCommand, McpClient, bailianMcpUrl, detectOutputFormat } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ensureApiKey } from "bailian-cli-runtime";
|
||||
|
||||
@@ -13,23 +6,28 @@ export default defineCommand({
|
||||
description: "List tools exposed by an MCP server (tools/list)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--server <code> [--url <url>]",
|
||||
options: [
|
||||
{
|
||||
flag: "--server <code>",
|
||||
flags: {
|
||||
server: {
|
||||
type: "string",
|
||||
valueHint: "<code>",
|
||||
description: "Server code from `mcp list` (e.g. market-cmapi00073529)",
|
||||
required: true,
|
||||
},
|
||||
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
|
||||
],
|
||||
url: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Override the MCP endpoint URL (for non-Bailian servers)",
|
||||
},
|
||||
},
|
||||
exampleArgs: [
|
||||
"--server market-cmapi00073529",
|
||||
"--server market-cmapi00073529 --output json",
|
||||
"--server my-server --url https://example.com/mcp",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const code = flags.server as string;
|
||||
async run(config, flags) {
|
||||
const code = flags.server;
|
||||
|
||||
const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code);
|
||||
const url = flags.url || bailianMcpUrl(config.baseUrl, code);
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
if (config.dryRun) {
|
||||
|
||||
@@ -3,41 +3,54 @@ import {
|
||||
requestJson,
|
||||
memoryAddEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
type MemoryAddRequest,
|
||||
type MemoryAddResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const ADD_FLAGS = {
|
||||
userId: { type: "string", valueHint: "<id>", description: "User ID (required)", required: true },
|
||||
messages: {
|
||||
type: "string",
|
||||
valueHint: "<json>",
|
||||
description: 'Messages JSON array: [{"role":"user","content":"..."},...]',
|
||||
},
|
||||
content: { type: "string", valueHint: "<text>", description: "Custom content text to memorize" },
|
||||
profileSchema: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Profile schema ID for user profiling",
|
||||
},
|
||||
memoryLibraryId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Memory library ID (isolate memory space)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type AddFlags = Flags<typeof ADD_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Add memory from messages or custom content",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--user-id <id> [--messages <json>] [--content <text>] [flags]",
|
||||
options: [
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
{
|
||||
flag: "--messages <json>",
|
||||
description: 'Messages JSON array: [{"role":"user","content":"..."},...]',
|
||||
},
|
||||
{ flag: "--content <text>", description: "Custom content text to memorize" },
|
||||
{ flag: "--profile-schema <id>", description: "Profile schema ID for user profiling" },
|
||||
{ flag: "--memory-library-id <id>", description: "Memory library ID (isolate memory space)" },
|
||||
],
|
||||
flags: ADD_FLAGS,
|
||||
exampleArgs: [
|
||||
'--user-id user1 --content "The user likes Python programming"',
|
||||
'--user-id user1 --messages \'[{"role":"user","content":"I like traveling"}]\'',
|
||||
'--user-id user1 --content "Lives in Beijing" --profile-schema schema_xxx',
|
||||
],
|
||||
validate: (f) => (!f.messages && !f.content ? "Provide --messages or --content." : undefined),
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const userId = flags.userId as string;
|
||||
validate: (f: AddFlags) =>
|
||||
!f.messages && !f.content ? "Provide --messages or --content." : undefined,
|
||||
async run(config, flags) {
|
||||
const userId = flags.userId;
|
||||
|
||||
const body: MemoryAddRequest = { user_id: userId };
|
||||
|
||||
if (flags.messages) {
|
||||
try {
|
||||
body.messages = JSON.parse(flags.messages as string);
|
||||
body.messages = JSON.parse(flags.messages);
|
||||
} catch {
|
||||
process.stderr.write("Error: --messages must be valid JSON array\n");
|
||||
process.exit(1);
|
||||
@@ -45,11 +58,11 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (flags.content) {
|
||||
body.custom_content = flags.content as string;
|
||||
body.custom_content = flags.content;
|
||||
}
|
||||
|
||||
if (flags.profileSchema) body.profile_schema = flags.profileSchema as string;
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId as string;
|
||||
if (flags.profileSchema) body.profile_schema = flags.profileSchema;
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
memoryNodeEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
@@ -12,19 +10,33 @@ export default defineCommand({
|
||||
description: "Delete a memory node",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--node-id <id> --user-id <id>",
|
||||
options: [
|
||||
{ flag: "--node-id <id>", description: "Memory node ID (required)", required: true },
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
{ flag: "--memory-library-id <id>", description: "Memory library ID (non-default library)" },
|
||||
],
|
||||
flags: {
|
||||
nodeId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Memory node ID (required)",
|
||||
required: true,
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "User ID (required)",
|
||||
required: true,
|
||||
},
|
||||
memoryLibraryId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Memory library ID (non-default library)",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--node-id node_xxx --user-id user1"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const nodeId = flags.nodeId as string;
|
||||
const userId = flags.userId as string;
|
||||
async run(config, flags) {
|
||||
const nodeId = flags.nodeId;
|
||||
const userId = flags.userId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
const params = new URLSearchParams({ user_id: userId });
|
||||
if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId as string);
|
||||
if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId);
|
||||
const url = `${memoryNodeEndpoint(config.baseUrl, nodeId)}?${params.toString()}`;
|
||||
|
||||
if (config.dryRun) {
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
memoryListEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type MemoryNodeListResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
@@ -13,22 +11,31 @@ export default defineCommand({
|
||||
description: "List memory nodes for a user",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--user-id <id> [flags]",
|
||||
options: [
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
{ flag: "--page-size <n>", description: "Results per page (default: 10)", type: "number" },
|
||||
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
|
||||
{ flag: "--memory-library-id <id>", description: "Memory library ID" },
|
||||
],
|
||||
flags: {
|
||||
userId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "User ID (required)",
|
||||
required: true,
|
||||
},
|
||||
pageSize: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Results per page (default: 10)",
|
||||
},
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
memoryLibraryId: { type: "string", valueHint: "<id>", description: "Memory library ID" },
|
||||
},
|
||||
exampleArgs: ["--user-id user1", "--user-id user1 --page-size 20 --page 2"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const userId = flags.userId as string;
|
||||
async run(config, flags) {
|
||||
const userId = flags.userId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
const params = new URLSearchParams();
|
||||
params.set("user_id", userId);
|
||||
if (flags.pageSize !== undefined) params.set("page_size", String(flags.pageSize as number));
|
||||
if (flags.page !== undefined) params.set("page_num", String(flags.page as number));
|
||||
if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId as string);
|
||||
if (flags.pageSize !== undefined) params.set("page_size", String(flags.pageSize));
|
||||
if (flags.page !== undefined) params.set("page_num", String(flags.page));
|
||||
if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId);
|
||||
|
||||
const url = `${memoryListEndpoint(config.baseUrl)}?${params.toString()}`;
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
profileSchemaEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type ProfileSchemaCreateRequest,
|
||||
type ProfileSchemaCreateResponse,
|
||||
} from "bailian-cli-core";
|
||||
@@ -14,21 +12,27 @@ export default defineCommand({
|
||||
description: "Create a user profile schema for memory profiling",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--name <name> --attributes <json> [flags]",
|
||||
options: [
|
||||
{ flag: "--name <name>", description: "Schema name (required)", required: true },
|
||||
{ flag: "--description <text>", description: "Schema description" },
|
||||
{
|
||||
flag: "--attributes <json>",
|
||||
flags: {
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<name>",
|
||||
description: "Schema name (required)",
|
||||
required: true,
|
||||
},
|
||||
description: { type: "string", valueHint: "<text>", description: "Schema description" },
|
||||
attributes: {
|
||||
type: "string",
|
||||
valueHint: "<json>",
|
||||
description: 'Attributes JSON array: [{"name":"age","description":"age"}]',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
'--name "user_basic" --attributes \'[{"name":"age","description":"age"},{"name":"hobby","description":"hobby"}]\'',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const name = flags.name as string;
|
||||
const attrStr = flags.attributes as string;
|
||||
async run(config, flags) {
|
||||
const name = flags.name;
|
||||
const attrStr = flags.attributes;
|
||||
|
||||
let attributes;
|
||||
try {
|
||||
@@ -39,7 +43,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
const body: ProfileSchemaCreateRequest = { name, attributes };
|
||||
if (flags.description) body.description = flags.description as string;
|
||||
if (flags.description) body.description = flags.description;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
userProfileEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type UserProfileResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
@@ -13,14 +11,24 @@ export default defineCommand({
|
||||
description: "Get user profile by schema ID and user ID",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--schema-id <id> --user-id <id>",
|
||||
options: [
|
||||
{ flag: "--schema-id <id>", description: "Profile schema ID (required)", required: true },
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
],
|
||||
flags: {
|
||||
schemaId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Profile schema ID (required)",
|
||||
required: true,
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "User ID (required)",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--schema-id schema_xxx --user-id user1"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const schemaId = flags.schemaId as string;
|
||||
const userId = flags.userId as string;
|
||||
async run(config, flags) {
|
||||
const schemaId = flags.schemaId;
|
||||
const userId = flags.userId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
const params = new URLSearchParams({ user_id: userId });
|
||||
|
||||
@@ -3,43 +3,51 @@ import {
|
||||
requestJson,
|
||||
memorySearchEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
type MemorySearchRequest,
|
||||
type MemorySearchResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const SEARCH_FLAGS = {
|
||||
userId: { type: "string", valueHint: "<id>", description: "User ID (required)", required: true },
|
||||
query: { type: "string", valueHint: "<text>", description: "Search query text" },
|
||||
messages: {
|
||||
type: "string",
|
||||
valueHint: "<json>",
|
||||
description: "Messages JSON array for context-based search",
|
||||
},
|
||||
topK: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Number of results to return (default: 10)",
|
||||
},
|
||||
memoryLibraryId: { type: "string", valueHint: "<id>", description: "Memory library ID" },
|
||||
} satisfies FlagsDef;
|
||||
type SearchFlags = Flags<typeof SEARCH_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Search memory nodes by query or messages",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--user-id <id> [--query <text>] [flags]",
|
||||
options: [
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
{ flag: "--query <text>", description: "Search query text" },
|
||||
{ flag: "--messages <json>", description: "Messages JSON array for context-based search" },
|
||||
{
|
||||
flag: "--top-k <n>",
|
||||
description: "Number of results to return (default: 10)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--memory-library-id <id>", description: "Memory library ID" },
|
||||
],
|
||||
flags: SEARCH_FLAGS,
|
||||
exampleArgs: [
|
||||
'--user-id user1 --query "programming preferences"',
|
||||
'--user-id user1 --messages \'[{"role":"user","content":"recommend a book"}]\' --top-k 5',
|
||||
],
|
||||
validate: (f) => (!f.query && !f.messages ? "Provide --query or --messages." : undefined),
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const userId = flags.userId as string;
|
||||
validate: (f: SearchFlags) =>
|
||||
!f.query && !f.messages ? "Provide --query or --messages." : undefined,
|
||||
async run(config, flags) {
|
||||
const userId = flags.userId;
|
||||
|
||||
const body: MemorySearchRequest = { user_id: userId };
|
||||
|
||||
if (flags.query) body.query = flags.query as string;
|
||||
if (flags.query) body.query = flags.query;
|
||||
|
||||
if (flags.messages) {
|
||||
try {
|
||||
body.messages = JSON.parse(flags.messages as string);
|
||||
body.messages = JSON.parse(flags.messages);
|
||||
} catch {
|
||||
process.stderr.write("Error: --messages must be valid JSON array\n");
|
||||
process.exit(1);
|
||||
@@ -51,8 +59,8 @@ export default defineCommand({
|
||||
body.messages = [{ role: "user", content: body.query }];
|
||||
}
|
||||
|
||||
if (flags.topK !== undefined) body.top_k = flags.topK as number;
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId as string;
|
||||
if (flags.topK !== undefined) body.top_k = flags.topK;
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
memoryNodeEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type MemoryNodeUpdateRequest,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
@@ -13,27 +11,42 @@ export default defineCommand({
|
||||
description: "Update a memory node content",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--node-id <id> --user-id <id> --content <text>",
|
||||
options: [
|
||||
{ flag: "--node-id <id>", description: "Memory node ID (required)", required: true },
|
||||
{ flag: "--user-id <id>", description: "User ID (required)", required: true },
|
||||
{
|
||||
flag: "--content <text>",
|
||||
flags: {
|
||||
nodeId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Memory node ID (required)",
|
||||
required: true,
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "User ID (required)",
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "New content for the memory node (required)",
|
||||
required: true,
|
||||
},
|
||||
{ flag: "--memory-library-id <id>", description: "Memory library ID (non-default library)" },
|
||||
],
|
||||
memoryLibraryId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Memory library ID (non-default library)",
|
||||
},
|
||||
},
|
||||
exampleArgs: ['--node-id node_xxx --user-id user1 --content "updated memory content"'],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const nodeId = flags.nodeId as string;
|
||||
const userId = flags.userId as string;
|
||||
const content = flags.content as string;
|
||||
async run(config, flags) {
|
||||
const nodeId = flags.nodeId;
|
||||
const userId = flags.userId;
|
||||
const content = flags.content;
|
||||
|
||||
const body: MemoryNodeUpdateRequest = {
|
||||
user_id: userId,
|
||||
custom_content: content,
|
||||
};
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId as string;
|
||||
if (flags.memoryLibraryId) body.memory_library_id = flags.memoryLibraryId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
detectOutputFormat,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type ChatMessage,
|
||||
type ChatMessageContent,
|
||||
type ChatRequest,
|
||||
@@ -86,36 +84,57 @@ export default defineCommand({
|
||||
description: "Multimodal chat with text + audio output (Qwen-Omni)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--message <text> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--message <text>",
|
||||
flags: {
|
||||
message: {
|
||||
type: "array",
|
||||
valueHint: "<text>",
|
||||
description: "Message text (repeatable, prefix role: to set role)",
|
||||
required: true,
|
||||
type: "array",
|
||||
},
|
||||
{ flag: "--model <model>", description: "Model ID (default: qwen3.5-omni-plus)" },
|
||||
{ flag: "--system <text>", description: "System prompt" },
|
||||
{ flag: "--image <url>", description: "Image URL or local file (repeatable)", type: "array" },
|
||||
{
|
||||
flag: "--audio <url>",
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: qwen3.5-omni-plus)",
|
||||
},
|
||||
system: { type: "string", valueHint: "<text>", description: "System prompt" },
|
||||
image: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Image URL or local file (repeatable)",
|
||||
},
|
||||
audio: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp)",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--video <url>",
|
||||
video: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Video file URL / local path, or comma-separated frame URLs",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--voice <voice>",
|
||||
voice: {
|
||||
type: "string",
|
||||
valueHint: "<voice>",
|
||||
description: `Output voice (default: Cherry). Options: ${OMNI_VOICES.join(", ")}`,
|
||||
},
|
||||
{ flag: "--audio-format <fmt>", description: "Audio output format (default: wav)" },
|
||||
{ flag: "--audio-out <path>", description: "Save audio to file (default: auto-generate)" },
|
||||
{ flag: "--text-only", description: "Output text only, no audio generation" },
|
||||
{ flag: "--max-tokens <n>", description: "Maximum tokens to generate", type: "number" },
|
||||
{ flag: "--temperature <n>", description: "Sampling temperature (0.0, 2.0]", type: "number" },
|
||||
],
|
||||
audioFormat: {
|
||||
type: "string",
|
||||
valueHint: "<fmt>",
|
||||
description: "Audio output format (default: wav)",
|
||||
},
|
||||
audioOut: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save audio to file (default: auto-generate)",
|
||||
},
|
||||
textOnly: { type: "switch", description: "Output text only, no audio generation" },
|
||||
maxTokens: { type: "number", valueHint: "<n>", description: "Maximum tokens to generate" },
|
||||
temperature: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Sampling temperature (0.0, 2.0]",
|
||||
},
|
||||
},
|
||||
exampleArgs: [
|
||||
'--message "Hello, who are you?"',
|
||||
'--message "Describe this image" --image ./photo.jpg',
|
||||
@@ -126,20 +145,20 @@ export default defineCommand({
|
||||
'--message "Hello" --text-only --output json',
|
||||
'--message "Read this passage aloud" --audio-out greeting.wav',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
// --- Parse messages ---
|
||||
const userMessages = flags.message as string[];
|
||||
const userMessages = flags.message;
|
||||
|
||||
const model = (flags.model as string) || config.defaultOmniModel || "qwen3.5-omni-plus";
|
||||
const voice = (flags.voice as string) || "Cherry";
|
||||
const audioFormat = (flags.audioFormat as string) || "wav";
|
||||
const model = flags.model || config.defaultOmniModel || "qwen3.5-omni-plus";
|
||||
const voice = flags.voice || "Cherry";
|
||||
const audioFormat = flags.audioFormat || "wav";
|
||||
const textOnly = flags.textOnly === true;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
// --- Build messages array ---
|
||||
const allMessages: ChatMessage[] = [];
|
||||
if (flags.system) {
|
||||
allMessages.push({ role: "system", content: flags.system as string });
|
||||
allMessages.push({ role: "system", content: flags.system });
|
||||
}
|
||||
|
||||
// Build multimodal content for user messages
|
||||
@@ -161,9 +180,9 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
// Attach multimodal inputs to the last user message
|
||||
const rawImageUrls = (flags.image as string[] | undefined) || [];
|
||||
const rawAudioUrls = (flags.audio as string[] | undefined) || [];
|
||||
const rawVideoUrls = (flags.video as string[] | undefined) || [];
|
||||
const rawImageUrls = flags.image || [];
|
||||
const rawAudioUrls = flags.audio || [];
|
||||
const rawVideoUrls = flags.video || [];
|
||||
|
||||
// Auto-upload local files
|
||||
const imageUrls: string[] = [];
|
||||
@@ -258,8 +277,8 @@ export default defineCommand({
|
||||
body.audio = { voice, format: audioFormat };
|
||||
}
|
||||
|
||||
if (flags.maxTokens !== undefined) body.max_tokens = flags.maxTokens as number;
|
||||
if (flags.temperature !== undefined) body.temperature = flags.temperature as number;
|
||||
if (flags.maxTokens !== undefined) body.max_tokens = flags.maxTokens;
|
||||
if (flags.temperature !== undefined) body.temperature = flags.temperature;
|
||||
|
||||
if (config.dryRun) {
|
||||
emitResult({ request: body }, format);
|
||||
@@ -322,7 +341,7 @@ export default defineCommand({
|
||||
const wavHeader = buildWavHeader(pcmBuffer.length);
|
||||
const wavBuffer = Buffer.concat([wavHeader, pcmBuffer]);
|
||||
|
||||
let destPath = flags.audioOut as string | undefined;
|
||||
let destPath = flags.audioOut;
|
||||
if (!destPath) {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { join } = await import("path");
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { defineCommand, type Config, type GlobalFlags } from "bailian-cli-core";
|
||||
import { defineCommand, type FlagsDef, type Flags } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { initPipelineSteps } from "bailian-cli-runtime";
|
||||
import { executePipeline, streamPipelineEvents } from "bailian-cli-runtime";
|
||||
import type { PipelineLifecycleEvent } from "bailian-cli-runtime";
|
||||
import { loadPipelineFile } from "./load-file.ts";
|
||||
|
||||
const RUN_FLAGS = {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Pipeline definition file (YAML/JSON)",
|
||||
required: true,
|
||||
},
|
||||
input: { type: "string", valueHint: "<json>", description: "Runtime input as inline JSON" },
|
||||
inputFile: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Runtime input from a JSON file",
|
||||
},
|
||||
concurrency: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Max parallel steps (default: 1)",
|
||||
},
|
||||
events: { type: "string", valueHint: "<format>", description: "Emit lifecycle events: jsonl" },
|
||||
timeout: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Default step timeout in seconds",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type RunFlags = Flags<typeof RUN_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Run a pipeline workflow definition",
|
||||
auth: "none",
|
||||
usageArgs: "--file <path> [flags]",
|
||||
options: [
|
||||
{ flag: "--file <path>", description: "Pipeline definition file (YAML/JSON)", required: true },
|
||||
{ flag: "--input <json>", description: "Runtime input as inline JSON" },
|
||||
{ flag: "--input-file <path>", description: "Runtime input from a JSON file" },
|
||||
{
|
||||
flag: "--concurrency <n>",
|
||||
description: "Max parallel steps (default: 1)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--events <format>", description: "Emit lifecycle events: jsonl" },
|
||||
{
|
||||
flag: "--timeout <seconds>",
|
||||
description: "Default step timeout in seconds",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
flags: RUN_FLAGS,
|
||||
exampleArgs: [
|
||||
'--file workflow.yaml --input \'{"brief":"hello"}\'',
|
||||
"--file workflow.json --input-file inputs.json --concurrency 3",
|
||||
@@ -34,12 +46,12 @@ export default defineCommand({
|
||||
"--file workflow.json --events jsonl",
|
||||
"--file workflow.yaml --output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const file = flags.file as string;
|
||||
async run(config, flags) {
|
||||
const file = flags.file;
|
||||
|
||||
initPipelineSteps();
|
||||
|
||||
const eventsFormat = flags.events as string | undefined;
|
||||
const eventsFormat = flags.events;
|
||||
if (eventsFormat !== undefined && eventsFormat !== "jsonl") {
|
||||
process.stderr.write(
|
||||
`Error: unsupported --events format: ${eventsFormat}. Supported: jsonl\n`,
|
||||
@@ -54,10 +66,10 @@ export default defineCommand({
|
||||
|
||||
if (eventsFormat === "jsonl") {
|
||||
for await (const event of streamPipelineEvents(pipeline, runtimeInput, {
|
||||
concurrency: flags.concurrency as number | undefined,
|
||||
concurrency: flags.concurrency,
|
||||
basePath,
|
||||
dryRun: flags.dryRun,
|
||||
timeoutSeconds: flags.timeout as number | undefined,
|
||||
timeoutSeconds: flags.timeout,
|
||||
})) {
|
||||
process.stdout.write(JSON.stringify(event) + "\n");
|
||||
}
|
||||
@@ -65,10 +77,10 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
const report = await executePipeline(pipeline, runtimeInput, {
|
||||
concurrency: flags.concurrency as number | undefined,
|
||||
concurrency: flags.concurrency,
|
||||
basePath,
|
||||
dryRun: flags.dryRun,
|
||||
timeoutSeconds: flags.timeout as number | undefined,
|
||||
timeoutSeconds: flags.timeout,
|
||||
onEvent: flags.verbose ? logEvent : undefined,
|
||||
});
|
||||
|
||||
@@ -82,9 +94,9 @@ export default defineCommand({
|
||||
},
|
||||
});
|
||||
|
||||
async function resolveRuntimeInput(flags: GlobalFlags): Promise<Record<string, unknown>> {
|
||||
const inputJson = flags.input as string | undefined;
|
||||
const inputFile = flags.inputFile as string | undefined;
|
||||
async function resolveRuntimeInput(flags: RunFlags): Promise<Record<string, unknown>> {
|
||||
const inputJson = flags.input;
|
||||
const inputFile = flags.inputFile;
|
||||
if (inputJson && inputFile) {
|
||||
process.stderr.write("Error: use --input or --input-file, not both\n");
|
||||
process.exit(2);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { resolve } from "node:path";
|
||||
import { defineCommand, type Config, type GlobalFlags } from "bailian-cli-core";
|
||||
import { defineCommand } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { initPipelineSteps } from "bailian-cli-runtime";
|
||||
import { collectPipelineIssues, collectPipelineHints } from "bailian-cli-runtime";
|
||||
@@ -9,12 +9,17 @@ export default defineCommand({
|
||||
description: "Validate a pipeline definition without executing",
|
||||
auth: "none",
|
||||
usageArgs: "--file <path>",
|
||||
options: [
|
||||
{ flag: "--file <path>", description: "Pipeline definition file (YAML/JSON)", required: true },
|
||||
],
|
||||
flags: {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Pipeline definition file (YAML/JSON)",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--file workflow.yaml", "--file workflow.json --output json"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const file = flags.file as string;
|
||||
async run(config, flags) {
|
||||
const file = flags.file;
|
||||
|
||||
initPipelineSteps();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -238,26 +237,25 @@ export default defineCommand({
|
||||
description: "Check current usage against rate limits",
|
||||
auth: "console",
|
||||
usageArgs: "[--model <model>] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name(s), comma-separated",
|
||||
},
|
||||
{
|
||||
flag: "--period <minutes>",
|
||||
period: {
|
||||
type: "string",
|
||||
valueHint: "<minutes>",
|
||||
description: "Query usage for the last N minutes (default: 2)",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"",
|
||||
"--model qwen3.6-plus",
|
||||
@@ -265,8 +263,8 @@ export default defineCommand({
|
||||
"--model qwen3.6-plus,qwen-turbo",
|
||||
"--output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelFlag = (flags.model as string) || undefined;
|
||||
async run(config, flags) {
|
||||
const modelFlag = flags.model || undefined;
|
||||
const rawPeriod = Number(flags.period) || 2;
|
||||
if (rawPeriod < 1) {
|
||||
process.stderr.write("Error: --period must be at least 1 minute.\n");
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
BailianError,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -94,35 +92,35 @@ export default defineCommand({
|
||||
description: "View quota change history",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--page <n>",
|
||||
flags: {
|
||||
page: {
|
||||
type: "string",
|
||||
valueHint: "<n>",
|
||||
description: "Page number (default: 1)",
|
||||
},
|
||||
{
|
||||
flag: "--page-size <n>",
|
||||
pageSize: {
|
||||
type: "string",
|
||||
valueHint: "<n>",
|
||||
description: "Page size (default: 10)",
|
||||
},
|
||||
{
|
||||
flag: "--model <model>",
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Filter by model name",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: ["", "--page 2", "--page-size 20", "--model qwen-turbo", "--output json"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
const page = Number(flags.page) || 1;
|
||||
const pageSize = Number(flags.pageSize) || 10;
|
||||
const modelFilter = (flags.model as string) || undefined;
|
||||
const modelFilter = flags.model || undefined;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const requestData = {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -153,26 +152,24 @@ export default defineCommand({
|
||||
description: "View model RPM/TPM rate limits",
|
||||
auth: "console",
|
||||
usageArgs: "[--model <model>] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name(s), comma-separated",
|
||||
},
|
||||
{
|
||||
flag: "--all",
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Show all models, not just self-service ones",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"",
|
||||
"--model qwen3.6-plus",
|
||||
@@ -180,8 +177,8 @@ export default defineCommand({
|
||||
"--all",
|
||||
"--output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelFlag = (flags.model as string) || undefined;
|
||||
async run(config, flags) {
|
||||
const modelFlag = flags.model || undefined;
|
||||
const showAll = Boolean(flags.all);
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
detectOutputFormat,
|
||||
BailianError,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -81,39 +80,35 @@ export default defineCommand({
|
||||
description: "Request a temporary quota increase",
|
||||
auth: "console",
|
||||
usageArgs: "--model <model> --tpm <value> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name (required)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--tpm <value>",
|
||||
tpm: {
|
||||
type: "string",
|
||||
valueHint: "<value>",
|
||||
description: "Target TPM value (required)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--yes",
|
||||
description: "Skip downgrade confirmation",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
yes: { type: "switch", description: "Skip downgrade confirmation" },
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"--model qwen-turbo --tpm 100000",
|
||||
"--model qwen3.6-plus --tpm 8000000 --yes",
|
||||
"--model qwen-turbo --tpm 100000 --output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelName = flags.model as string;
|
||||
async run(config, flags) {
|
||||
const modelName = flags.model;
|
||||
if (!modelName) {
|
||||
process.stderr.write("Error: --model is required.\n");
|
||||
process.exit(1);
|
||||
|
||||
@@ -2,22 +2,27 @@ import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
mcpWebSearchEndpoint,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
McpClient,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { createSpinner } from "bailian-cli-runtime";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const WEB_SEARCH_FLAGS = {
|
||||
query: { type: "string", valueHint: "<text>", description: "Search query text" },
|
||||
count: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Number of search results (default: 10)",
|
||||
},
|
||||
listTools: { type: "switch", description: "List available MCP tools and exit" },
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Search the web using DashScope MCP WebSearch service",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--query <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--query <text>", description: "Search query text" },
|
||||
{ flag: "--count <n>", description: "Number of search results (default: 10)", type: "number" },
|
||||
{ flag: "--list-tools", description: "List available MCP tools and exit" },
|
||||
],
|
||||
flags: WEB_SEARCH_FLAGS,
|
||||
exampleArgs: [
|
||||
'--query "Alibaba Cloud Bailian latest features"',
|
||||
'--query "TypeScript 5.9 new features" --count 5',
|
||||
@@ -25,7 +30,7 @@ export default defineCommand({
|
||||
"--list-tools",
|
||||
],
|
||||
validate: (f) => (!f.listTools && !f.query ? "Missing required flag: --query" : undefined),
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
const mcpUrl = mcpWebSearchEndpoint(config.baseUrl);
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
@@ -45,7 +50,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
// --- Search mode ---
|
||||
const query = flags.query as string;
|
||||
const query = flags.query;
|
||||
|
||||
if (config.dryRun) {
|
||||
emitResult(
|
||||
@@ -55,7 +60,7 @@ export default defineCommand({
|
||||
tool: "bailian_web_search",
|
||||
arguments: {
|
||||
query: query!,
|
||||
count: (flags.count as number) || undefined,
|
||||
count: flags.count || undefined,
|
||||
},
|
||||
},
|
||||
format,
|
||||
@@ -76,7 +81,7 @@ export default defineCommand({
|
||||
|
||||
// Build tool arguments
|
||||
const toolArgs: Record<string, unknown> = { query: query! };
|
||||
if (flags.count) toolArgs.count = flags.count as number;
|
||||
if (flags.count) toolArgs.count = flags.count;
|
||||
|
||||
// Call the search tool
|
||||
const result = await client.callTool("bailian_web_search", toolArgs);
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ExitCode,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeASRRequest,
|
||||
type DashScopeASRTaskResult,
|
||||
type DashScopeAsyncResponse,
|
||||
@@ -17,39 +16,52 @@ import {
|
||||
requestJson,
|
||||
type OutputFormat,
|
||||
speechRecognizeEndpoint,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
} from "bailian-cli-core";
|
||||
import { poll } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const RECOGNIZE_FLAGS = {
|
||||
url: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Audio file URL or local file path (repeatable, max 100)",
|
||||
required: true,
|
||||
},
|
||||
model: { type: "string", valueHint: "<model>", description: "Model ID (default: fun-asr)" },
|
||||
language: { type: "string", valueHint: "<lang>", description: "Language hint (e.g. zh, en, ja)" },
|
||||
diarization: { type: "switch", description: "Enable automatic speaker diarization" },
|
||||
speakerCount: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Expected number of speakers (requires --diarization)",
|
||||
},
|
||||
vocabularyId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Hot-word vocabulary ID for improved accuracy",
|
||||
},
|
||||
channelId: { type: "number", valueHint: "<n>", description: "Audio channel ID (default: 0)" },
|
||||
out: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save full transcription result to JSON file",
|
||||
},
|
||||
noWait: { type: "switch", description: "Return task ID immediately without polling" },
|
||||
pollInterval: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Polling interval in seconds (default: 2)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type RecognizeFlags = Flags<typeof RECOGNIZE_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Recognize speech from audio files (FunAudio-ASR)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--url <audio-url> [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--url <url>",
|
||||
description: "Audio file URL or local file path (repeatable, max 100)",
|
||||
required: true,
|
||||
type: "array",
|
||||
},
|
||||
{ flag: "--model <model>", description: "Model ID (default: fun-asr)" },
|
||||
{ flag: "--language <lang>", description: "Language hint (e.g. zh, en, ja)" },
|
||||
{ flag: "--diarization", description: "Enable automatic speaker diarization" },
|
||||
{
|
||||
flag: "--speaker-count <n>",
|
||||
description: "Expected number of speakers (requires --diarization)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--vocabulary-id <id>", description: "Hot-word vocabulary ID for improved accuracy" },
|
||||
{ flag: "--channel-id <n>", description: "Audio channel ID (default: 0)", type: "number" },
|
||||
{ flag: "--out <path>", description: "Save full transcription result to JSON file" },
|
||||
{ flag: "--no-wait", description: "Return task ID immediately without polling" },
|
||||
{
|
||||
flag: "--poll-interval <seconds>",
|
||||
description: "Polling interval in seconds (default: 2)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
flags: RECOGNIZE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--url https://example.com/audio.mp3",
|
||||
"--url https://example.com/a.mp3 --url https://example.com/b.mp3",
|
||||
@@ -59,17 +71,17 @@ export default defineCommand({
|
||||
"--url https://example.com/audio.mp3 --out result.json",
|
||||
"--url https://example.com/audio.mp3 --no-wait --quiet",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
// Normalize --url to string[] (supports both single and repeated flags)
|
||||
let rawUrls: string[] = [];
|
||||
if (Array.isArray(flags.url)) {
|
||||
rawUrls = flags.url as string[];
|
||||
rawUrls = flags.url;
|
||||
} else if (typeof flags.url === "string") {
|
||||
rawUrls = [flags.url];
|
||||
}
|
||||
|
||||
// Strict validation: --speaker-count requires --diarization
|
||||
const speakerCount = flags.speakerCount as number | undefined;
|
||||
const speakerCount = flags.speakerCount;
|
||||
const diarization = flags.diarization === true;
|
||||
if (speakerCount !== undefined && !diarization) {
|
||||
throw new BailianError(
|
||||
@@ -78,7 +90,7 @@ export default defineCommand({
|
||||
);
|
||||
}
|
||||
|
||||
const model = (flags.model as string) || "fun-asr";
|
||||
const model = flags.model || "fun-asr";
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
// Auto-upload local files in parallel
|
||||
@@ -86,9 +98,9 @@ export default defineCommand({
|
||||
const resolvedUrls = await Promise.all(
|
||||
rawUrls.map((u) => resolveFileUrl(u, credential.token, model)),
|
||||
);
|
||||
const channelId = flags.channelId as number | undefined;
|
||||
const language = flags.language as string | undefined;
|
||||
const vocabularyId = flags.vocabularyId as string | undefined;
|
||||
const channelId = flags.channelId;
|
||||
const language = flags.language;
|
||||
const vocabularyId = flags.vocabularyId;
|
||||
|
||||
const body: DashScopeASRRequest = {
|
||||
model,
|
||||
@@ -125,7 +137,7 @@ async function handleAsyncMode(
|
||||
config: Config,
|
||||
url: string,
|
||||
body: DashScopeASRRequest,
|
||||
flags: GlobalFlags,
|
||||
flags: RecognizeFlags,
|
||||
format: OutputFormat,
|
||||
fileCount: number,
|
||||
): Promise<void> {
|
||||
@@ -146,7 +158,7 @@ async function handleAsyncMode(
|
||||
}
|
||||
|
||||
// Poll until completion
|
||||
const pollInterval = (flags.pollInterval as number) ?? 2;
|
||||
const pollInterval = flags.pollInterval ?? 2;
|
||||
const pollUrl = taskEndpoint(config.baseUrl, taskId);
|
||||
|
||||
const result = await poll<DashScopeASRTaskResult>(config, {
|
||||
@@ -234,7 +246,7 @@ async function handleAsyncMode(
|
||||
|
||||
// Save to --out file
|
||||
if (flags.out) {
|
||||
const outPath = flags.out as string;
|
||||
const outPath = flags.out;
|
||||
const outData = allTransData.length === 1 ? allTransData[0] : allTransData;
|
||||
writeFileSync(outPath, JSON.stringify(outData, null, 2) + "\n");
|
||||
if (!config.quiet) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ExitCode,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeTTSRequest,
|
||||
type DashScopeTTSResponse,
|
||||
type DashScopeTTSStreamChunk,
|
||||
@@ -17,6 +16,8 @@ import {
|
||||
resolveOutputDir,
|
||||
request,
|
||||
DOCS_HOSTS,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
} from "bailian-cli-core";
|
||||
|
||||
const COSYVOICE_CLONE_DESIGN_DOC = `${DOCS_HOSTS.cn}/cosyvoice-clone-design-api`;
|
||||
@@ -139,46 +140,81 @@ function printVoiceList(model: string): void {
|
||||
process.stdout.write(`\nTotal: ${voices.length} voices\n`);
|
||||
}
|
||||
|
||||
const SYNTHESIZE_FLAGS = {
|
||||
text: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Text to synthesize into speech (or use --text-file)",
|
||||
},
|
||||
textFile: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Read text from a file instead of --text",
|
||||
},
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description:
|
||||
"Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash",
|
||||
},
|
||||
voice: {
|
||||
type: "string",
|
||||
valueHint: "<voice>",
|
||||
description:
|
||||
"Voice ID. Use --list-voices to see system voices for cosyvoice-v3-flash; for v3.5-flash provide a clone/design voice ID",
|
||||
},
|
||||
listVoices: {
|
||||
type: "switch",
|
||||
description: "List available system voices for the selected model and exit",
|
||||
},
|
||||
format: {
|
||||
type: "string",
|
||||
valueHint: "<format>",
|
||||
description: "Audio format: mp3, pcm, wav, opus (default: mp3)",
|
||||
choices: ["mp3", "pcm", "wav", "opus"] as const,
|
||||
},
|
||||
sampleRate: {
|
||||
type: "string",
|
||||
valueHint: "<rate>",
|
||||
description: "Audio sample rate in Hz (e.g. 24000)",
|
||||
},
|
||||
volume: { type: "string", valueHint: "<volume>", description: "Volume 0-100 (default: 50)" },
|
||||
rate: { type: "string", valueHint: "<rate>", description: "Speech rate 0.5-2.0 (default: 1.0)" },
|
||||
pitch: {
|
||||
type: "string",
|
||||
valueHint: "<pitch>",
|
||||
description: "Pitch multiplier 0.5-2.0 (default: 1.0)",
|
||||
},
|
||||
seed: {
|
||||
type: "string",
|
||||
valueHint: "<seed>",
|
||||
description: "Random seed 0-65535 for reproducible synthesis",
|
||||
},
|
||||
language: {
|
||||
type: "string",
|
||||
valueHint: "<lang>",
|
||||
description: "Language hint (e.g. zh, en, ja, ko, fr, de)",
|
||||
},
|
||||
instruction: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: 'Natural language instruction to control speech style (e.g. "Use a gentle tone")',
|
||||
},
|
||||
enableSsml: { type: "switch", description: "Enable SSML markup parsing in input text" },
|
||||
out: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save audio to file (default: auto-generate in temp dir)",
|
||||
},
|
||||
stream: { type: "switch", description: "Stream raw PCM audio to stdout (pipe to player)" },
|
||||
} satisfies FlagsDef;
|
||||
type SynthesizeFlags = Flags<typeof SYNTHESIZE_FLAGS>;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Synthesize speech from text (CosyVoice TTS)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--text <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--text <text>", description: "Text to synthesize into speech (or use --text-file)" },
|
||||
{ flag: "--text-file <path>", description: "Read text from a file instead of --text" },
|
||||
{
|
||||
flag: "--model <model>",
|
||||
description:
|
||||
"Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash",
|
||||
},
|
||||
{
|
||||
flag: "--voice <voice>",
|
||||
description:
|
||||
"Voice ID. Use --list-voices to see system voices for cosyvoice-v3-flash; for v3.5-flash provide a clone/design voice ID",
|
||||
},
|
||||
{
|
||||
flag: "--list-voices",
|
||||
description: "List available system voices for the selected model and exit",
|
||||
},
|
||||
{ flag: "--format <format>", description: "Audio format: mp3, pcm, wav, opus (default: mp3)" },
|
||||
{ flag: "--sample-rate <rate>", description: "Audio sample rate in Hz (e.g. 24000)" },
|
||||
{ flag: "--volume <volume>", description: "Volume 0-100 (default: 50)" },
|
||||
{ flag: "--rate <rate>", description: "Speech rate 0.5-2.0 (default: 1.0)" },
|
||||
{ flag: "--pitch <pitch>", description: "Pitch multiplier 0.5-2.0 (default: 1.0)" },
|
||||
{ flag: "--seed <seed>", description: "Random seed 0-65535 for reproducible synthesis" },
|
||||
{ flag: "--language <lang>", description: "Language hint (e.g. zh, en, ja, ko, fr, de)" },
|
||||
{
|
||||
flag: "--instruction <text>",
|
||||
description:
|
||||
'Natural language instruction to control speech style (e.g. "Use a gentle tone")',
|
||||
},
|
||||
{ flag: "--enable-ssml", description: "Enable SSML markup parsing in input text" },
|
||||
{
|
||||
flag: "--out <path>",
|
||||
description: "Save audio to file (default: auto-generate in temp dir)",
|
||||
},
|
||||
{ flag: "--stream", description: "Stream raw PCM audio to stdout (pipe to player)" },
|
||||
],
|
||||
flags: SYNTHESIZE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--list-voices --model cosyvoice-v3-flash",
|
||||
'--text "Hello, I am Qwen" --voice <voice_id>',
|
||||
@@ -197,8 +233,8 @@ export default defineCommand({
|
||||
if (!f.voice) return "Missing required flag: --voice";
|
||||
return undefined;
|
||||
},
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const model = (flags.model as string) || config.defaultSpeechModel || "cosyvoice-v3-flash";
|
||||
async run(config, flags) {
|
||||
const model = flags.model || config.defaultSpeechModel || "cosyvoice-v3-flash";
|
||||
|
||||
// --list-voices: print voice list for the model and exit
|
||||
if (flags.listVoices) {
|
||||
@@ -207,9 +243,9 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
// --text / --text-file presence enforced by validate; empty file content → API rejects.
|
||||
let text = (flags.text as string) || "";
|
||||
let text = flags.text || "";
|
||||
if (!text && flags.textFile) {
|
||||
const filePath = flags.textFile as string;
|
||||
const filePath = flags.textFile;
|
||||
try {
|
||||
text = readFileSync(filePath, "utf-8").trim();
|
||||
} catch {
|
||||
@@ -218,9 +254,9 @@ export default defineCommand({
|
||||
}
|
||||
const voice = flags.voice as string;
|
||||
|
||||
const language = (flags.language as string) || undefined;
|
||||
const instruction = (flags.instruction as string) || undefined;
|
||||
const audioFormat = (flags.format as "mp3" | "pcm" | "wav" | "opus") || undefined;
|
||||
const language = flags.language || undefined;
|
||||
const instruction = flags.instruction || undefined;
|
||||
const audioFormat = flags.format || undefined;
|
||||
const sampleRate = flags.sampleRate !== undefined ? Number(flags.sampleRate) : undefined;
|
||||
const volume = flags.volume !== undefined ? Number(flags.volume) : undefined;
|
||||
const rate = flags.rate !== undefined ? Number(flags.rate) : undefined;
|
||||
@@ -274,7 +310,7 @@ async function handleNonStreamMode(
|
||||
config: Config,
|
||||
url: string,
|
||||
body: DashScopeTTSRequest,
|
||||
flags: GlobalFlags,
|
||||
flags: SynthesizeFlags,
|
||||
format: OutputFormat,
|
||||
): Promise<void> {
|
||||
const concurrent = getConcurrency(flags);
|
||||
@@ -294,7 +330,7 @@ async function handleNonStreamMode(
|
||||
const destDir = resolveOutputDir(config, { subDir: "speech" });
|
||||
|
||||
const items = audioUrls.map((audioUrl, i) => {
|
||||
let destPath = flags.out as string | undefined;
|
||||
let destPath = flags.out;
|
||||
if (destPath && audioUrls.length === 1) {
|
||||
// Single explicit output path
|
||||
} else {
|
||||
@@ -340,7 +376,7 @@ async function handleStreamMode(
|
||||
config: Config,
|
||||
url: string,
|
||||
body: DashScopeTTSRequest,
|
||||
flags: GlobalFlags,
|
||||
flags: SynthesizeFlags,
|
||||
format: OutputFormat,
|
||||
): Promise<void> {
|
||||
const res = await request(config, {
|
||||
@@ -354,7 +390,7 @@ async function handleStreamMode(
|
||||
},
|
||||
});
|
||||
|
||||
const outPath = flags.out as string | undefined;
|
||||
const outPath = flags.out;
|
||||
const writer = outPath ? createWriteStream(outPath) : null;
|
||||
let lastAudioUrl: string | undefined;
|
||||
|
||||
|
||||
@@ -5,31 +5,73 @@ import {
|
||||
chatEndpoint,
|
||||
parseSSE,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type ChatMessage,
|
||||
type ChatRequest,
|
||||
type ChatResponse,
|
||||
type StreamChunk,
|
||||
type FlagsDef,
|
||||
type Flags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const CHAT_FLAGS = {
|
||||
model: { type: "string", valueHint: "<model>", description: "Model ID (default: qwen3.7-max)" },
|
||||
message: {
|
||||
type: "array",
|
||||
valueHint: "<text>",
|
||||
description: "Message text (repeatable, prefix role: to set role); or use --messages-file",
|
||||
},
|
||||
messagesFile: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "JSON file with messages array (use - for stdin)",
|
||||
},
|
||||
system: { type: "string", valueHint: "<text>", description: "System prompt" },
|
||||
maxTokens: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Maximum tokens to generate (default: 4096)",
|
||||
},
|
||||
temperature: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Sampling temperature (0.0, 2.0]",
|
||||
},
|
||||
topP: { type: "number", valueHint: "<n>", description: "Nucleus sampling threshold" },
|
||||
stream: { type: "switch", description: "Stream response tokens (default: on in TTY)" },
|
||||
tool: {
|
||||
type: "array",
|
||||
valueHint: "<json-or-path>",
|
||||
description: "Tool definition as JSON or file path (repeatable)",
|
||||
},
|
||||
enableThinking: {
|
||||
type: "switch",
|
||||
description: "Enable thinking/reasoning mode (for qwen3/qwq models)",
|
||||
},
|
||||
thinkingBudget: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Max tokens for thinking (default: 4096)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
type ChatFlags = Flags<typeof CHAT_FLAGS>;
|
||||
|
||||
interface ParsedMessages {
|
||||
system?: string;
|
||||
messages: ChatMessage[];
|
||||
}
|
||||
|
||||
function parseMessages(flags: GlobalFlags): ParsedMessages {
|
||||
function parseMessages(flags: ChatFlags): ParsedMessages {
|
||||
const messages: ChatMessage[] = [];
|
||||
let system: string | undefined;
|
||||
|
||||
if (flags.system) {
|
||||
system = flags.system as string;
|
||||
system = flags.system;
|
||||
}
|
||||
|
||||
if (flags.messagesFile) {
|
||||
const filePath = flags.messagesFile as string;
|
||||
const filePath = flags.messagesFile;
|
||||
const raw =
|
||||
filePath === "-" ? readFileSync("/dev/stdin", "utf-8") : readFileSync(filePath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as Array<{ role: string; content: string }>;
|
||||
@@ -44,7 +86,7 @@ function parseMessages(flags: GlobalFlags): ParsedMessages {
|
||||
|
||||
if (flags.message) {
|
||||
const validRoles = new Set(["system", "user", "assistant"]);
|
||||
const msgs = flags.message as string[];
|
||||
const msgs = flags.message;
|
||||
for (const m of msgs) {
|
||||
const colonIdx = m.indexOf(":");
|
||||
const maybeRole = colonIdx !== -1 ? m.slice(0, colonIdx) : "";
|
||||
@@ -69,41 +111,7 @@ export default defineCommand({
|
||||
description: "Send a chat completion (OpenAI compatible, DashScope)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--message <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--model <model>", description: "Model ID (default: qwen3.7-max)" },
|
||||
{
|
||||
flag: "--message <text>",
|
||||
description: "Message text (repeatable, prefix role: to set role); or use --messages-file",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--messages-file <path>",
|
||||
description: "JSON file with messages array (use - for stdin)",
|
||||
},
|
||||
{ flag: "--system <text>", description: "System prompt" },
|
||||
{
|
||||
flag: "--max-tokens <n>",
|
||||
description: "Maximum tokens to generate (default: 4096)",
|
||||
type: "number",
|
||||
},
|
||||
{ flag: "--temperature <n>", description: "Sampling temperature (0.0, 2.0]", type: "number" },
|
||||
{ flag: "--top-p <n>", description: "Nucleus sampling threshold", type: "number" },
|
||||
{ flag: "--stream", description: "Stream response tokens (default: on in TTY)" },
|
||||
{
|
||||
flag: "--tool <json-or-path>",
|
||||
description: "Tool definition as JSON or file path (repeatable)",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--enable-thinking",
|
||||
description: "Enable thinking/reasoning mode (for qwen3/qwq models)",
|
||||
},
|
||||
{
|
||||
flag: "--thinking-budget <n>",
|
||||
description: "Max tokens for thinking (default: 4096)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
flags: CHAT_FLAGS,
|
||||
exampleArgs: [
|
||||
'--message "What is Qwen?"',
|
||||
'--model qwen-max --system "You are a coding assistant." --message "Write fizzbuzz in Python"',
|
||||
@@ -114,12 +122,11 @@ export default defineCommand({
|
||||
],
|
||||
validate: (f) =>
|
||||
!f.message && !f.messagesFile ? "Provide --message or --messages-file." : undefined,
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
const { system, messages } = parseMessages(flags);
|
||||
|
||||
const model = (flags.model as string) || config.defaultTextModel || "qwen3.7-max";
|
||||
const shouldStream =
|
||||
flags.stream === true || (flags.stream === undefined && process.stdout.isTTY);
|
||||
const model = flags.model || config.defaultTextModel || "qwen3.7-max";
|
||||
const shouldStream = flags.stream || process.stdout.isTTY;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
// Build messages array with system prompt
|
||||
@@ -132,22 +139,22 @@ export default defineCommand({
|
||||
const body: ChatRequest = {
|
||||
model,
|
||||
messages: allMessages,
|
||||
max_tokens: (flags.maxTokens as number) ?? 4096,
|
||||
max_tokens: flags.maxTokens ?? 4096,
|
||||
stream: shouldStream,
|
||||
};
|
||||
|
||||
if (flags.temperature !== undefined) body.temperature = flags.temperature as number;
|
||||
if (flags.topP !== undefined) body.top_p = flags.topP as number;
|
||||
if (flags.temperature !== undefined) body.temperature = flags.temperature;
|
||||
if (flags.topP !== undefined) body.top_p = flags.topP;
|
||||
|
||||
if (flags.enableThinking) {
|
||||
body.enable_thinking = true;
|
||||
if (flags.thinkingBudget !== undefined) {
|
||||
body.thinking_budget = flags.thinkingBudget as number;
|
||||
body.thinking_budget = flags.thinkingBudget;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.tool) {
|
||||
const tools = (flags.tool as string[]).map((t) => {
|
||||
const tools = flags.tool.map((t) => {
|
||||
try {
|
||||
return JSON.parse(t);
|
||||
} catch {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
fetchModelList,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -187,30 +186,30 @@ export default defineCommand({
|
||||
description: "Query free-tier quota for models (all models if --model is omitted)",
|
||||
auth: "console",
|
||||
usageArgs: "[--model <model>[,model2,...]] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name(s) to query, comma-separated for multiple; omit for all models",
|
||||
},
|
||||
{
|
||||
flag: "--expiring <days>",
|
||||
expiring: {
|
||||
type: "string",
|
||||
valueHint: "<days>",
|
||||
description: "Only show quotas expiring within N days",
|
||||
},
|
||||
{
|
||||
flag: "--sort <field>",
|
||||
sort: {
|
||||
type: "string",
|
||||
valueHint: "<field>",
|
||||
description: "Sort by: remaining (ascending), expires (ascending)",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"",
|
||||
"--model qwen3-max",
|
||||
@@ -220,11 +219,11 @@ export default defineCommand({
|
||||
"--model qwen-turbo --output json",
|
||||
"--model qwen3-max --console-region cn-beijing",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelFlag = (flags.model as string) || undefined;
|
||||
async run(config, flags) {
|
||||
const modelFlag = flags.model || undefined;
|
||||
const expiringDays = Number(flags.expiring) || 0;
|
||||
const VALID_SORT_FIELDS = ["remaining", "expires"] as const;
|
||||
const sortField = (flags.sort as string) || undefined;
|
||||
const sortField = flags.sort || undefined;
|
||||
if (sortField && !VALID_SORT_FIELDS.includes(sortField as (typeof VALID_SORT_FIELDS)[number])) {
|
||||
process.stderr.write(
|
||||
`Error: invalid --sort value "${sortField}". Must be one of: ${VALID_SORT_FIELDS.join(", ")}\n`,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
fetchModelList,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
@@ -104,34 +103,32 @@ export default defineCommand({
|
||||
"Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable",
|
||||
auth: "console",
|
||||
usageArgs: "<--model <model>[,model2,...] | --all> [--off] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name(s), comma-separated for multiple",
|
||||
},
|
||||
{
|
||||
flag: "--all",
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Apply to all free-tier models",
|
||||
},
|
||||
{
|
||||
flag: "--on",
|
||||
on: {
|
||||
type: "switch",
|
||||
description: "Enable auto-stop (default behavior)",
|
||||
},
|
||||
{
|
||||
flag: "--off",
|
||||
off: {
|
||||
type: "switch",
|
||||
description: "Disable auto-stop",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"--model qwen3-max",
|
||||
"--model qwen3-max,qwen-turbo",
|
||||
@@ -142,8 +139,8 @@ export default defineCommand({
|
||||
],
|
||||
validate: (f) =>
|
||||
!f.model && !f.all ? "Provide --model <model>[,model2,...] or --all." : undefined,
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelFlag = (flags.model as string) || undefined;
|
||||
async run(config, flags) {
|
||||
const modelFlag = flags.model || undefined;
|
||||
const off = Boolean(flags.off);
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -288,34 +287,35 @@ export default defineCommand({
|
||||
description: "Query model usage statistics",
|
||||
auth: "console",
|
||||
usageArgs: "[--model <model>] [--days <days>] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model name(s), comma-separated; omit for overview",
|
||||
},
|
||||
{
|
||||
flag: "--days <days>",
|
||||
days: {
|
||||
type: "string",
|
||||
valueHint: "<days>",
|
||||
description: "Number of days (default: 7)",
|
||||
},
|
||||
{
|
||||
flag: "--type <type>",
|
||||
type: {
|
||||
type: "string",
|
||||
valueHint: "<type>",
|
||||
description: "Model type: Text, Vision, Multimodal, Audio, Embedding",
|
||||
},
|
||||
{
|
||||
flag: "--workspace-id <id>",
|
||||
workspaceId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Workspace ID (env: BAILIAN_WORKSPACE_ID)",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: [
|
||||
"",
|
||||
"--days 30",
|
||||
@@ -325,13 +325,13 @@ export default defineCommand({
|
||||
"--type Text --days 14",
|
||||
"--output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const modelFlag = (flags.model as string) || undefined;
|
||||
async run(config, flags) {
|
||||
const modelFlag = flags.model || undefined;
|
||||
const daysFlag = Number(flags.days) || 7;
|
||||
const typeFlag = (flags.type as string) || undefined;
|
||||
const typeFlag = flags.type || undefined;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const flagWorkspaceId = (flags.workspaceId as string) || undefined;
|
||||
const flagWorkspaceId = flags.workspaceId || undefined;
|
||||
const workspaceId = resolveWorkspaceId(config, flagWorkspaceId);
|
||||
|
||||
const endTime = Date.now();
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeTaskResponse,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
@@ -16,18 +14,23 @@ export default defineCommand({
|
||||
description: "Download a completed video by task ID",
|
||||
auth: "none",
|
||||
usageArgs: "--task-id <id> --out <path>",
|
||||
options: [
|
||||
{ flag: "--task-id <id>", description: "Task ID to download from", required: true },
|
||||
{ flag: "--out <path>", description: "Output file path", required: true },
|
||||
],
|
||||
flags: {
|
||||
taskId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Task ID to download from",
|
||||
required: true,
|
||||
},
|
||||
out: { type: "string", valueHint: "<path>", description: "Output file path", required: true },
|
||||
},
|
||||
exampleArgs: [
|
||||
"--task-id 3b256896-xxxx --out video.mp4",
|
||||
"--task-id 3b256896-xxxx --out video.mp4 --quiet",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const taskId = flags.taskId as string;
|
||||
async run(config, flags) {
|
||||
const taskId = flags.taskId;
|
||||
|
||||
const outPath = flags.out as string;
|
||||
const outPath = flags.out;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
videoGenerateEndpoint,
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeVideoEditRequest,
|
||||
type DashScopeAsyncResponse,
|
||||
type DashScopeTaskResponse,
|
||||
@@ -27,81 +25,110 @@ export default defineCommand({
|
||||
"Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--video <url> --prompt <text> [flags]",
|
||||
options: [
|
||||
{ flag: "--model <model>", description: "Model ID (default: happyhorse-1.0-video-edit)" },
|
||||
{
|
||||
flag: "--video <url>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: happyhorse-1.0-video-edit)",
|
||||
},
|
||||
video: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Input video URL or local file (mp4/mov, 2-10s)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--prompt <text>",
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: 'Edit instruction (e.g. "Convert the scene to a claymation style")',
|
||||
},
|
||||
{ flag: "--ref-image <url>", description: "Reference image URL (up to 4, comma-separated)" },
|
||||
{
|
||||
flag: "--negative-prompt <text>",
|
||||
refImage: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Reference image URL (up to 4, comma-separated)",
|
||||
},
|
||||
negativePrompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Negative prompt to exclude unwanted content",
|
||||
},
|
||||
{ flag: "--resolution <res>", description: "Resolution: 720P or 1080P (default: 1080P)" },
|
||||
{ flag: "--ratio <ratio>", description: "Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4)" },
|
||||
{
|
||||
flag: "--duration <seconds>",
|
||||
description: "Output video duration in seconds (2-10)",
|
||||
resolution: {
|
||||
type: "string",
|
||||
valueHint: "<res>",
|
||||
description: "Resolution: 720P or 1080P (default: 1080P)",
|
||||
},
|
||||
ratio: {
|
||||
type: "string",
|
||||
valueHint: "<ratio>",
|
||||
description: "Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4)",
|
||||
},
|
||||
duration: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Output video duration in seconds (2-10)",
|
||||
},
|
||||
{
|
||||
flag: "--audio-setting <mode>",
|
||||
audioSetting: {
|
||||
type: "string",
|
||||
valueHint: "<mode>",
|
||||
description: "Audio: auto (default) or origin (keep original)",
|
||||
choices: ["auto", "origin"] as const,
|
||||
},
|
||||
{
|
||||
flag: "--prompt-extend <bool>",
|
||||
promptExtend: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--watermark <bool>",
|
||||
watermark: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
type: "boolean",
|
||||
},
|
||||
{ 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" },
|
||||
{
|
||||
flag: "--async",
|
||||
seed: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Random seed for reproducible generation",
|
||||
},
|
||||
download: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save video to file on completion",
|
||||
},
|
||||
noWait: { type: "switch", description: "Return task ID immediately without waiting" },
|
||||
pollInterval: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Polling interval when waiting (default: 15)",
|
||||
},
|
||||
async: {
|
||||
type: "switch",
|
||||
description: "Return task ID immediately (agent/CI mode, same as --no-wait)",
|
||||
},
|
||||
{
|
||||
flag: "--poll-interval <seconds>",
|
||||
description: "Polling interval when waiting (default: 15)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
'--video https://example.com/input.mp4 --prompt "Convert the entire scene to claymation style"',
|
||||
'--video https://example.com/input.mp4 --prompt "Replace the outfit with the style shown in the image" --ref-image https://example.com/clothes.png',
|
||||
'--video https://example.com/input.mp4 --prompt "Convert to anime style" --resolution 720P --download output.mp4',
|
||||
'--video https://example.com/input.mp4 --prompt "Put clothes on the kitten in the video" --watermark false',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const videoUrl = flags.video as string;
|
||||
async run(config, flags) {
|
||||
const videoUrl = flags.video;
|
||||
|
||||
// prompt is optional for video edit per API spec
|
||||
const prompt = flags.prompt as string | undefined;
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model = (flags.model as string) || "happyhorse-1.0-video-edit";
|
||||
const model = flags.model || "happyhorse-1.0-video-edit";
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
// Auto-upload local files
|
||||
const credential = await resolveCredential(config);
|
||||
const resolvedVideoUrl = await resolveFileUrl(videoUrl!, credential.token, model);
|
||||
const resolvedVideoUrl = await resolveFileUrl(videoUrl, credential.token, model);
|
||||
// --- Build media array ---
|
||||
const media: DashScopeVideoEditRequest["input"]["media"] = [
|
||||
{ type: "video", url: resolvedVideoUrl },
|
||||
];
|
||||
|
||||
// Support comma-separated reference images
|
||||
const refImageArg = flags.refImage as string | undefined;
|
||||
const refImageArg = flags.refImage;
|
||||
if (refImageArg) {
|
||||
const images = refImageArg
|
||||
.split(",")
|
||||
@@ -121,17 +148,17 @@ export default defineCommand({
|
||||
model,
|
||||
input: {
|
||||
prompt: prompt || undefined,
|
||||
negative_prompt: (flags.negativePrompt as string) || undefined,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
media,
|
||||
},
|
||||
parameters: {
|
||||
resolution: (flags.resolution as string) || undefined,
|
||||
ratio: (flags.ratio as string) || undefined,
|
||||
duration: (flags.duration as number) || undefined,
|
||||
audio_setting: (flags.audioSetting as "auto" | "origin") || undefined,
|
||||
resolution: flags.resolution || undefined,
|
||||
ratio: flags.ratio || undefined,
|
||||
duration: flags.duration || undefined,
|
||||
audio_setting: flags.audioSetting || undefined,
|
||||
prompt_extend: promptExtend,
|
||||
watermark,
|
||||
seed: flags.seed as number | undefined,
|
||||
seed: flags.seed,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -164,7 +191,7 @@ export default defineCommand({
|
||||
|
||||
// --- Poll until completion ---
|
||||
// Video editing is compute-intensive; default timeout = 600s (10 min)
|
||||
const pollInterval = (flags.pollInterval as number) ?? 15;
|
||||
const pollInterval = flags.pollInterval ?? 15;
|
||||
const pollUrl = taskEndpoint(config.baseUrl, taskId);
|
||||
const editTimeout = Math.max(config.timeout, 600);
|
||||
|
||||
@@ -190,7 +217,7 @@ export default defineCommand({
|
||||
|
||||
// --download: save to file
|
||||
if (flags.download) {
|
||||
const destPath = flags.download as string;
|
||||
const destPath = flags.download;
|
||||
const { size } = await downloadFile(resultVideoUrl, destPath, { quiet: config.quiet });
|
||||
|
||||
if (config.quiet) {
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
videoGenerateEndpoint,
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeVideoRequest,
|
||||
type DashScopeAsyncResponse,
|
||||
type DashScopeTaskResponse,
|
||||
@@ -28,47 +26,74 @@ export default defineCommand({
|
||||
"Generate a video from text or image (happyhorse-1.0-t2v / happyhorse-1.0-i2v / wan2.6-t2v)",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--prompt <text> [--image <url>] [flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--model <model>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: happyhorse-1.0-t2v, or happyhorse-1.0-i2v with --image)",
|
||||
},
|
||||
{ flag: "--prompt <text>", description: "Video description", required: true },
|
||||
{ flag: "--image <url>", description: "Input image URL for image-to-video generation" },
|
||||
{
|
||||
flag: "--negative-prompt <text>",
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Video description",
|
||||
required: true,
|
||||
},
|
||||
image: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Input image URL for image-to-video generation",
|
||||
},
|
||||
negativePrompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Negative prompt to exclude unwanted content",
|
||||
},
|
||||
{ flag: "--resolution <res>", description: "Resolution: 720P or 1080P (default: 1080P)" },
|
||||
{ flag: "--ratio <ratio>", description: "Aspect ratio (e.g. 16:9, 9:16, 1:1)" },
|
||||
{
|
||||
flag: "--duration <seconds>",
|
||||
description: "Video duration in seconds (default: 5)",
|
||||
resolution: {
|
||||
type: "string",
|
||||
valueHint: "<res>",
|
||||
description: "Resolution: 720P or 1080P (default: 1080P)",
|
||||
},
|
||||
ratio: {
|
||||
type: "string",
|
||||
valueHint: "<ratio>",
|
||||
description: "Aspect ratio (e.g. 16:9, 9:16, 1:1)",
|
||||
},
|
||||
duration: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Video duration in seconds (default: 5)",
|
||||
},
|
||||
{
|
||||
flag: "--prompt-extend <bool>",
|
||||
promptExtend: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--watermark <bool>",
|
||||
watermark: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
type: "boolean",
|
||||
},
|
||||
{ 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" },
|
||||
{
|
||||
flag: "--async",
|
||||
seed: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Random seed for reproducible generation",
|
||||
},
|
||||
download: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save video to file on completion",
|
||||
},
|
||||
noWait: { type: "switch", description: "Return task ID immediately without waiting" },
|
||||
pollInterval: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Polling interval when waiting (default: 5)",
|
||||
},
|
||||
async: {
|
||||
type: "switch",
|
||||
description: "Return task ID immediately (agent/CI mode, same as --no-wait)",
|
||||
},
|
||||
{
|
||||
flag: "--poll-interval <seconds>",
|
||||
description: "Polling interval when waiting (default: 5)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
'--prompt "A person reading a book, static shot"',
|
||||
'--prompt "Ocean waves at sunset." --download sunset.mp4',
|
||||
@@ -76,16 +101,16 @@ export default defineCommand({
|
||||
'--prompt "Mountain landscape" --resolution 720P --duration 5',
|
||||
'--prompt "A cat playing with a ball" --watermark false',
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const prompt = flags.prompt as string;
|
||||
async run(config, flags) {
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model =
|
||||
(flags.model as string) ||
|
||||
flags.model ||
|
||||
config.defaultVideoModel ||
|
||||
((flags.image as string) ? "happyhorse-1.0-i2v" : "happyhorse-1.0-t2v");
|
||||
(flags.image ? "happyhorse-1.0-i2v" : "happyhorse-1.0-t2v");
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
const imageUrl = flags.image as string | undefined;
|
||||
const imageUrl = flags.image;
|
||||
|
||||
// Auto-upload local image file for i2v
|
||||
let resolvedImageUrl: string | undefined;
|
||||
@@ -100,20 +125,20 @@ export default defineCommand({
|
||||
const body: DashScopeVideoRequest = {
|
||||
model,
|
||||
input: {
|
||||
prompt: prompt!,
|
||||
negative_prompt: (flags.negativePrompt as string) || undefined,
|
||||
prompt: prompt,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
// i2v models (happyhorse-1.0-i2v) require input.media with type 'first_frame'
|
||||
...(resolvedImageUrl
|
||||
? { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
|
||||
: {}),
|
||||
},
|
||||
parameters: {
|
||||
resolution: (flags.resolution as string) || undefined,
|
||||
ratio: (flags.ratio as string) || undefined,
|
||||
duration: (flags.duration as number) || undefined,
|
||||
resolution: flags.resolution || undefined,
|
||||
ratio: flags.ratio || undefined,
|
||||
duration: flags.duration || undefined,
|
||||
prompt_extend: promptExtend,
|
||||
watermark,
|
||||
seed: flags.seed as number | undefined,
|
||||
seed: flags.seed,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -152,7 +177,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
// Poll all tasks concurrently
|
||||
const pollInterval = (flags.pollInterval as number) ?? 5;
|
||||
const pollInterval = flags.pollInterval ?? 5;
|
||||
|
||||
const pollPromises = taskIds.map((taskId) => {
|
||||
const pollUrl = taskEndpoint(config.baseUrl, taskId);
|
||||
@@ -189,7 +214,7 @@ export default defineCommand({
|
||||
|
||||
// --download: save to file (first video only for explicit path)
|
||||
if (flags.download) {
|
||||
const destPath = flags.download as string;
|
||||
const destPath = flags.download;
|
||||
const { size } = await downloadFile(videos[0]!.videoUrl, destPath, { quiet: config.quiet });
|
||||
|
||||
if (config.quiet) {
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
videoGenerateEndpoint,
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeVideoRefRequest,
|
||||
type DashScopeAsyncResponse,
|
||||
type DashScopeTaskResponse,
|
||||
@@ -27,63 +25,80 @@ export default defineCommand({
|
||||
"Reference-to-video generation (happyhorse-1.0-r2v / wan2.6-r2v): multi-subject, multi-shot with voice",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--prompt <text> --image <url>... [--ref-video <url>...] [flags]",
|
||||
options: [
|
||||
{ flag: "--model <model>", description: "Model ID (default: happyhorse-1.0-r2v)" },
|
||||
{
|
||||
flag: "--prompt <text>",
|
||||
flags: {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: happyhorse-1.0-r2v)",
|
||||
},
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Video description with reference markers (image1, video1, etc.)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--image <url>",
|
||||
image: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Reference image URL or local file (repeatable for multiple subjects)",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--ref-video <url>",
|
||||
refVideo: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Reference video URL or local file (repeatable)",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--image-voice <url>",
|
||||
imageVoice: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Voice URL for corresponding image (pairs by position)",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--video-voice <url>",
|
||||
videoVoice: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Voice URL for corresponding ref-video (pairs by position)",
|
||||
type: "array",
|
||||
},
|
||||
{ flag: "--resolution <res>", description: "Resolution: 720P or 1080P (default: 1080P)" },
|
||||
{ flag: "--ratio <ratio>", description: "Aspect ratio (16:9, 9:16, 1:1)" },
|
||||
{
|
||||
flag: "--duration <seconds>",
|
||||
description: "Video duration in seconds (default: 5)",
|
||||
resolution: {
|
||||
type: "string",
|
||||
valueHint: "<res>",
|
||||
description: "Resolution: 720P or 1080P (default: 1080P)",
|
||||
},
|
||||
ratio: { type: "string", valueHint: "<ratio>", description: "Aspect ratio (16:9, 9:16, 1:1)" },
|
||||
duration: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Video duration in seconds (default: 5)",
|
||||
},
|
||||
{
|
||||
flag: "--prompt-extend <bool>",
|
||||
promptExtend: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
flag: "--watermark <bool>",
|
||||
watermark: {
|
||||
type: "boolean",
|
||||
valueHint: "<bool>",
|
||||
description: BOOL_FLAG_WATERMARK,
|
||||
type: "boolean",
|
||||
},
|
||||
{ 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" },
|
||||
{
|
||||
flag: "--async",
|
||||
seed: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Random seed for reproducible generation",
|
||||
},
|
||||
download: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Save video to file on completion",
|
||||
},
|
||||
noWait: { type: "switch", description: "Return task ID immediately without waiting" },
|
||||
pollInterval: {
|
||||
type: "number",
|
||||
valueHint: "<seconds>",
|
||||
description: "Polling interval when waiting (default: 15)",
|
||||
},
|
||||
async: {
|
||||
type: "switch",
|
||||
description: "Return task ID immediately (agent/CI mode, same as --no-wait)",
|
||||
},
|
||||
{
|
||||
flag: "--poll-interval <seconds>",
|
||||
description: "Polling interval when waiting (default: 15)",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
},
|
||||
exampleArgs: [
|
||||
'--prompt "Image1 running on the grass" --image person.jpg',
|
||||
'--prompt "Video 1 plays guitar, Image 1 walks over" --ref-video scene.mp4 --image person.jpg',
|
||||
@@ -95,16 +110,16 @@ export default defineCommand({
|
||||
!(f.image as string[] | undefined)?.length && !(f.refVideo as string[] | undefined)?.length
|
||||
? "Provide at least one --image or --ref-video."
|
||||
: undefined,
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const prompt = flags.prompt as string;
|
||||
async run(config, flags) {
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const images = (flags.image as string[] | undefined) || [];
|
||||
const refVideos = (flags.refVideo as string[] | undefined) || [];
|
||||
const images = flags.image || [];
|
||||
const refVideos = flags.refVideo || [];
|
||||
|
||||
const imageVoices = (flags.imageVoice as string[] | undefined) || [];
|
||||
const videoVoices = (flags.videoVoice as string[] | undefined) || [];
|
||||
const imageVoices = flags.imageVoice || [];
|
||||
const videoVoices = flags.videoVoice || [];
|
||||
|
||||
const model = (flags.model as string) || "happyhorse-1.0-r2v";
|
||||
const model = flags.model || "happyhorse-1.0-r2v";
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
// --- Resolve file URLs (auto-upload local files) ---
|
||||
@@ -152,16 +167,16 @@ export default defineCommand({
|
||||
const body: DashScopeVideoRefRequest = {
|
||||
model,
|
||||
input: {
|
||||
prompt: prompt!,
|
||||
prompt: prompt,
|
||||
media,
|
||||
},
|
||||
parameters: {
|
||||
resolution: (flags.resolution as string) || undefined,
|
||||
ratio: (flags.ratio as string) || undefined,
|
||||
duration: (flags.duration as number) || undefined,
|
||||
resolution: flags.resolution || undefined,
|
||||
ratio: flags.ratio || undefined,
|
||||
duration: flags.duration || undefined,
|
||||
prompt_extend: promptExtend,
|
||||
watermark,
|
||||
seed: flags.seed as number | undefined,
|
||||
seed: flags.seed,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -195,7 +210,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
// --- Poll until completion ---
|
||||
const pollInterval = (flags.pollInterval as number) ?? 15;
|
||||
const pollInterval = flags.pollInterval ?? 15;
|
||||
const pollUrl = taskEndpoint(config.baseUrl, taskId);
|
||||
const refTimeout = Math.max(config.timeout, 600);
|
||||
|
||||
@@ -221,7 +236,7 @@ export default defineCommand({
|
||||
|
||||
// --download: save to file
|
||||
if (flags.download) {
|
||||
const destPath = flags.download as string;
|
||||
const destPath = flags.download;
|
||||
const { size } = await downloadFile(resultVideoUrl, destPath, { quiet: config.quiet });
|
||||
|
||||
if (config.quiet) {
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
taskEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type DashScopeTaskResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
@@ -13,13 +11,15 @@ export default defineCommand({
|
||||
description: "Query async task status",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--task-id <id>",
|
||||
options: [{ flag: "--task-id <id>", description: "Async task ID", required: true }],
|
||||
flags: {
|
||||
taskId: { type: "string", valueHint: "<id>", description: "Async task ID", required: true },
|
||||
},
|
||||
exampleArgs: [
|
||||
"--task-id 3b256896-3e70-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"--task-id 3b256896-3e70-xxxx --output json",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const taskId = flags.taskId as string;
|
||||
async run(config, flags) {
|
||||
const taskId = flags.taskId;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
requestJson,
|
||||
chatEndpoint,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type ChatRequest,
|
||||
type ChatResponse,
|
||||
type ChatMessageContent,
|
||||
@@ -58,16 +56,24 @@ export default defineCommand({
|
||||
description: "Describe an image or video using Qwen-VL",
|
||||
auth: "apiKey",
|
||||
usageArgs: "--image <path-or-url> [--video <url>] [--prompt <text>]",
|
||||
options: [
|
||||
{ flag: "--image <path-or-url>", description: "Local image path or URL" },
|
||||
{
|
||||
flag: "--video <url>",
|
||||
description: "Video file URL or local path (mp4/mov/avi/mkv/webm)",
|
||||
flags: {
|
||||
image: { type: "string", valueHint: "<path-or-url>", description: "Local image path or URL" },
|
||||
video: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: "Video file URL or local path (mp4/mov/avi/mkv/webm)",
|
||||
},
|
||||
{ flag: "--prompt <text>", description: "Question about the content (default: auto-detected)" },
|
||||
{ flag: "--model <model>", description: "Vision model (default: qwen3-vl-plus)" },
|
||||
],
|
||||
prompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: "Question about the content (default: auto-detected)",
|
||||
},
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Vision model (default: qwen3-vl-plus)",
|
||||
},
|
||||
},
|
||||
exampleArgs: [
|
||||
"--image photo.jpg",
|
||||
'--image https://example.com/photo.jpg --prompt "What breed is this dog?"',
|
||||
@@ -79,10 +85,10 @@ export default defineCommand({
|
||||
!f.image && !(f.video as string[] | undefined)?.length
|
||||
? "Provide --image or --video."
|
||||
: undefined,
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
let image = flags.image as string | undefined;
|
||||
const videoInputs = (flags.video as string[] | undefined) ?? [];
|
||||
const model = (flags.model as string) || "qwen3-vl-plus";
|
||||
async run(config, flags) {
|
||||
let image = flags.image;
|
||||
const videoInputs = flags.video ?? [];
|
||||
const model = flags.model || "qwen3-vl-plus";
|
||||
|
||||
// Auto-detect: if --image was given a video file, treat it as --video
|
||||
if (image && isVideoInput(image)) {
|
||||
@@ -92,7 +98,7 @@ export default defineCommand({
|
||||
|
||||
const hasVideo = videoInputs.length > 0;
|
||||
const defaultPrompt = hasVideo ? "Describe the video." : "Describe the image.";
|
||||
const prompt = (flags.prompt as string) || defaultPrompt;
|
||||
const prompt = flags.prompt || defaultPrompt;
|
||||
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
callConsoleGateway,
|
||||
resolveConsoleGatewayCredential,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
@@ -79,24 +77,22 @@ export default defineCommand({
|
||||
description: "List all workspaces",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
options: [
|
||||
{
|
||||
flag: "--list <n>",
|
||||
flags: {
|
||||
list: {
|
||||
type: "string",
|
||||
valueHint: "<n>",
|
||||
description: "Limit number of results",
|
||||
},
|
||||
{ flag: "--console-region <region>", description: "Console region" },
|
||||
{
|
||||
flag: "--console-site <site>",
|
||||
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID",
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
|
||||
},
|
||||
exampleArgs: ["", "--list 5", "--output json"],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
async run(config, flags) {
|
||||
const limit = Number(flags.list) || 0;
|
||||
const format = detectOutputFormat(config.output);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ensureConfigDir, getConfigPath } from "./paths.ts";
|
||||
import { detectOutputFormat, type OutputFormat } from "../output/formatter.ts";
|
||||
import { BailianError } from "../errors/base.ts";
|
||||
import { ExitCode } from "../errors/codes.ts";
|
||||
import type { GlobalFlags } from "../types/flags.ts";
|
||||
import type { GlobalFlags } from "../types/command.ts";
|
||||
|
||||
export function readConfigFile(): ConfigFile {
|
||||
const path = getConfigPath();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Config } from "../config/schema.ts";
|
||||
import type { GlobalFlags } from "../types/flags.ts";
|
||||
import type { GlobalFlags } from "../types/command.ts";
|
||||
import { BailianError } from "../errors/base.ts";
|
||||
import { createTrackingEvent } from "./event.ts";
|
||||
import { localSink, remoteSink } from "./sink.ts";
|
||||
|
||||
@@ -1,111 +1,135 @@
|
||||
import type { Config } from "../config/schema.ts";
|
||||
import type { GlobalFlags } from "./flags.ts";
|
||||
|
||||
/**
|
||||
* Flag value type, driving the parser.
|
||||
* - string : `--flag <value>` → string (default).
|
||||
* - number : `--flag <n>` → coerced + validated finite number.
|
||||
* - boolean : `--flag true|false` → coerced boolean (a *value* flag).
|
||||
* - switch : `--flag` → presence = true, takes NO value (`--flag=x` errors).
|
||||
* - array : repeatable `--flag a --flag b` → string[].
|
||||
* Omitting `type`: a flag string with a `<…>`/`[…]` placeholder defaults to
|
||||
* string, otherwise to switch.
|
||||
*/
|
||||
export interface OptionDef {
|
||||
flag: string;
|
||||
// ── Flag definitions ─────────────────────────────────────────────────────────
|
||||
// Flags are keyed by camelCase name (the key IS the parsed flag name, e.g.
|
||||
// `maxTokens` ↔ `--max-tokens`). The flag's type drives both runtime parsing
|
||||
// and the compile-time flag types inferred via ParsedFlags.
|
||||
|
||||
/** A presence flag: `--quiet`. No value; absent → false. */
|
||||
export interface SwitchFlag {
|
||||
type: "switch";
|
||||
description: string;
|
||||
type?: "string" | "number" | "boolean" | "switch" | "array";
|
||||
required?: boolean;
|
||||
}
|
||||
/** A value flag: `--prompt <text>`, `--n <count>`, `--watermark true|false`. */
|
||||
export interface ValueFlag {
|
||||
type: "string" | "number" | "boolean" | "array";
|
||||
description: string;
|
||||
valueHint: string;
|
||||
required?: boolean;
|
||||
/**
|
||||
* Restrict to a fixed set of values. The parser rejects anything else and the
|
||||
* parsed type narrows to the union. Declare with `as const` so the literals
|
||||
* survive inference: `choices: ["mp3", "wav"] as const`.
|
||||
*/
|
||||
choices?: readonly string[];
|
||||
}
|
||||
export type FlagDef = SwitchFlag | ValueFlag;
|
||||
export type FlagsDef = Record<string, FlagDef>;
|
||||
|
||||
// ── Type inference: definition → parsed flag types ───────────────────────────
|
||||
type ParsedValue<F extends FlagDef> = F extends SwitchFlag
|
||||
? boolean
|
||||
: F extends { type: "number" }
|
||||
? number
|
||||
: F extends { type: "boolean" }
|
||||
? boolean
|
||||
: F extends { choices: readonly (infer C extends string)[] }
|
||||
? F extends { type: "array" }
|
||||
? C[]
|
||||
: C
|
||||
: F extends { type: "array" }
|
||||
? string[]
|
||||
: string;
|
||||
|
||||
/** Switches and `required` value flags are present; other value flags optional. */
|
||||
type IsRequired<F extends FlagDef> = F extends SwitchFlag
|
||||
? true
|
||||
: F extends { required: true }
|
||||
? true
|
||||
: false;
|
||||
|
||||
/**
|
||||
* Credential a command requires. The runtime prepares it before execution.
|
||||
* - "apiKey" : DashScope API Key. The runtime runs ensureApiKey beforehand,
|
||||
* except under --dry-run (which only prints the request).
|
||||
* - "console" : Console Gateway credential (usage / quota / app list / workspace, …).
|
||||
* - "none" : No credential (config / auth login flow / pipeline validate / update, …).
|
||||
* Map a FlagsDef to the parsed flags object type. Required flags (switches +
|
||||
* `required: true`) are required properties; optional value flags are `?`.
|
||||
*/
|
||||
export type ParsedFlags<F extends FlagsDef> = {
|
||||
[K in keyof F as IsRequired<F[K]> extends true ? K : never]: ParsedValue<F[K]>;
|
||||
} & {
|
||||
[K in keyof F as IsRequired<F[K]> extends true ? never : K]?: ParsedValue<F[K]>;
|
||||
};
|
||||
|
||||
export type AuthRequirement = "apiKey" | "console" | "none";
|
||||
|
||||
export interface Command {
|
||||
description: string;
|
||||
/**
|
||||
* Argument portion of the usage line, WITHOUT the `<bin> <path>` prefix
|
||||
* (e.g. "--index-id <id> --query <text> [flags]"). The runtime prepends the
|
||||
* product binary name and the command's actual path when rendering help, so
|
||||
* the same command renders correctly under any product (bl / rag / …).
|
||||
*/
|
||||
usageArgs?: string;
|
||||
options?: OptionDef[];
|
||||
/**
|
||||
* Example argument strings, each WITHOUT the `<bin> <path>` prefix
|
||||
* (e.g. '--index-id idx_xxx --query "..."'). The runtime prepends
|
||||
* `<bin> <path>` per product when rendering help.
|
||||
*/
|
||||
exampleArgs?: string[];
|
||||
/** Credential this command requires. See {@link AuthRequirement}. */
|
||||
auth: AuthRequirement;
|
||||
notes?: string[];
|
||||
/**
|
||||
* Cross-flag validation, run after parsing and before execute (one-of, 3-of-N,
|
||||
* value-conditional, dependency, …). Return an error message → UsageError;
|
||||
* undefined to pass. Single-flag `required: true` is enforced by the parser —
|
||||
* use this only for rules spanning multiple flags or depending on a flag's *value*.
|
||||
*/
|
||||
validate?: (flags: GlobalFlags) => string | undefined;
|
||||
execute: (config: Config, flags: GlobalFlags) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface CommandSpec {
|
||||
description: string;
|
||||
/** See {@link Command.usageArgs} — argument portion only, no `<bin> <path>` prefix. */
|
||||
usageArgs?: string;
|
||||
options?: OptionDef[];
|
||||
/** See {@link Command.exampleArgs} — argument strings only, no `<bin> <path>` prefix. */
|
||||
exampleArgs?: string[];
|
||||
/** Credential this command requires. See {@link AuthRequirement}. */
|
||||
auth: AuthRequirement;
|
||||
notes?: string[];
|
||||
/** Cross-flag validation — see {@link Command.validate}. */
|
||||
validate?: (flags: GlobalFlags) => string | undefined;
|
||||
run: (config: Config, flags: GlobalFlags) => Promise<void>;
|
||||
}
|
||||
|
||||
export function defineCommand(spec: CommandSpec): Command {
|
||||
return {
|
||||
description: spec.description,
|
||||
usageArgs: spec.usageArgs,
|
||||
options: spec.options,
|
||||
exampleArgs: spec.exampleArgs,
|
||||
auth: spec.auth,
|
||||
notes: spec.notes,
|
||||
validate: spec.validate,
|
||||
execute: (config, flags) => spec.run(config, flags),
|
||||
};
|
||||
}
|
||||
|
||||
/** Global flags shared by all commands — drives the parser's type resolution. */
|
||||
export const GLOBAL_OPTIONS: OptionDef[] = [
|
||||
{ flag: "--api-key <key>", description: "API key" },
|
||||
{ flag: "--base-url <url>", description: "API base URL" },
|
||||
{ flag: "--output <format>", description: "Output format: text, json" },
|
||||
{ flag: "--timeout <seconds>", description: "Request timeout", type: "number" },
|
||||
{ flag: "--quiet", description: "Suppress non-essential output", type: "switch" },
|
||||
{ flag: "--verbose", description: "Print HTTP request/response details", type: "switch" },
|
||||
{ flag: "--no-color", description: "Disable ANSI colors", type: "switch" },
|
||||
{ flag: "--dry-run", description: "Dry run mode", type: "switch" },
|
||||
{ flag: "--non-interactive", description: "Disable interactive prompts", type: "switch" },
|
||||
{ flag: "--concurrent <n>", description: "Run N parallel requests (default: 1)", type: "number" },
|
||||
{
|
||||
flag: "--console-region <region>",
|
||||
// ── Global flags (single source: derived from GLOBAL_FLAGS) ──────────────────
|
||||
export const GLOBAL_FLAGS = {
|
||||
apiKey: { type: "string", valueHint: "<key>", description: "API key" },
|
||||
baseUrl: { type: "string", valueHint: "<url>", description: "API base URL" },
|
||||
output: { type: "string", valueHint: "<format>", description: "Output format: text, json" },
|
||||
timeout: { type: "number", valueHint: "<seconds>", description: "Request timeout" },
|
||||
concurrent: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Run N parallel requests (default: 1)",
|
||||
},
|
||||
quiet: { type: "switch", description: "Suppress non-essential output" },
|
||||
verbose: { type: "switch", description: "Print HTTP request/response details" },
|
||||
noColor: { type: "switch", description: "Disable ANSI colors" },
|
||||
dryRun: { type: "switch", description: "Dry run mode" },
|
||||
nonInteractive: { type: "switch", description: "Disable interactive prompts" },
|
||||
yes: { type: "switch", description: "Skip confirmation prompts" },
|
||||
async: { type: "switch", description: "Return async task id without waiting" },
|
||||
consoleRegion: {
|
||||
type: "string",
|
||||
valueHint: "<region>",
|
||||
description: "Console gateway region (e.g. cn-beijing, ap-southeast-1)",
|
||||
},
|
||||
{ flag: "--console-site <site>", description: "Console site: domestic, international" },
|
||||
{
|
||||
flag: "--console-switch-agent <uid>",
|
||||
description: "Switch agent UID for delegated access",
|
||||
type: "number",
|
||||
consoleSite: {
|
||||
type: "string",
|
||||
valueHint: "<site>",
|
||||
description: "Console site: domestic, international",
|
||||
},
|
||||
{ flag: "--help", description: "Show help", type: "switch" },
|
||||
{ flag: "--version", description: "Print version", type: "switch" },
|
||||
];
|
||||
consoleSwitchAgent: {
|
||||
type: "number",
|
||||
valueHint: "<uid>",
|
||||
description: "Switch agent UID for delegated access",
|
||||
},
|
||||
help: { type: "switch", description: "Show help" },
|
||||
version: { type: "switch", description: "Print version" },
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export type GlobalFlags = ParsedFlags<typeof GLOBAL_FLAGS>;
|
||||
/** A command's full flags: global + its own flags, inferred in one pass. */
|
||||
export type Flags<F extends FlagsDef> = ParsedFlags<typeof GLOBAL_FLAGS & F>;
|
||||
|
||||
// ── Command ──────────────────────────────────────────────────────────────────
|
||||
/**
|
||||
* A command. Generic over its flags `F` so `run`/`validate` receive precisely
|
||||
* typed flags (`Flags<F>` = global + own flags). Stored heterogeneously as
|
||||
* {@link AnyCommand}; the precise typing lives at the `defineCommand` call site.
|
||||
*/
|
||||
export interface Command<F extends FlagsDef = FlagsDef> {
|
||||
description: string;
|
||||
/** Credential this command requires. See {@link AuthRequirement}. */
|
||||
auth: AuthRequirement;
|
||||
/** Usage line arg portion, e.g. "--prompt <text> [flags]". Manually written. */
|
||||
usageArgs?: string;
|
||||
/** Example arg strings (without the `<bin> <path>` prefix). */
|
||||
exampleArgs?: string[];
|
||||
notes?: string[];
|
||||
flags?: F;
|
||||
/**
|
||||
* Cross-flag validation, after parsing and before run. Return an error message
|
||||
* → UsageError; undefined to pass. Single-flag `required` is enforced by the
|
||||
* parser — use this for rules spanning flags or depending on a flag's *value*.
|
||||
*/
|
||||
validate?: (flags: Flags<F>) => string | undefined;
|
||||
run: (config: Config, flags: Flags<F>) => Promise<void>;
|
||||
}
|
||||
|
||||
/** Type-erased command for heterogeneous storage (registry / context). */
|
||||
export type AnyCommand = Command<any>;
|
||||
|
||||
/** Identity wrapper whose only job is to infer `F` from `spec.flags`. */
|
||||
export function defineCommand<F extends FlagsDef>(spec: Command<F>): Command<F> {
|
||||
return spec;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
export interface GlobalFlags {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
output?: string;
|
||||
quiet: boolean;
|
||||
verbose: boolean;
|
||||
timeout?: number;
|
||||
noColor: boolean;
|
||||
yes: boolean;
|
||||
dryRun: boolean;
|
||||
nonInteractive: boolean;
|
||||
async: boolean;
|
||||
consoleRegion?: string;
|
||||
consoleSite?: string;
|
||||
consoleSwitchAgent?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
export type { Command, CommandSpec, OptionDef } from "./command.ts";
|
||||
export { defineCommand, GLOBAL_OPTIONS } from "./command.ts";
|
||||
export type { GlobalFlags } from "./flags.ts";
|
||||
export type {
|
||||
Command,
|
||||
AnyCommand,
|
||||
FlagDef,
|
||||
FlagsDef,
|
||||
ParsedFlags,
|
||||
Flags,
|
||||
GlobalFlags,
|
||||
} from "./command.ts";
|
||||
export { defineCommand, GLOBAL_FLAGS } from "./command.ts";
|
||||
export type {
|
||||
AppCompletionRequest,
|
||||
AppCompletionResponse,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createCli } from "bailian-cli-runtime";
|
||||
import type { Command } from "bailian-cli-core";
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import {
|
||||
authLogin,
|
||||
authStatus,
|
||||
@@ -24,7 +24,7 @@ import pkg from "../package.json" with { type: "json" };
|
||||
// remapped to a flat `rag retrieve` path. Routing is driven entirely by these
|
||||
// keys, and usage/examples/errors render the path from the key — so the same
|
||||
// shared command shows `rag retrieve` here and `bl knowledge retrieve` in bl.
|
||||
const commands: Record<string, Command> = {
|
||||
const commands: Record<string, AnyCommand> = {
|
||||
"auth login": authLogin,
|
||||
"auth status": authStatus,
|
||||
"auth logout": authLogout,
|
||||
|
||||
+47
-102
@@ -1,66 +1,13 @@
|
||||
import type { GlobalFlags } from "bailian-cli-core";
|
||||
import type { OptionDef } from "bailian-cli-core";
|
||||
import type { FlagsDef, ParsedFlags } from "bailian-cli-core";
|
||||
import { UsageError } from "bailian-cli-core";
|
||||
|
||||
function kebabToCamel(str: string): string {
|
||||
return str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
||||
}
|
||||
|
||||
/** Extract camelCase flag name from an OptionDef.flag string, e.g. '--max-tokens <n>' → 'maxTokens' */
|
||||
function flagKey(def: OptionDef): string | null {
|
||||
const m = def.flag.match(/^--([a-z][a-z0-9-]*)/i);
|
||||
return m ? kebabToCamel(m[1]!) : null;
|
||||
}
|
||||
|
||||
interface FlagSchema {
|
||||
switches: Set<string>;
|
||||
booleans: Set<string>;
|
||||
numbers: Set<string>;
|
||||
arrays: Set<string>;
|
||||
}
|
||||
|
||||
function buildAllowedFlagKeys(options: OptionDef[]): Set<string> {
|
||||
const keys = new Set<string>();
|
||||
for (const opt of options) {
|
||||
const key = flagKey(opt);
|
||||
if (key) keys.add(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify each option by its declared (or inferred) type. Inference: a flag
|
||||
* with a `<…>`/`[…]` placeholder defaults to string, otherwise to switch.
|
||||
* Strings are the default bucket and need no set.
|
||||
*/
|
||||
function buildSchema(options: OptionDef[]): FlagSchema {
|
||||
const switches = new Set<string>();
|
||||
const booleans = new Set<string>();
|
||||
const numbers = new Set<string>();
|
||||
const arrays = new Set<string>();
|
||||
for (const opt of options) {
|
||||
const key = flagKey(opt);
|
||||
if (!key) continue;
|
||||
switch (opt.type) {
|
||||
case "switch":
|
||||
switches.add(key);
|
||||
break;
|
||||
case "boolean":
|
||||
booleans.add(key);
|
||||
break;
|
||||
case "number":
|
||||
numbers.add(key);
|
||||
break;
|
||||
case "array":
|
||||
arrays.add(key);
|
||||
break;
|
||||
case "string":
|
||||
break;
|
||||
default:
|
||||
if (!opt.flag.includes("<") && !opt.flag.includes("[")) switches.add(key);
|
||||
}
|
||||
}
|
||||
return { switches, booleans, numbers, arrays };
|
||||
/** maxTokens → max-tokens. For rendering flags in help / error messages. */
|
||||
export function camelToKebab(str: string): string {
|
||||
return str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
||||
}
|
||||
|
||||
export interface ParsePathResult {
|
||||
@@ -92,22 +39,15 @@ export function parsePath(argv: string[]): ParsePathResult {
|
||||
|
||||
/**
|
||||
* Second pass — parse the flag region into typed values, driven entirely by the
|
||||
* OptionDef schema. Pure: returns typed flags or throws UsageError — never
|
||||
* prints/exits. The runtime's error boundary decides rendering.
|
||||
* keyed FlagsDef (key = camelCase flag name). Pure: returns typed flags or
|
||||
* throws UsageError — never prints/exits. The error boundary decides rendering.
|
||||
*/
|
||||
export function parseFlags(rest: string[], options: OptionDef[]): GlobalFlags {
|
||||
const allowedKeys = buildAllowedFlagKeys(options);
|
||||
const schema = buildSchema(options);
|
||||
export function parseFlags<F extends FlagsDef>(rest: string[], defs: F): ParsedFlags<F> {
|
||||
const flags: Record<string, unknown> = {};
|
||||
const seen = new Set<string>();
|
||||
const flags: GlobalFlags = {
|
||||
quiet: false,
|
||||
verbose: false,
|
||||
noColor: false,
|
||||
yes: false,
|
||||
dryRun: false,
|
||||
nonInteractive: false,
|
||||
async: false,
|
||||
};
|
||||
for (const [key, def] of Object.entries(defs)) {
|
||||
if (def.type === "switch") flags[key] = false;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
while (i < rest.length) {
|
||||
@@ -121,22 +61,23 @@ export function parseFlags(rest: string[], options: OptionDef[]): GlobalFlags {
|
||||
}
|
||||
|
||||
const eqIdx = arg.indexOf("=");
|
||||
const key = eqIdx !== -1 ? arg.slice(2, eqIdx) : arg.slice(2);
|
||||
const rawKey = eqIdx !== -1 ? arg.slice(2, eqIdx) : arg.slice(2);
|
||||
let value: string | undefined = eqIdx !== -1 ? arg.slice(eqIdx + 1) : undefined;
|
||||
|
||||
if (key === "") {
|
||||
if (rawKey === "") {
|
||||
throw new UsageError(`Unknown flag "${arg}".`);
|
||||
}
|
||||
const camelKey = kebabToCamel(key);
|
||||
if (!allowedKeys.has(camelKey)) {
|
||||
throw new UsageError(`Unknown flag "--${key}". Run with --help to see available options.`);
|
||||
const key = kebabToCamel(rawKey);
|
||||
const def = defs[key];
|
||||
if (!def) {
|
||||
throw new UsageError(`Unknown flag "--${rawKey}". Run with --help to see available options.`);
|
||||
}
|
||||
|
||||
if (schema.switches.has(camelKey)) {
|
||||
if (def.type === "switch") {
|
||||
if (value !== undefined) {
|
||||
throw new UsageError(`Flag --${key} is a switch and takes no value.`);
|
||||
throw new UsageError(`Flag --${rawKey} is a switch and takes no value.`);
|
||||
}
|
||||
(flags as Record<string, unknown>)[camelKey] = true;
|
||||
flags[key] = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
@@ -144,7 +85,7 @@ export function parseFlags(rest: string[], options: OptionDef[]): GlobalFlags {
|
||||
if (value === undefined) {
|
||||
const next = rest[i + 1];
|
||||
if (next === undefined || next.startsWith("--")) {
|
||||
throw new UsageError(`Flag --${key} requires a value.`);
|
||||
throw new UsageError(`Flag --${rawKey} requires a value.`);
|
||||
}
|
||||
value = next;
|
||||
i += 2;
|
||||
@@ -152,45 +93,49 @@ export function parseFlags(rest: string[], options: OptionDef[]): GlobalFlags {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (schema.arrays.has(camelKey)) {
|
||||
const arr = (flags as Record<string, unknown>)[camelKey] as string[] | undefined;
|
||||
if (def.choices && !def.choices.includes(value)) {
|
||||
throw new UsageError(`Flag --${rawKey} must be one of: ${def.choices.join(", ")}.`);
|
||||
}
|
||||
|
||||
if (def.type === "array") {
|
||||
const arr = flags[key] as string[] | undefined;
|
||||
if (arr) arr.push(value);
|
||||
else (flags as Record<string, unknown>)[camelKey] = [value];
|
||||
else flags[key] = [value];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seen.has(camelKey)) {
|
||||
throw new UsageError(`Flag --${key} given more than once.`);
|
||||
if (seen.has(key)) {
|
||||
throw new UsageError(`Flag --${rawKey} given more than once.`);
|
||||
}
|
||||
seen.add(camelKey);
|
||||
seen.add(key);
|
||||
|
||||
if (schema.numbers.has(camelKey)) {
|
||||
if (def.type === "number") {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n)) {
|
||||
throw new UsageError(`Flag --${key} requires a finite number.`);
|
||||
throw new UsageError(`Flag --${rawKey} requires a finite number.`);
|
||||
}
|
||||
(flags as Record<string, unknown>)[camelKey] = n;
|
||||
} else if (schema.booleans.has(camelKey)) {
|
||||
flags[key] = n;
|
||||
} else if (def.type === "boolean") {
|
||||
const v = value.trim().toLowerCase();
|
||||
if (v === "true") (flags as Record<string, unknown>)[camelKey] = true;
|
||||
else if (v === "false") (flags as Record<string, unknown>)[camelKey] = false;
|
||||
else throw new UsageError(`Flag --${key} requires true or false.`);
|
||||
if (v === "true") flags[key] = true;
|
||||
else if (v === "false") flags[key] = false;
|
||||
else throw new UsageError(`Flag --${rawKey} requires true or false.`);
|
||||
} else {
|
||||
(flags as Record<string, unknown>)[camelKey] = value;
|
||||
flags[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
const missing = options.filter((opt) => {
|
||||
if (!opt.required) return false;
|
||||
const key = flagKey(opt);
|
||||
return key !== null && (flags as Record<string, unknown>)[key] === undefined;
|
||||
});
|
||||
// Required enforcement — declarative, driven by the schema.
|
||||
const missing = Object.entries(defs)
|
||||
.filter(
|
||||
([key, def]) => def.type !== "switch" && def.required === true && flags[key] === undefined,
|
||||
)
|
||||
.map(([key]) => `--${camelToKebab(key)}`);
|
||||
if (missing.length > 0) {
|
||||
const names = missing.map((opt) => opt.flag.match(/^(--[a-z][a-z0-9-]*)/i)?.[1] ?? opt.flag);
|
||||
throw new UsageError(
|
||||
`Missing required ${names.length > 1 ? "flags" : "flag"}: ${names.join(", ")}`,
|
||||
`Missing required ${missing.length > 1 ? "flags" : "flag"}: ${missing.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
return flags as unknown as ParsedFlags<F>;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
runCommandStage,
|
||||
type RunContext,
|
||||
} from "./middleware.ts";
|
||||
import type { Command, Config, GlobalFlags } from "bailian-cli-core";
|
||||
import { GLOBAL_OPTIONS, UsageError, loadConfig, flushTelemetry } from "bailian-cli-core";
|
||||
import type { AnyCommand, Config, GlobalFlags } from "bailian-cli-core";
|
||||
import { GLOBAL_FLAGS, UsageError, loadConfig, flushTelemetry } from "bailian-cli-core";
|
||||
import { setupProxyFromEnv } from "./proxy.ts";
|
||||
import { handleError } from "./error-handler.ts";
|
||||
import { printWelcomeBanner, printQuickStart } from "./output/banner.ts";
|
||||
@@ -36,7 +36,7 @@ export interface Cli {
|
||||
* its own commands + identity. `run` resolves argv into a {@link Resolution},
|
||||
* then dispatches it.
|
||||
*/
|
||||
export function createCli(commands: Record<string, Command>, opts: CliOptions): Cli {
|
||||
export function createCli(commands: Record<string, AnyCommand>, opts: CliOptions): Cli {
|
||||
const registry = new CommandRegistry(commands, opts.binName);
|
||||
const clientName = opts.clientName ?? opts.binName;
|
||||
const { binName, version, npmPackage } = opts;
|
||||
@@ -77,7 +77,7 @@ export function createCli(commands: Record<string, Command>, opts: CliOptions):
|
||||
|
||||
let hasKey = false;
|
||||
try {
|
||||
const config = buildConfig(parseFlags(argv, GLOBAL_OPTIONS));
|
||||
const config = buildConfig(parseFlags(argv, GLOBAL_FLAGS));
|
||||
hasKey = !!(
|
||||
config.apiKey ||
|
||||
config.fileApiKey ||
|
||||
@@ -109,8 +109,11 @@ export function createCli(commands: Record<string, Command>, opts: CliOptions):
|
||||
|
||||
case "run": {
|
||||
try {
|
||||
// 解析 flag + 跨 flag 校验:任何用法问题都抛 UsageError
|
||||
const flags = parseFlags(res.rest, [...GLOBAL_OPTIONS, ...(res.command.options ?? [])]);
|
||||
// 解析 flag + 跨 flag 校验:任何用法问题都抛 UsageError。
|
||||
const flags = parseFlags(res.rest, {
|
||||
...GLOBAL_FLAGS,
|
||||
...res.command.flags,
|
||||
}) as GlobalFlags;
|
||||
const invalid = res.command.validate?.(flags);
|
||||
if (invalid) throw new UsageError(invalid);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export type { Cli, CliOptions } from "./create-cli.ts";
|
||||
|
||||
// Command routing
|
||||
export { CommandRegistry } from "./registry.ts";
|
||||
export type { Command, OptionDef, LocateResult } from "./registry.ts";
|
||||
export type { Command, FlagDef, LocateResult } from "./registry.ts";
|
||||
export { resolve } from "./resolve.ts";
|
||||
export type { Resolution } from "./resolve.ts";
|
||||
export { compose, type RunContext, type Middleware } from "./middleware.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Command, Config, GlobalFlags } from "bailian-cli-core";
|
||||
import type { AnyCommand, Config, GlobalFlags } from "bailian-cli-core";
|
||||
import { resolveCredential, trackCommandExecution } from "bailian-cli-core";
|
||||
import { ensureApiKey } from "./utils/ensure-key.ts";
|
||||
import { maybeShowStatusBar } from "./output/status-bar.ts";
|
||||
@@ -16,7 +16,7 @@ export interface RunContext {
|
||||
readonly npmPackage: string;
|
||||
/** The matched command path, e.g. ["speech","recognize"]. */
|
||||
readonly path: string[];
|
||||
readonly command: Command;
|
||||
readonly command: AnyCommand;
|
||||
config: Config;
|
||||
flags: GlobalFlags;
|
||||
}
|
||||
@@ -80,4 +80,4 @@ export const versionCheckStage: Middleware = async (ctx, next) => {
|
||||
};
|
||||
|
||||
/** Innermost stage: hand control to the command. */
|
||||
export const runCommandStage: Middleware = (ctx) => ctx.command.execute(ctx.config, ctx.flags);
|
||||
export const runCommandStage: Middleware = (ctx) => ctx.command.run(ctx.config, ctx.flags);
|
||||
|
||||
@@ -9,6 +9,7 @@ const PIPELINE_FLAGS: GlobalFlags = {
|
||||
yes: false,
|
||||
dryRun: false,
|
||||
help: false,
|
||||
version: false,
|
||||
async: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import type { Command } from "bailian-cli-core";
|
||||
import type { AnyCommand, FlagDef } from "bailian-cli-core";
|
||||
import { UsageError } from "bailian-cli-core";
|
||||
import { GLOBAL_OPTIONS } from "bailian-cli-core";
|
||||
import { GLOBAL_FLAGS } from "bailian-cli-core";
|
||||
import { camelToKebab } from "./args.ts";
|
||||
|
||||
export type { Command, OptionDef } from "bailian-cli-core";
|
||||
export type { Command, AnyCommand, FlagDef, FlagsDef } from "bailian-cli-core";
|
||||
|
||||
/** "--max-tokens <count>" for a value flag, "--quiet" for a switch. */
|
||||
function flagDisplay(key: string, def: FlagDef): string {
|
||||
const flag = `--${camelToKebab(key)}`;
|
||||
if (def.type === "switch") return flag;
|
||||
const hint = def.choices ? `<${def.choices.join("|")}>` : def.valueHint;
|
||||
return `${flag} ${hint}`;
|
||||
}
|
||||
|
||||
interface CommandNode {
|
||||
command?: Command;
|
||||
command?: AnyCommand;
|
||||
children: Map<string, CommandNode>;
|
||||
}
|
||||
|
||||
@@ -18,7 +27,7 @@ interface CommandNode {
|
||||
* tokens (no positionals); `error` carries the message + hint.
|
||||
*/
|
||||
export type LocateResult =
|
||||
| { kind: "leaf"; command: Command; matched: string[] }
|
||||
| { kind: "leaf"; command: AnyCommand; matched: string[] }
|
||||
| { kind: "group"; matched: string[] }
|
||||
| { kind: "unknown"; error: UsageError };
|
||||
|
||||
@@ -27,14 +36,14 @@ export class CommandRegistry {
|
||||
/** Binary name shown in usage/help/error strings (e.g. "bl", "rag"). */
|
||||
private readonly cliName: string;
|
||||
|
||||
constructor(commands: Record<string, Command>, cliName: string) {
|
||||
constructor(commands: Record<string, AnyCommand>, cliName: string) {
|
||||
this.cliName = cliName;
|
||||
for (const [path, cmd] of Object.entries(commands)) {
|
||||
this.register(path, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
private register(path: string, command: Command): void {
|
||||
private register(path: string, command: AnyCommand): void {
|
||||
const parts = path.split(" ");
|
||||
let node = this.root;
|
||||
for (const part of parts) {
|
||||
@@ -46,8 +55,8 @@ export class CommandRegistry {
|
||||
node.command = command;
|
||||
}
|
||||
|
||||
getAllCommands(): Command[] {
|
||||
const commands: Command[] = [];
|
||||
getAllCommands(): AnyCommand[] {
|
||||
const commands: AnyCommand[] = [];
|
||||
const traverse = (node: CommandNode) => {
|
||||
if (node.command) commands.push(node.command);
|
||||
for (const child of node.children.values()) {
|
||||
@@ -153,10 +162,12 @@ export class CommandRegistry {
|
||||
}
|
||||
|
||||
private buildGlobalFlagLines(a: (s: string) => string, d: (s: string) => string): string {
|
||||
const maxLen = Math.max(...GLOBAL_OPTIONS.map((o) => o.flag.length));
|
||||
return GLOBAL_OPTIONS.map((o) => ` ${a(o.flag.padEnd(maxLen + 2))} ${d(o.description)}`).join(
|
||||
"\n",
|
||||
);
|
||||
const lines = Object.entries(GLOBAL_FLAGS).map(([k, def]) => ({
|
||||
flag: flagDisplay(k, def),
|
||||
desc: def.description,
|
||||
}));
|
||||
const maxLen = Math.max(...lines.map((l) => l.flag.length));
|
||||
return lines.map((l) => ` ${a(l.flag.padEnd(maxLen + 2))} ${d(l.desc)}`).join("\n");
|
||||
}
|
||||
|
||||
// Color helpers — no-ops when output is not a TTY
|
||||
@@ -256,12 +267,12 @@ ${b("Global Flags:")}
|
||||
${globalFlagLines}
|
||||
|
||||
${b("Getting Help:")}
|
||||
${d("Add --help after any command to see its full list of options, defaults,")}
|
||||
${d("Add --help after any command to see its full list of flags, defaults,")}
|
||||
${d("and usage examples. For example:")} ${this.cliName} ${this.helpExample()} --help
|
||||
`);
|
||||
}
|
||||
|
||||
private printCommandHelp(cmd: Command, commandPath: string[], out: NodeJS.WriteStream): void {
|
||||
private printCommandHelp(cmd: AnyCommand, commandPath: string[], out: NodeJS.WriteStream): void {
|
||||
const b = (s: string) => this.bold(s, out);
|
||||
const a = (s: string) => this.accent(s, out);
|
||||
const d = (s: string) => this.dim(s, out);
|
||||
@@ -272,11 +283,16 @@ ${b("Getting Help:")}
|
||||
|
||||
out.write(`\n${cmd.description}\n`);
|
||||
out.write(`${b("Usage:")} ${prefix}${cmd.usageArgs ? ` ${cmd.usageArgs}` : ""}\n`);
|
||||
if (cmd.options && cmd.options.length > 0) {
|
||||
const maxLen = Math.max(...cmd.options.map((o) => o.flag.length));
|
||||
out.write(`\n${b("Options:")}\n`);
|
||||
for (const opt of cmd.options) {
|
||||
out.write(` ${a(opt.flag.padEnd(maxLen + 2))} ${d(opt.description)}\n`);
|
||||
const flagEntries = Object.entries(cmd.flags ?? {}) as [string, FlagDef][];
|
||||
if (flagEntries.length > 0) {
|
||||
const lines = flagEntries.map(([k, def]) => ({
|
||||
flag: flagDisplay(k, def),
|
||||
desc: def.description,
|
||||
}));
|
||||
const maxLen = Math.max(...lines.map((l) => l.flag.length));
|
||||
out.write(`\n${b("Flags:")}\n`);
|
||||
for (const l of lines) {
|
||||
out.write(` ${a(l.flag.padEnd(maxLen + 2))} ${d(l.desc)}\n`);
|
||||
}
|
||||
}
|
||||
if (cmd.notes && cmd.notes.length > 0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Command } from "bailian-cli-core";
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import { type UsageError } from "bailian-cli-core";
|
||||
import type { CommandRegistry } from "./registry.ts";
|
||||
import { parsePath } from "./args.ts";
|
||||
@@ -15,7 +15,7 @@ import { parsePath } from "./args.ts";
|
||||
export type Resolution =
|
||||
| { kind: "version" }
|
||||
| { kind: "help"; path: string[] }
|
||||
| { kind: "run"; path: string[]; command: Command; rest: string[] }
|
||||
| { kind: "run"; path: string[]; command: AnyCommand; rest: string[] }
|
||||
| { kind: "usageError"; error: UsageError };
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { expect, test } from "vite-plus/test";
|
||||
import { ExitCode, GLOBAL_OPTIONS, type OptionDef } from "bailian-cli-core";
|
||||
import { ExitCode, GLOBAL_FLAGS, type FlagsDef } from "bailian-cli-core";
|
||||
import { parsePath, parseFlags } from "../src/args.ts";
|
||||
|
||||
const IMAGE_GENERATE_OPTIONS: OptionDef[] = [
|
||||
{ flag: "--prompt <text>", description: "Image description", required: true },
|
||||
{ flag: "--model <model>", description: "Model ID" },
|
||||
{ flag: "--image <url>", description: "Image URL (repeatable)", type: "array" },
|
||||
{ flag: "--n <count>", description: "Number of images", type: "number" },
|
||||
{ flag: "--watermark <bool>", description: "Watermark", type: "boolean" },
|
||||
{ flag: "--no-wait", description: "Return immediately", type: "switch" },
|
||||
];
|
||||
const OPTS = [...GLOBAL_OPTIONS, ...IMAGE_GENERATE_OPTIONS];
|
||||
const IMAGE_GENERATE_FLAGS = {
|
||||
prompt: { type: "string", valueHint: "<text>", description: "Image description", required: true },
|
||||
model: { type: "string", valueHint: "<model>", description: "Model ID" },
|
||||
image: { type: "array", valueHint: "<url>", description: "Image URL (repeatable)" },
|
||||
n: { type: "number", valueHint: "<count>", description: "Number of images" },
|
||||
watermark: { type: "boolean", valueHint: "<bool>", description: "Watermark" },
|
||||
noWait: { type: "switch", description: "Return immediately" },
|
||||
} satisfies FlagsDef;
|
||||
const OPTS = { ...GLOBAL_FLAGS, ...IMAGE_GENERATE_FLAGS };
|
||||
|
||||
// ---- parsePath: routing only (command path first, then flags) ----
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) |
|
||||
| **Usage** | `bl advisor recommend --message <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | -------------------------- |
|
||||
|
||||
@@ -22,20 +22,20 @@ Index: [index.md](index.md)
|
||||
| **Description** | Call a Bailian application (agent or workflow) |
|
||||
| **Usage** | `bl app call --app-id <id> --prompt <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------- | -------- | --------------------------------------------- |
|
||||
| `--app-id <id>` | string | yes | Application ID (required) |
|
||||
| `--prompt <text>` | string | yes | Input prompt text |
|
||||
| `--image <url>` | array | no | Image URL(s) to pass to the app (repeatable) |
|
||||
| `--file-id <id>` | array | no | Pre-uploaded file ID(s) (repeatable) |
|
||||
| `--session-id <id>` | string | no | Session ID for multi-turn conversation |
|
||||
| `--stream` | boolean | no | Stream response (default: on in TTY) |
|
||||
| `--pipeline-ids <ids>` | string | no | Knowledge base pipeline IDs (comma-separated) |
|
||||
| `--memory-id <id>` | string | no | Memory ID for long-term memory |
|
||||
| `--biz-params <json>` | string | no | Business parameters JSON (workflow variables) |
|
||||
| `--has-thoughts` | boolean | no | Show agent thinking process |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | --------------------------------------------- |
|
||||
| `--app-id <id>` | string | yes | Application ID (required) |
|
||||
| `--prompt <text>` | string | yes | Input prompt text |
|
||||
| `--image <url>` | array | no | Image URL(s) to pass to the app (repeatable) |
|
||||
| `--file-id <id>` | array | no | Pre-uploaded file ID(s) (repeatable) |
|
||||
| `--session-id <id>` | string | no | Session ID for multi-turn conversation |
|
||||
| `--stream` | switch | no | Stream response (default: on in TTY) |
|
||||
| `--pipeline-ids <ids>` | string | no | Knowledge base pipeline IDs (comma-separated) |
|
||||
| `--memory-id <id>` | string | no | Memory ID for long-term memory |
|
||||
| `--biz-params <json>` | string | no | Business parameters JSON (workflow variables) |
|
||||
| `--has-thoughts` | switch | no | Show agent thinking process |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -71,7 +71,7 @@ bl app call --app-id abc123 --prompt "Start" --biz-params '{"key":"value"}'
|
||||
| **Description** | List Bailian applications |
|
||||
| **Usage** | `bl app list [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------- |
|
||||
|
||||
@@ -23,13 +23,13 @@ Index: [index.md](index.md)
|
||||
| **Description** | Authenticate with API key or console browser login (credentials can coexist) |
|
||||
| **Usage** | `bl auth login --api-key <key> \| --console` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------- | -------- | ------------------------------------------------------------------------------------- |
|
||||
| `--api-key <key>` | string | no | DashScope API key to store |
|
||||
| `--base-url <url>` | string | no | DashScope API base URL (used with --api-key for validation) |
|
||||
| `--console` | boolean | no | Sign in via browser; use --console-site to choose domestic (default) or international |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------- |
|
||||
| `--api-key <key>` | string | no | DashScope API key to store |
|
||||
| `--base-url <url>` | string | no | DashScope API base URL (used with --api-key for validation) |
|
||||
| `--console` | switch | no | Sign in via browser; use --console-site to choose domestic (default) or international |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -49,12 +49,12 @@ bl auth login --console
|
||||
| **Description** | Clear stored credentials |
|
||||
| **Usage** | `bl auth logout [--console] [--yes] [--dry-run]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------- | ------- | -------- | -------------------------------------------------------- |
|
||||
| `--console` | boolean | no | Only clear the console access_token, keep api_key intact |
|
||||
| `--yes` | boolean | no | Skip confirmation prompt |
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------- | ------ | -------- | -------------------------------------------------------- |
|
||||
| `--console` | switch | no | Only clear the console access_token, keep api_key intact |
|
||||
| `--yes` | switch | no | Skip confirmation prompt |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -82,7 +82,7 @@ bl auth logout --yes
|
||||
| **Description** | Show current authentication state |
|
||||
| **Usage** | `bl auth status` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------- |
|
||||
|
||||
@@ -22,7 +22,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Set a config value |
|
||||
| **Usage** | `bl config set --key <key> --value <value>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -51,9 +51,9 @@ bl config set --key base_url --value https://dashscope.aliyuncs.com
|
||||
| **Description** | Display current configuration |
|
||||
| **Usage** | `bl config show` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
_No command-specific options._
|
||||
_No command-specific flags._
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Call a Bailian console API via the CLI gateway |
|
||||
| **Usage** | `bl console call --api <api> --data <json> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------ |
|
||||
|
||||
@@ -21,7 +21,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Upload a local file to DashScope temporary storage (48h) |
|
||||
| **Usage** | `bl file upload --file <path> --model <model>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | ----------------------------------------------- |
|
||||
|
||||
@@ -22,7 +22,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Edit an existing image with text instructions (Qwen-Image) |
|
||||
| **Usage** | `bl image edit --image <url> --prompt <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------- | -------- | ----------------------------------------------------------------------- |
|
||||
@@ -68,7 +68,7 @@ bl image edit --image ./photo.png --prompt "Replace the background with a beach"
|
||||
| **Description** | Generate images (Qwen-Image / wan2.x) |
|
||||
| **Usage** | `bl image generate --prompt <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
@@ -80,7 +80,7 @@ bl image edit --image ./photo.png --prompt "Replace the background with a beach"
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag: true for qwen-image sync; parameter omitted on async models (API default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--no-wait` | boolean | no | Return task ID immediately without waiting (async models only) |
|
||||
| `--no-wait` | switch | no | Return task ID immediately without waiting (async models only) |
|
||||
| `--out-dir <dir>` | string | no | Download images to directory |
|
||||
| `--out-prefix <prefix>` | string | no | Filename prefix (default: image) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 3) |
|
||||
|
||||
@@ -84,7 +84,7 @@ Use this index for the full quick index and global flags.
|
||||
|
||||
## Global flags
|
||||
|
||||
Available on every command (in addition to command-specific options):
|
||||
Available on every command (in addition to command-specific flags):
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||
@@ -92,12 +92,14 @@ Available on every command (in addition to command-specific options):
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| `--output <format>` | string | no | Output format: text, json |
|
||||
| `--timeout <seconds>` | number | no | Request timeout |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--quiet` | switch | no | Suppress non-essential output |
|
||||
| `--verbose` | switch | no | Print HTTP request/response details |
|
||||
| `--no-color` | switch | no | Disable ANSI colors |
|
||||
| `--dry-run` | switch | no | Dry run mode |
|
||||
| `--non-interactive` | switch | no | Disable interactive prompts |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--yes` | switch | no | Skip confirmation prompts |
|
||||
| `--async` | switch | no | Return async task id without waiting |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
|
||||
@@ -21,23 +21,23 @@ Index: [index.md](index.md)
|
||||
| **Description** | Retrieve from a Bailian knowledge base |
|
||||
| **Usage** | `bl knowledge retrieve --index-id <id> --query <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------- | ------- | -------- | ------------------------------------------------------------ |
|
||||
| `--index-id <id>` | string | yes | Knowledge base index ID (required) |
|
||||
| `--query <text>` | string | yes | Search query (required) |
|
||||
| `--dense-similarity-top-k <n>` | number | no | Dense retrieval top K |
|
||||
| `--sparse-similarity-top-k <n>` | number | no | Sparse retrieval top K |
|
||||
| `--rerank` | boolean | no | Enable reranking |
|
||||
| `--rerank-top-n <n>` | number | no | Rerank top N results |
|
||||
| `--rerank-model <name>` | string | no | Rerank model, e.g. qwen3-rerank-hybrid |
|
||||
| `--rerank-mode <mode>` | string | no | Rerank mode: qa, similar, or custom |
|
||||
| `--rerank-instruct <text>` | string | no | Custom rerank instruction, when mode=custom |
|
||||
| `--top-k <n>` | number | no | Number of results (deprecated, use --rerank-top-n) |
|
||||
| `--workspace-id <id>` | string | no | Bailian workspace ID (only needed for deprecated AK/SK auth) |
|
||||
| `--access-key-id <key>` | string | no | Deprecated: use global --api-key instead |
|
||||
| `--access-key-secret <key>` | string | no | Deprecated: use global --api-key instead |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------- | ------ | -------- | ------------------------------------------------------------ |
|
||||
| `--index-id <id>` | string | yes | Knowledge base index ID (required) |
|
||||
| `--query <text>` | string | yes | Search query (required) |
|
||||
| `--dense-similarity-top-k <n>` | number | no | Dense retrieval top K |
|
||||
| `--sparse-similarity-top-k <n>` | number | no | Sparse retrieval top K |
|
||||
| `--rerank` | switch | no | Enable reranking |
|
||||
| `--rerank-top-n <n>` | number | no | Rerank top N results |
|
||||
| `--rerank-model <name>` | string | no | Rerank model, e.g. qwen3-rerank-hybrid |
|
||||
| `--rerank-mode <mode>` | string | no | Rerank mode: qa, similar, or custom |
|
||||
| `--rerank-instruct <text>` | string | no | Custom rerank instruction, when mode=custom |
|
||||
| `--top-k <n>` | number | no | Number of results (deprecated, use --rerank-top-n) |
|
||||
| `--workspace-id <id>` | string | no | Bailian workspace ID (only needed for deprecated AK/SK auth) |
|
||||
| `--access-key-id <key>` | string | no | Deprecated: use global --api-key instead |
|
||||
| `--access-key-secret <key>` | string | no | Deprecated: use global --api-key instead |
|
||||
|
||||
#### Notes
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Call a tool on an MCP server (tools/call) |
|
||||
| **Usage** | `bl mcp call --target <server.tool> [--arg k=v ...] [--json '{...}'] [--url <url>]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------- |
|
||||
@@ -55,7 +55,7 @@ bl mcp call --target market-cmapi00073529.SmartFundSelection --arg riskLevel=R3
|
||||
| **Description** | List MCP servers activated under your Bailian account |
|
||||
| **Usage** | `bl mcp list [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------- |
|
||||
@@ -89,7 +89,7 @@ bl mcp list --output json
|
||||
| **Description** | List tools exposed by an MCP server (tools/list) |
|
||||
| **Usage** | `bl mcp tools --server <code> [--url <url>]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | ------------------------------------------------------- |
|
||||
|
||||
@@ -27,7 +27,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Add memory from messages or custom content |
|
||||
| **Usage** | `bl memory add --user-id <id> [--messages <json>] [--content <text>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------ | -------- | ---------------------------------------------------------- |
|
||||
@@ -59,7 +59,7 @@ bl memory add --user-id user1 --content "Lives in Beijing" --profile-schema sche
|
||||
| **Description** | Delete a memory node |
|
||||
| **Usage** | `bl memory delete --node-id <id> --user-id <id>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------ | -------- | --------------------------------------- |
|
||||
@@ -81,7 +81,7 @@ bl memory delete --node-id node_xxx --user-id user1
|
||||
| **Description** | List memory nodes for a user |
|
||||
| **Usage** | `bl memory list --user-id <id> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------ | -------- | ------------------------------ |
|
||||
@@ -108,7 +108,7 @@ bl memory list --user-id user1 --page-size 20 --page 2
|
||||
| **Description** | Create a user profile schema for memory profiling |
|
||||
| **Usage** | `bl memory profile create --name <name> --attributes <json> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | ----------------------------------------------------------- |
|
||||
@@ -130,7 +130,7 @@ bl memory profile create --name "user_basic" --attributes '[{"name":"age","descr
|
||||
| **Description** | Get user profile by schema ID and user ID |
|
||||
| **Usage** | `bl memory profile get --schema-id <id> --user-id <id>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | ---------------------------- |
|
||||
@@ -151,7 +151,7 @@ bl memory profile get --schema-id schema_xxx --user-id user1
|
||||
| **Description** | Search memory nodes by query or messages |
|
||||
| **Usage** | `bl memory search --user-id <id> [--query <text>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------ | -------- | -------------------------------------------- |
|
||||
@@ -179,7 +179,7 @@ bl memory search --user-id user1 --messages '[{"role":"user","content":"recommen
|
||||
| **Description** | Update a memory node content |
|
||||
| **Usage** | `bl memory update --node-id <id> --user-id <id> --content <text>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------- | ------ | -------- | ------------------------------------------ |
|
||||
|
||||
@@ -21,22 +21,22 @@ Index: [index.md](index.md)
|
||||
| **Description** | Multimodal chat with text + audio output (Qwen-Omni) |
|
||||
| **Usage** | `bl omni --message <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------- | -------- | ------------------------------------------------------------------------------------ |
|
||||
| `--message <text>` | array | yes | Message text (repeatable, prefix role: to set role) |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen3.5-omni-plus) |
|
||||
| `--system <text>` | string | no | System prompt |
|
||||
| `--image <url>` | array | no | Image URL or local file (repeatable) |
|
||||
| `--audio <url>` | array | no | Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp) |
|
||||
| `--video <url>` | array | no | Video file URL / local path, or comma-separated frame URLs |
|
||||
| `--voice <voice>` | string | no | Output voice (default: Cherry). Options: Chelsie, Cherry, Ethan, Serena, Sunny, Tina |
|
||||
| `--audio-format <fmt>` | string | no | Audio output format (default: wav) |
|
||||
| `--audio-out <path>` | string | no | Save audio to file (default: auto-generate) |
|
||||
| `--text-only` | boolean | no | Output text only, no audio generation |
|
||||
| `--max-tokens <n>` | number | no | Maximum tokens to generate |
|
||||
| `--temperature <n>` | number | no | Sampling temperature (0.0, 2.0] |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | ------------------------------------------------------------------------------------ |
|
||||
| `--message <text>` | array | yes | Message text (repeatable, prefix role: to set role) |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen3.5-omni-plus) |
|
||||
| `--system <text>` | string | no | System prompt |
|
||||
| `--image <url>` | array | no | Image URL or local file (repeatable) |
|
||||
| `--audio <url>` | array | no | Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp) |
|
||||
| `--video <url>` | array | no | Video file URL / local path, or comma-separated frame URLs |
|
||||
| `--voice <voice>` | string | no | Output voice (default: Cherry). Options: Chelsie, Cherry, Ethan, Serena, Sunny, Tina |
|
||||
| `--audio-format <fmt>` | string | no | Audio output format (default: wav) |
|
||||
| `--audio-out <path>` | string | no | Save audio to file (default: auto-generate) |
|
||||
| `--text-only` | switch | no | Output text only, no audio generation |
|
||||
| `--max-tokens <n>` | number | no | Maximum tokens to generate |
|
||||
| `--temperature <n>` | number | no | Sampling temperature (0.0, 2.0] |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Run a pipeline workflow definition |
|
||||
| **Usage** | `bl pipeline run --file <path> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------ |
|
||||
@@ -63,7 +63,7 @@ bl pipeline run --file workflow.yaml --output json
|
||||
| **Description** | Validate a pipeline definition without executing |
|
||||
| **Usage** | `bl pipeline validate --file <path>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------- | ------ | -------- | ------------------------------------ |
|
||||
|
||||
@@ -24,7 +24,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Check current usage against rate limits |
|
||||
| **Usage** | `bl quota check [--model <model>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ----------------------------------------------- |
|
||||
@@ -64,7 +64,7 @@ bl quota check --output json
|
||||
| **Description** | View quota change history |
|
||||
| **Usage** | `bl quota history [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------- |
|
||||
@@ -105,15 +105,15 @@ bl quota history --output json
|
||||
| **Description** | View model RPM/TPM rate limits |
|
||||
| **Usage** | `bl quota list [--model <model>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------- | -------- | ------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model name(s), comma-separated |
|
||||
| `--all` | boolean | no | Show all models, not just self-service ones |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model name(s), comma-separated |
|
||||
| `--all` | switch | no | Show all models, not just self-service ones |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -145,16 +145,16 @@ bl quota list --output json
|
||||
| **Description** | Request a temporary quota increase |
|
||||
| **Usage** | `bl quota request --model <model> --tpm <value> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------- | -------- | ------------------------------------- |
|
||||
| `--model <model>` | string | yes | Model name (required) |
|
||||
| `--tpm <value>` | string | yes | Target TPM value (required) |
|
||||
| `--yes` | boolean | no | Skip downgrade confirmation |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------- |
|
||||
| `--model <model>` | string | yes | Model name (required) |
|
||||
| `--tpm <value>` | string | yes | Target TPM value (required) |
|
||||
| `--yes` | switch | no | Skip downgrade confirmation |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ Index: [index.md](index.md)
|
||||
| **Description** | Search the web using DashScope MCP WebSearch service |
|
||||
| **Usage** | `bl search web --query <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------- | ------- | -------- | -------------------------------------- |
|
||||
| `--query <text>` | string | no | Search query text |
|
||||
| `--count <n>` | number | no | Number of search results (default: 10) |
|
||||
| `--list-tools` | boolean | no | List available MCP tools and exit |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | -------------------------------------- |
|
||||
| `--query <text>` | string | no | Search query text |
|
||||
| `--count <n>` | number | no | Number of search results (default: 10) |
|
||||
| `--list-tools` | switch | no | List available MCP tools and exit |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -22,20 +22,20 @@ Index: [index.md](index.md)
|
||||
| **Description** | Recognize speech from audio files (FunAudio-ASR) |
|
||||
| **Usage** | `bl speech recognize --url <audio-url> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | ------------------------------------------------------- |
|
||||
| `--url <url>` | array | yes | Audio file URL or local file path (repeatable, max 100) |
|
||||
| `--model <model>` | string | no | Model ID (default: fun-asr) |
|
||||
| `--language <lang>` | string | no | Language hint (e.g. zh, en, ja) |
|
||||
| `--diarization` | boolean | no | Enable automatic speaker diarization |
|
||||
| `--speaker-count <n>` | number | no | Expected number of speakers (requires --diarization) |
|
||||
| `--vocabulary-id <id>` | string | no | Hot-word vocabulary ID for improved accuracy |
|
||||
| `--channel-id <n>` | number | no | Audio channel ID (default: 0) |
|
||||
| `--out <path>` | string | no | Save full transcription result to JSON file |
|
||||
| `--no-wait` | boolean | no | Return task ID immediately without polling |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval in seconds (default: 2) |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | ------------------------------------------------------- |
|
||||
| `--url <url>` | array | yes | Audio file URL or local file path (repeatable, max 100) |
|
||||
| `--model <model>` | string | no | Model ID (default: fun-asr) |
|
||||
| `--language <lang>` | string | no | Language hint (e.g. zh, en, ja) |
|
||||
| `--diarization` | switch | no | Enable automatic speaker diarization |
|
||||
| `--speaker-count <n>` | number | no | Expected number of speakers (requires --diarization) |
|
||||
| `--vocabulary-id <id>` | string | no | Hot-word vocabulary ID for improved accuracy |
|
||||
| `--channel-id <n>` | number | no | Audio channel ID (default: 0) |
|
||||
| `--out <path>` | string | no | Save full transcription result to JSON file |
|
||||
| `--no-wait` | switch | no | Return task ID immediately without polling |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval in seconds (default: 2) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -75,26 +75,26 @@ bl speech recognize --url https://example.com/audio.mp3 --no-wait --quiet
|
||||
| **Description** | Synthesize speech from text (CosyVoice TTS) |
|
||||
| **Usage** | `bl speech synthesize --text <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--text <text>` | string | no | Text to synthesize into speech (or use --text-file) |
|
||||
| `--text-file <path>` | string | no | Read text from a file instead of --text |
|
||||
| `--model <model>` | string | no | Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash |
|
||||
| `--voice <voice>` | string | no | Voice ID. Use --list-voices to see system voices for cosyvoice-v3-flash; for v3.5-flash provide a clone/design voice ID |
|
||||
| `--list-voices` | boolean | no | List available system voices for the selected model and exit |
|
||||
| `--format <format>` | string | no | Audio format: mp3, pcm, wav, opus (default: mp3) |
|
||||
| `--sample-rate <rate>` | string | no | Audio sample rate in Hz (e.g. 24000) |
|
||||
| `--volume <volume>` | string | no | Volume 0-100 (default: 50) |
|
||||
| `--rate <rate>` | string | no | Speech rate 0.5-2.0 (default: 1.0) |
|
||||
| `--pitch <pitch>` | string | no | Pitch multiplier 0.5-2.0 (default: 1.0) |
|
||||
| `--seed <seed>` | string | no | Random seed 0-65535 for reproducible synthesis |
|
||||
| `--language <lang>` | string | no | Language hint (e.g. zh, en, ja, ko, fr, de) |
|
||||
| `--instruction <text>` | string | no | Natural language instruction to control speech style (e.g. "Use a gentle tone") |
|
||||
| `--enable-ssml` | boolean | no | Enable SSML markup parsing in input text |
|
||||
| `--out <path>` | string | no | Save audio to file (default: auto-generate in temp dir) |
|
||||
| `--stream` | boolean | no | Stream raw PCM audio to stdout (pipe to player) |
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--text <text>` | string | no | Text to synthesize into speech (or use --text-file) |
|
||||
| `--text-file <path>` | string | no | Read text from a file instead of --text |
|
||||
| `--model <model>` | string | no | Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash |
|
||||
| `--voice <voice>` | string | no | Voice ID. Use --list-voices to see system voices for cosyvoice-v3-flash; for v3.5-flash provide a clone/design voice ID |
|
||||
| `--list-voices` | switch | no | List available system voices for the selected model and exit |
|
||||
| `--format <mp3\|pcm\|wav\|opus>` | string | no | Audio format: mp3, pcm, wav, opus (default: mp3) |
|
||||
| `--sample-rate <rate>` | string | no | Audio sample rate in Hz (e.g. 24000) |
|
||||
| `--volume <volume>` | string | no | Volume 0-100 (default: 50) |
|
||||
| `--rate <rate>` | string | no | Speech rate 0.5-2.0 (default: 1.0) |
|
||||
| `--pitch <pitch>` | string | no | Pitch multiplier 0.5-2.0 (default: 1.0) |
|
||||
| `--seed <seed>` | string | no | Random seed 0-65535 for reproducible synthesis |
|
||||
| `--language <lang>` | string | no | Language hint (e.g. zh, en, ja, ko, fr, de) |
|
||||
| `--instruction <text>` | string | no | Natural language instruction to control speech style (e.g. "Use a gentle tone") |
|
||||
| `--enable-ssml` | switch | no | Enable SSML markup parsing in input text |
|
||||
| `--out <path>` | string | no | Save audio to file (default: auto-generate in temp dir) |
|
||||
| `--stream` | switch | no | Stream raw PCM audio to stdout (pipe to player) |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -21,21 +21,21 @@ Index: [index.md](index.md)
|
||||
| **Description** | Send a chat completion (OpenAI compatible, DashScope) |
|
||||
| **Usage** | `bl text chat --message <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------- | -------- | --------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen3.7-max) |
|
||||
| `--message <text>` | array | no | Message text (repeatable, prefix role: to set role); or use --messages-file |
|
||||
| `--messages-file <path>` | string | no | JSON file with messages array (use - for stdin) |
|
||||
| `--system <text>` | string | no | System prompt |
|
||||
| `--max-tokens <n>` | number | no | Maximum tokens to generate (default: 4096) |
|
||||
| `--temperature <n>` | number | no | Sampling temperature (0.0, 2.0] |
|
||||
| `--top-p <n>` | number | no | Nucleus sampling threshold |
|
||||
| `--stream` | boolean | no | Stream response tokens (default: on in TTY) |
|
||||
| `--tool <json-or-path>` | array | no | Tool definition as JSON or file path (repeatable) |
|
||||
| `--enable-thinking` | boolean | no | Enable thinking/reasoning mode (for qwen3/qwq models) |
|
||||
| `--thinking-budget <n>` | number | no | Max tokens for thinking (default: 4096) |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | --------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen3.7-max) |
|
||||
| `--message <text>` | array | no | Message text (repeatable, prefix role: to set role); or use --messages-file |
|
||||
| `--messages-file <path>` | string | no | JSON file with messages array (use - for stdin) |
|
||||
| `--system <text>` | string | no | System prompt |
|
||||
| `--max-tokens <n>` | number | no | Maximum tokens to generate (default: 4096) |
|
||||
| `--temperature <n>` | number | no | Sampling temperature (0.0, 2.0] |
|
||||
| `--top-p <n>` | number | no | Nucleus sampling threshold |
|
||||
| `--stream` | switch | no | Stream response tokens (default: on in TTY) |
|
||||
| `--tool <json-or-path>` | array | no | Tool definition as JSON or file path (repeatable) |
|
||||
| `--enable-thinking` | switch | no | Enable thinking/reasoning mode (for qwen3/qwq models) |
|
||||
| `--thinking-budget <n>` | number | no | Max tokens for thinking (default: 4096) |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ Index: [index.md](index.md)
|
||||
| **Description** | Update the CLI to the latest version |
|
||||
| **Usage** | `bl update` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
_No command-specific options._
|
||||
_No command-specific flags._
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Query free-tier quota for models (all models if --model is omitted) |
|
||||
| **Usage** | `bl usage free [--model <model>[,model2,...]] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------- |
|
||||
@@ -72,17 +72,17 @@ bl usage free --model qwen3-max --console-region cn-beijing
|
||||
| **Description** | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable |
|
||||
| **Usage** | `bl usage freetier <--model <model>[,model2,...] \| --all> [--off] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------- | -------- | ------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model name(s), comma-separated for multiple |
|
||||
| `--all` | boolean | no | Apply to all free-tier models |
|
||||
| `--on` | boolean | no | Enable auto-stop (default behavior) |
|
||||
| `--off` | boolean | no | Disable auto-stop |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model name(s), comma-separated for multiple |
|
||||
| `--all` | switch | no | Apply to all free-tier models |
|
||||
| `--on` | switch | no | Enable auto-stop (default behavior) |
|
||||
| `--off` | switch | no | Disable auto-stop |
|
||||
| `--console-region <region>` | string | no | Console region |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -118,7 +118,7 @@ bl usage freetier --off --all
|
||||
| **Description** | Query model usage statistics |
|
||||
| **Usage** | `bl usage stats [--model <model>] [--days <days>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------------------ |
|
||||
|
||||
@@ -25,7 +25,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Download a completed video by task ID |
|
||||
| **Usage** | `bl video download --task-id <id> --out <path>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | ------------------------ |
|
||||
@@ -50,26 +50,26 @@ bl video download --task-id 3b256896-xxxx --out video.mp4 --quiet
|
||||
| **Description** | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) |
|
||||
| **Usage** | `bl video edit --video <url> --prompt <text> [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: happyhorse-1.0-video-edit) |
|
||||
| `--video <url>` | string | yes | Input video URL or local file (mp4/mov, 2-10s) |
|
||||
| `--prompt <text>` | string | no | Edit instruction (e.g. "Convert the scene to a claymation style") |
|
||||
| `--ref-image <url>` | string | no | Reference image URL (up to 4, comma-separated) |
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4) |
|
||||
| `--duration <seconds>` | number | no | Output video duration in seconds (2-10) |
|
||||
| `--audio-setting <mode>` | string | no | Audio: auto (default) or origin (keep original) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--no-wait` | boolean | no | Return task ID immediately without waiting |
|
||||
| `--async` | boolean | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 15) |
|
||||
| Flag | Type | Required | Description |
|
||||
| -------------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: happyhorse-1.0-video-edit) |
|
||||
| `--video <url>` | string | yes | Input video URL or local file (mp4/mov, 2-10s) |
|
||||
| `--prompt <text>` | string | no | Edit instruction (e.g. "Convert the scene to a claymation style") |
|
||||
| `--ref-image <url>` | string | no | Reference image URL (up to 4, comma-separated) |
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4) |
|
||||
| `--duration <seconds>` | number | no | Output video duration in seconds (2-10) |
|
||||
| `--audio-setting <auto\|origin>` | string | no | Audio: auto (default) or origin (keep original) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--no-wait` | switch | no | Return task ID immediately without waiting |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 15) |
|
||||
| `--async` | switch | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -97,7 +97,7 @@ bl video edit --video https://example.com/input.mp4 --prompt "Put clothes on the
|
||||
| **Description** | Generate a video from text or image (happyhorse-1.0-t2v / happyhorse-1.0-i2v / wan2.6-t2v) |
|
||||
| **Usage** | `bl video generate --prompt <text> [--image <url>] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
@@ -112,9 +112,9 @@ bl video edit --video https://example.com/input.mp4 --prompt "Put clothes on the
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--no-wait` | boolean | no | Return task ID immediately without waiting |
|
||||
| `--async` | boolean | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
| `--no-wait` | switch | no | Return task ID immediately without waiting |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 5) |
|
||||
| `--async` | switch | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -146,7 +146,7 @@ bl video generate --prompt "A cat playing with a ball" --watermark false
|
||||
| **Description** | Reference-to-video generation (happyhorse-1.0-r2v / wan2.6-r2v): multi-subject, multi-shot with voice |
|
||||
| **Usage** | `bl video ref --prompt <text> --image <url>... [--ref-video <url>...] [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
@@ -163,9 +163,9 @@ bl video generate --prompt "A cat playing with a ball" --watermark false
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--no-wait` | boolean | no | Return task ID immediately without waiting |
|
||||
| `--async` | boolean | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
| `--no-wait` | switch | no | Return task ID immediately without waiting |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 15) |
|
||||
| `--async` | switch | no | Return task ID immediately (agent/CI mode, same as --no-wait) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -197,7 +197,7 @@ bl video ref --prompt "Image 1 drinks water" --image person.jpg --watermark fals
|
||||
| **Description** | Query async task status |
|
||||
| **Usage** | `bl video task get --task-id <id>` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | ------------- |
|
||||
|
||||
@@ -21,7 +21,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | Describe an image or video using Qwen-VL |
|
||||
| **Usage** | `bl vision describe --image <path-or-url> [--video <url>] [--prompt <text>]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------------- | ------ | -------- | --------------------------------------------------- |
|
||||
|
||||
@@ -21,7 +21,7 @@ Index: [index.md](index.md)
|
||||
| **Description** | List all workspaces |
|
||||
| **Usage** | `bl workspace list [flags]` |
|
||||
|
||||
#### Options
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------- |
|
||||
|
||||
+37
-20
@@ -11,7 +11,12 @@
|
||||
import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { GLOBAL_OPTIONS, type Command, type OptionDef } from "../packages/core/dist/index.mjs";
|
||||
import {
|
||||
GLOBAL_FLAGS,
|
||||
type AnyCommand,
|
||||
type FlagDef,
|
||||
type FlagsDef,
|
||||
} from "../packages/core/dist/index.mjs";
|
||||
import { commands } from "../packages/cli/src/commands.ts";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -30,17 +35,29 @@ function topLevel(path: string): string {
|
||||
return path.split(" ")[0]!;
|
||||
}
|
||||
|
||||
function optionType(opt: OptionDef): string {
|
||||
if (opt.type) return opt.type;
|
||||
if (!opt.flag.includes("<") && !opt.flag.includes("[")) return "boolean";
|
||||
return "string";
|
||||
/** maxTokens → max-tokens. Flags are keyed by camelCase flag name. */
|
||||
function camelToKebab(str: string): string {
|
||||
return str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
||||
}
|
||||
|
||||
function formatOptionsTable(options: OptionDef[] | undefined): string {
|
||||
if (!options?.length) return "_No command-specific options._\n";
|
||||
const rows = options.map((o) => {
|
||||
const req = o.required ? "yes" : "no";
|
||||
return `| \`${escCell(o.flag)}\` | ${escCell(optionType(o))} | ${req} | ${escCell(o.description)} |`;
|
||||
/** "--max-tokens <count>" for a value flag, "--quiet" for a switch. */
|
||||
function flagDisplay(key: string, def: FlagDef): string {
|
||||
const flag = `--${camelToKebab(key)}`;
|
||||
if (def.type === "switch") return flag;
|
||||
const hint = def.choices ? `<${def.choices.join("|")}>` : def.valueHint;
|
||||
return `${flag} ${hint}`;
|
||||
}
|
||||
|
||||
function flagType(def: FlagDef): string {
|
||||
return def.type;
|
||||
}
|
||||
|
||||
function formatFlagsTable(flags: FlagsDef | undefined): string {
|
||||
const entries = Object.entries(flags ?? {});
|
||||
if (!entries.length) return "_No command-specific flags._\n";
|
||||
const rows = entries.map(([key, def]) => {
|
||||
const req = def.type !== "switch" && def.required ? "yes" : "no";
|
||||
return `| \`${escCell(flagDisplay(key, def))}\` | ${escCell(flagType(def))} | ${req} | ${escCell(def.description)} |`;
|
||||
});
|
||||
return [
|
||||
"| Flag | Type | Required | Description |",
|
||||
@@ -65,7 +82,7 @@ function formatNotes(notes: string[] | undefined): string {
|
||||
return notes.map((n) => `- ${n}`).join("\n") + "\n";
|
||||
}
|
||||
|
||||
function commandSection(path: string, cmd: Command): string {
|
||||
function commandSection(path: string, cmd: AnyCommand): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`### \`bl ${path}\``, "");
|
||||
lines.push(`| Field | Value |`, `| --- | --- |`);
|
||||
@@ -76,8 +93,8 @@ function commandSection(path: string, cmd: Command): string {
|
||||
lines.push(`| **Usage** | \`${escCell(usage)}\` |`);
|
||||
lines.push("");
|
||||
|
||||
lines.push("#### Options", "");
|
||||
lines.push(formatOptionsTable(cmd.options));
|
||||
lines.push("#### Flags", "");
|
||||
lines.push(formatFlagsTable(cmd.flags));
|
||||
|
||||
if (cmd.notes?.length) {
|
||||
lines.push("#### Notes", "");
|
||||
@@ -90,8 +107,8 @@ function commandSection(path: string, cmd: Command): string {
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function groupByTopLevel(entries: [string, Command][]): Map<string, [string, Command][]> {
|
||||
const groups = new Map<string, [string, Command][]>();
|
||||
function groupByTopLevel(entries: [string, AnyCommand][]): Map<string, [string, AnyCommand][]> {
|
||||
const groups = new Map<string, [string, AnyCommand][]>();
|
||||
for (const entry of entries) {
|
||||
const key = topLevel(entry[0]);
|
||||
const list = groups.get(key) ?? [];
|
||||
@@ -104,7 +121,7 @@ function groupByTopLevel(entries: [string, Command][]): Map<string, [string, Com
|
||||
return groups;
|
||||
}
|
||||
|
||||
function buildGroupFile(group: string, groupEntries: [string, Command][]): string {
|
||||
function buildGroupFile(group: string, groupEntries: [string, AnyCommand][]): string {
|
||||
const lines: string[] = [
|
||||
`# \`bl ${group}\` commands`,
|
||||
"",
|
||||
@@ -131,8 +148,8 @@ function buildGroupFile(group: string, groupEntries: [string, Command][]): strin
|
||||
}
|
||||
|
||||
function buildIndex(
|
||||
entries: [string, Command][],
|
||||
groups: Map<string, [string, Command][]>,
|
||||
entries: [string, AnyCommand][],
|
||||
groups: Map<string, [string, AnyCommand][]>,
|
||||
): string {
|
||||
const lines: string[] = [
|
||||
"# bailian-cli (`bl`) command reference",
|
||||
@@ -168,9 +185,9 @@ function buildIndex(
|
||||
"",
|
||||
"## Global flags",
|
||||
"",
|
||||
"Available on every command (in addition to command-specific options):",
|
||||
"Available on every command (in addition to command-specific flags):",
|
||||
"",
|
||||
formatOptionsTable(GLOBAL_OPTIONS),
|
||||
formatFlagsTable(GLOBAL_FLAGS),
|
||||
"",
|
||||
"## Notes",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user