From f9bf36c2426f2c9f7b1eb29eb4b360cc5b7a52b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E9=BA=92?= Date: Sun, 28 Jun 2026 11:19:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor(flags):=20keyed=20type-inferred=20flag?= =?UTF-8?q?=20schema;=20option=E2=86=92flag=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the positional `OptionDef[]` array (key/type regex-parsed from "--x " strings) with a keyed `FlagsDef` record whose `type` drives both runtime parsing and compile-time flag-type inference (`Flags`). 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. --- packages/cli/src/commands.ts | 4 +- .../src/commands/advisor/recommend.ts | 15 +- packages/commands/src/commands/app/call.ts | 79 ++++--- packages/commands/src/commands/app/list.ts | 44 ++-- packages/commands/src/commands/auth/login.ts | 46 ++-- packages/commands/src/commands/auth/logout.ts | 15 +- packages/commands/src/commands/auth/status.ts | 22 +- packages/commands/src/commands/config/set.ts | 19 +- packages/commands/src/commands/config/show.ts | 4 +- .../commands/src/commands/console/call.ts | 35 ++- packages/commands/src/commands/file/upload.ts | 29 +-- packages/commands/src/commands/image/edit.ts | 86 ++++--- .../commands/src/commands/image/generate.ts | 128 +++++----- .../src/commands/knowledge/retrieve.ts | 153 ++++++------ packages/commands/src/commands/mcp/call.ts | 50 ++-- packages/commands/src/commands/mcp/list.ts | 44 ++-- packages/commands/src/commands/mcp/tools.ts | 30 ++- packages/commands/src/commands/memory/add.ts | 51 ++-- .../commands/src/commands/memory/delete.ts | 34 ++- packages/commands/src/commands/memory/list.ts | 33 ++- .../src/commands/memory/profile-create.ts | 28 ++- .../src/commands/memory/profile-get.ts | 26 +- .../commands/src/commands/memory/search.ts | 48 ++-- .../commands/src/commands/memory/update.ts | 41 ++-- packages/commands/src/commands/omni/chat.ts | 89 ++++--- .../commands/src/commands/pipeline/run.ts | 66 +++--- .../src/commands/pipeline/validate.ts | 17 +- packages/commands/src/commands/quota/check.ts | 32 ++- .../commands/src/commands/quota/history.ts | 38 ++- packages/commands/src/commands/quota/list.ts | 31 ++- .../commands/src/commands/quota/request.ts | 37 ++- packages/commands/src/commands/search/web.ts | 27 ++- .../commands/src/commands/speech/recognize.ts | 84 ++++--- .../src/commands/speech/synthesize.ts | 132 +++++++---- packages/commands/src/commands/text/chat.ts | 107 +++++---- packages/commands/src/commands/usage/free.ts | 39 ++- .../commands/src/commands/usage/freetier.ts | 39 ++- packages/commands/src/commands/usage/stats.ts | 46 ++-- .../commands/src/commands/video/download.ts | 21 +- packages/commands/src/commands/video/edit.ts | 125 ++++++---- .../commands/src/commands/video/generate.ts | 113 +++++---- packages/commands/src/commands/video/ref.ts | 123 +++++----- .../commands/src/commands/video/task-get.ts | 10 +- .../commands/src/commands/vision/describe.ts | 36 +-- .../commands/src/commands/workspace/list.ts | 26 +- packages/core/src/config/loader.ts | 2 +- packages/core/src/telemetry/tracker.ts | 2 +- packages/core/src/types/command.ts | 222 ++++++++++-------- packages/core/src/types/flags.ts | 17 -- packages/core/src/types/index.ts | 13 +- packages/rag/src/main.ts | 4 +- packages/runtime/src/args.ts | 149 ++++-------- packages/runtime/src/create-cli.ts | 15 +- packages/runtime/src/index.ts | 2 +- packages/runtime/src/middleware.ts | 6 +- packages/runtime/src/pipeline/bl-config.ts | 1 + packages/runtime/src/registry.ts | 56 +++-- packages/runtime/src/resolve.ts | 4 +- packages/runtime/tests/args.test.ts | 20 +- skills/bailian-cli/reference/advisor.md | 2 +- skills/bailian-cli/reference/app.md | 28 +-- skills/bailian-cli/reference/auth.md | 24 +- skills/bailian-cli/reference/config.md | 6 +- skills/bailian-cli/reference/console.md | 2 +- skills/bailian-cli/reference/file.md | 2 +- skills/bailian-cli/reference/image.md | 6 +- skills/bailian-cli/reference/index.md | 6 +- skills/bailian-cli/reference/knowledge.md | 32 +-- skills/bailian-cli/reference/mcp.md | 6 +- skills/bailian-cli/reference/memory.md | 14 +- skills/bailian-cli/reference/omni.md | 30 +-- skills/bailian-cli/reference/pipeline.md | 4 +- skills/bailian-cli/reference/quota.md | 38 +-- skills/bailian-cli/reference/search.md | 12 +- skills/bailian-cli/reference/speech.md | 64 ++--- skills/bailian-cli/reference/text.md | 28 +-- skills/bailian-cli/reference/update.md | 4 +- skills/bailian-cli/reference/usage.md | 24 +- skills/bailian-cli/reference/video.md | 54 ++--- skills/bailian-cli/reference/vision.md | 2 +- skills/bailian-cli/reference/workspace.md | 2 +- tools/generate-reference.ts | 57 +++-- 82 files changed, 1760 insertions(+), 1502 deletions(-) delete mode 100644 packages/core/src/types/flags.ts diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 9fbc675..ea9b671 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -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 = { +export const commands: Record = { "auth login": authLogin, "auth status": authStatus, "auth logout": authLogout, diff --git a/packages/commands/src/commands/advisor/recommend.ts b/packages/commands/src/commands/advisor/recommend.ts index 7f76b67..f07364d 100644 --- a/packages/commands/src/commands/advisor/recommend.ts +++ b/packages/commands/src/commands/advisor/recommend.ts @@ -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 [flags]", - options: [ - { - flag: "--message ", + flags: { + message: { + type: "string", + valueHint: "", 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); diff --git a/packages/commands/src/commands/app/call.ts b/packages/commands/src/commands/app/call.ts index 8e050ef..43d1933 100644 --- a/packages/commands/src/commands/app/call.ts +++ b/packages/commands/src/commands/app/call.ts @@ -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 --prompt [flags]", - options: [ - { flag: "--app-id ", description: "Application ID (required)", required: true }, - { flag: "--prompt ", description: "Input prompt text", required: true }, - { - flag: "--image ", - description: "Image URL(s) to pass to the app (repeatable)", - type: "array", + flags: { + appId: { + type: "string", + valueHint: "", + description: "Application ID (required)", + required: true, }, - { flag: "--file-id ", description: "Pre-uploaded file ID(s) (repeatable)", type: "array" }, - { flag: "--session-id ", description: "Session ID for multi-turn conversation" }, - { flag: "--stream", description: "Stream response (default: on in TTY)" }, - { flag: "--pipeline-ids ", description: "Knowledge base pipeline IDs (comma-separated)" }, - { flag: "--memory-id ", description: "Memory ID for long-term memory" }, - { flag: "--biz-params ", description: "Business parameters JSON (workflow variables)" }, - { flag: "--has-thoughts", description: "Show agent thinking process" }, - ], + prompt: { + type: "string", + valueHint: "", + description: "Input prompt text", + required: true, + }, + image: { + type: "array", + valueHint: "", + description: "Image URL(s) to pass to the app (repeatable)", + }, + fileId: { + type: "array", + valueHint: "", + description: "Pre-uploaded file ID(s) (repeatable)", + }, + sessionId: { + type: "string", + valueHint: "", + description: "Session ID for multi-turn conversation", + }, + stream: { type: "switch", description: "Stream response (default: on in TTY)" }, + pipelineIds: { + type: "string", + valueHint: "", + description: "Knowledge base pipeline IDs (comma-separated)", + }, + memoryId: { type: "string", valueHint: "", description: "Memory ID for long-term memory" }, + bizParams: { + type: "string", + valueHint: "", + 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); diff --git a/packages/commands/src/commands/app/list.ts b/packages/commands/src/commands/app/list.ts index 615fa9d..da1bced 100644 --- a/packages/commands/src/commands/app/list.ts +++ b/packages/commands/src/commands/app/list.ts @@ -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 ", + flags: { + name: { + type: "string", + valueHint: "", description: "Filter by app name (keyword search)", }, - { - flag: "--page ", + page: { + type: "number", + valueHint: "", description: "Page number (default: 1)", - type: "number", }, - { - flag: "--page-size ", + pageSize: { + type: "number", + valueHint: "", description: "Results per page (default: 30)", - type: "number", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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); diff --git a/packages/commands/src/commands/auth/login.ts b/packages/commands/src/commands/auth/login.ts index f7709a5..f5b2e49 100644 --- a/packages/commands/src/commands/auth/login.ts +++ b/packages/commands/src/commands/auth/login.ts @@ -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 | --console", - options: [ - { flag: "--api-key ", description: "DashScope API key to store" }, - { - flag: "--base-url ", + flags: { + apiKey: { type: "string", valueHint: "", description: "DashScope API key to store" }, + baseUrl: { + type: "string", + valueHint: "", 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; 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."); } }, }); diff --git a/packages/commands/src/commands/auth/logout.ts b/packages/commands/src/commands/auth/logout.ts index 7626d70..6c8ab47 100644 --- a/packages/commands/src/commands/auth/logout.ts +++ b/packages/commands/src/commands/auth/logout.ts @@ -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) { diff --git a/packages/commands/src/commands/auth/status.ts b/packages/commands/src/commands/auth/status.ts index ba6ee87..8e52253 100644 --- a/packages/commands/src/commands/auth/status.ts +++ b/packages/commands/src/commands/auth/status.ts @@ -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 ", description: "Console region" }, - { - flag: "--console-site ", + exampleArgs: ["", "--output json"], + flags: { + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], - exampleArgs: ["", "--output json"], - async run(config: Config, _flags: GlobalFlags) { + consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, + }, + async run(config, _flags) { const format = detectOutputFormat(config.output); const status = await buildStatus(config); diff --git a/packages/commands/src/commands/config/set.ts b/packages/commands/src/commands/config/set.ts index 50fccad..fe75df0 100644 --- a/packages/commands/src/commands/config/set.ts +++ b/packages/commands/src/commands/config/set.ts @@ -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 --value ", - options: [ - { - flag: "--key ", + flags: { + key: { + type: "string", + valueHint: "", 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 ", description: "Value to set", required: true }, - ], + value: { type: "string", valueHint: "", 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; diff --git a/packages/commands/src/commands/config/show.ts b/packages/commands/src/commands/config/show.ts index 5d311c3..8a39abd 100644 --- a/packages/commands/src/commands/config/show.ts +++ b/packages/commands/src/commands/config/show.ts @@ -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); diff --git a/packages/commands/src/commands/console/call.ts b/packages/commands/src/commands/console/call.ts index 7728850..fda4939 100644 --- a/packages/commands/src/commands/console/call.ts +++ b/packages/commands/src/commands/console/call.ts @@ -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 --data [flags]", - options: [ - { - flag: "--api ", + flags: { + api: { + type: "string", + valueHint: "", description: "API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries)", required: true, }, - { - flag: "--data ", + data: { + type: "string", + valueHint: "", description: "Request data as JSON string", required: true, }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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; try { diff --git a/packages/commands/src/commands/file/upload.ts b/packages/commands/src/commands/file/upload.ts index 41756f6..74bdf08 100644 --- a/packages/commands/src/commands/file/upload.ts +++ b/packages/commands/src/commands/file/upload.ts @@ -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 --model ", - options: [ - { - flag: "--file ", + flags: { + file: { + type: "string", + valueHint: "", description: "Local file to upload (image, video, audio)", required: true, }, - { - flag: "--model ", + model: { + type: "string", + valueHint: "", 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); diff --git a/packages/commands/src/commands/image/edit.ts b/packages/commands/src/commands/image/edit.ts index fd89e22..676bf3f 100644 --- a/packages/commands/src/commands/image/edit.ts +++ b/packages/commands/src/commands/image/edit.ts @@ -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 --prompt [flags]", - options: [ - { - flag: "--image ", + flags: { + image: { + type: "array", + valueHint: "", description: "Source image URL or local file path (repeatable for multi-image merge)", required: true, - type: "array", }, - { flag: "--prompt ", description: "Edit instruction text", required: true }, - { flag: "--model ", description: "Model ID (default: qwen-image-2.0)" }, - { - flag: "--size ", + prompt: { + type: "string", + valueHint: "", + description: "Edit instruction text", + required: true, + }, + model: { + type: "string", + valueHint: "", + description: "Model ID (default: qwen-image-2.0)", + }, + size: { + type: "string", + valueHint: "", description: "Output image size: ratio (3:4, 16:9) or pixels (2048*2048)", }, - { flag: "--n ", description: "Number of images (default: 1, max: 6)", type: "number" }, - { flag: "--seed ", description: "Random seed for reproducible results", type: "number" }, - { - flag: "--negative-prompt ", + n: { + type: "number", + valueHint: "", + description: "Number of images (default: 1, max: 6)", + }, + seed: { type: "number", valueHint: "", description: "Random seed for reproducible results" }, + negativePrompt: { + type: "string", + valueHint: "", description: "Negative prompt to exclude unwanted content", }, - { - flag: "--prompt-extend ", + promptExtend: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE, - type: "boolean", }, - { - flag: "--watermark ", + watermark: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_WATERMARK, - type: "boolean", }, - { flag: "--out-dir ", description: "Download images to directory" }, - { flag: "--out-prefix ", description: "Filename prefix (default: edited)" }, - ], + outDir: { type: "string", valueHint: "", description: "Download images to directory" }, + outPrefix: { + type: "string", + valueHint: "", + 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 = diff --git a/packages/commands/src/commands/image/generate.ts b/packages/commands/src/commands/image/generate.ts index 759a2fc..a5e0b7b 100644 --- a/packages/commands/src/commands/image/generate.ts +++ b/packages/commands/src/commands/image/generate.ts @@ -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: "", description: "Image description", required: true }, + model: { + type: "string", + valueHint: "", + description: "Model ID (default: qwen-image-2.0)", + }, + size: { + type: "string", + valueHint: "", + description: "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)", + }, + n: { + type: "number", + valueHint: "", + description: "Number of images per request (default: 1, max: 6)", + }, + seed: { + type: "number", + valueHint: "", + description: "Random seed for reproducible generation", + }, + negativePrompt: { + type: "string", + valueHint: "", + description: "Negative prompt to exclude unwanted content", + }, + promptExtend: { + type: "boolean", + valueHint: "", + description: BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE, + }, + watermark: { + type: "boolean", + valueHint: "", + description: BOOL_FLAG_WATERMARK, + }, + noWait: { + type: "switch", + description: "Return task ID immediately without waiting (async models only)", + }, + outDir: { type: "string", valueHint: "", description: "Download images to directory" }, + outPrefix: { + type: "string", + valueHint: "", + description: "Filename prefix (default: image)", + }, + pollInterval: { + type: "number", + valueHint: "", + description: "Polling interval when waiting (default: 3)", + }, +} satisfies FlagsDef; +type GenerateFlags = Flags; + export default defineCommand({ description: "Generate images (Qwen-Image / wan2.x)", auth: "apiKey", usageArgs: "--prompt [flags]", - options: [ - { flag: "--prompt ", description: "Image description", required: true }, - { flag: "--model ", description: "Model ID (default: qwen-image-2.0)" }, - { - flag: "--size ", - description: "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)", - }, - { - flag: "--n ", - description: "Number of images per request (default: 1, max: 6)", - type: "number", - }, - { flag: "--seed ", description: "Random seed for reproducible generation", type: "number" }, - { - flag: "--negative-prompt ", - description: "Negative prompt to exclude unwanted content", - }, - { - flag: "--prompt-extend ", - description: BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE, - type: "boolean", - }, - { - flag: "--watermark ", - description: BOOL_FLAG_WATERMARK, - type: "boolean", - }, - { - flag: "--no-wait", - description: "Return task ID immediately without waiting (async models only)", - }, - { flag: "--out-dir ", description: "Download images to directory" }, - { flag: "--out-prefix ", description: "Filename prefix (default: image)" }, - { - flag: "--poll-interval ", - 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 { @@ -177,7 +195,7 @@ async function handleAsyncMode( config: Config, _model: string, body: DashScopeImageRequest, - flags: GlobalFlags, + flags: GenerateFlags, format: string, concurrent: number, ): Promise { @@ -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 { 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 = diff --git a/packages/commands/src/commands/knowledge/retrieve.ts b/packages/commands/src/commands/knowledge/retrieve.ts index 355681f..94e2724 100644 --- a/packages/commands/src/commands/knowledge/retrieve.ts +++ b/packages/commands/src/commands/knowledge/retrieve.ts @@ -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: "", + description: "Knowledge base index ID (required)", + required: true, + }, + query: { + type: "string", + valueHint: "", + description: "Search query (required)", + required: true, + }, + denseSimilarityTopK: { + type: "number", + valueHint: "", + description: "Dense retrieval top K", + }, + sparseSimilarityTopK: { + type: "number", + valueHint: "", + description: "Sparse retrieval top K", + }, + rerank: { type: "switch", description: "Enable reranking" }, + rerankTopN: { type: "number", valueHint: "", description: "Rerank top N results" }, + rerankModel: { + type: "string", + valueHint: "", + description: "Rerank model, e.g. qwen3-rerank-hybrid", + }, + rerankMode: { + type: "string", + valueHint: "", + description: "Rerank mode: qa, similar, or custom", + }, + rerankInstruct: { + type: "string", + valueHint: "", + description: "Custom rerank instruction, when mode=custom", + }, + topK: { + type: "number", + valueHint: "", + description: "Number of results (deprecated, use --rerank-top-n)", + }, + workspaceId: { + type: "string", + valueHint: "", + description: "Bailian workspace ID (only needed for deprecated AK/SK auth)", + }, + accessKeyId: { + type: "string", + valueHint: "", + description: "Deprecated: use global --api-key instead", + }, + accessKeySecret: { + type: "string", + valueHint: "", + description: "Deprecated: use global --api-key instead", + }, +} satisfies FlagsDef; +type RetrieveFlags = Flags; + export default defineCommand({ description: "Retrieve from a Bailian knowledge base", auth: "apiKey", usageArgs: "--index-id --query [flags]", - options: [ - { flag: "--index-id ", description: "Knowledge base index ID (required)", required: true }, - { flag: "--query ", description: "Search query (required)", required: true }, - { - flag: "--dense-similarity-top-k ", - description: "Dense retrieval top K", - type: "number", - }, - { - flag: "--sparse-similarity-top-k ", - description: "Sparse retrieval top K", - type: "number", - }, - { flag: "--rerank", description: "Enable reranking" }, - { flag: "--rerank-top-n ", description: "Rerank top N results", type: "number" }, - { - flag: "--rerank-model ", - description: "Rerank model, e.g. qwen3-rerank-hybrid", - }, - { - flag: "--rerank-mode ", - description: "Rerank mode: qa, similar, or custom", - }, - { - flag: "--rerank-instruct ", - description: "Custom rerank instruction, when mode=custom", - }, - { - flag: "--top-k ", - description: "Number of results (deprecated, use --rerank-top-n)", - type: "number", - }, - { - flag: "--workspace-id ", - description: "Bailian workspace ID (only needed for deprecated AK/SK auth)", - }, - { - flag: "--access-key-id ", - description: "Deprecated: use global --api-key instead", - }, - { - flag: "--access-key-secret ", - description: "Deprecated: use global --api-key instead", - }, - ], + flags: RETRIEVE_FLAGS, notes: [ "Authentication: pass `--api-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 { - 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]; } diff --git a/packages/commands/src/commands/mcp/call.ts b/packages/commands/src/commands/mcp/call.ts index 692bb6e..489f16a 100644 --- a/packages/commands/src/commands/mcp/call.ts +++ b/packages/commands/src/commands/mcp/call.ts @@ -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 [--arg k=v ...] [--json '{...}'] [--url ]", - options: [ - { - flag: "--target ", + flags: { + target: { + type: "string", + valueHint: "", description: "Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection", required: true, }, - { - flag: "--arg ", - description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.", + arg: { type: "array", + valueHint: "", + description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.", }, - { - flag: "--json ", + json: { + type: "string", + valueHint: "", description: "Full arguments object as JSON; merged with --arg (arg wins).", }, - { - flag: "--query ", + query: { + type: "string", + valueHint: "", description: "Shortcut for --arg query= (mirrors many DashScope MCP tools).", }, - { flag: "--url ", description: "Override the MCP endpoint URL (for non-Bailian servers)" }, - ], + url: { + type: "string", + valueHint: "", + 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 = {}; 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) { diff --git a/packages/commands/src/commands/mcp/list.ts b/packages/commands/src/commands/mcp/list.ts index 0c72978..971eda1 100644 --- a/packages/commands/src/commands/mcp/list.ts +++ b/packages/commands/src/commands/mcp/list.ts @@ -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 ", description: "Filter by server name (substring match)" }, - { - flag: "--type ", + flags: { + name: { + type: "string", + valueHint: "", + description: "Filter by server name (substring match)", + }, + type: { + type: "string", + valueHint: "", description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)", }, - { flag: "--page ", description: "Page number (default: 1)", type: "number" }, - { flag: "--page-size ", description: "Results per page (default: 30)", type: "number" }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + pageSize: { type: "number", valueHint: "", description: "Results per page (default: 30)" }, + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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 = { diff --git a/packages/commands/src/commands/mcp/tools.ts b/packages/commands/src/commands/mcp/tools.ts index ac7f5b4..83904ba 100644 --- a/packages/commands/src/commands/mcp/tools.ts +++ b/packages/commands/src/commands/mcp/tools.ts @@ -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 [--url ]", - options: [ - { - flag: "--server ", + flags: { + server: { + type: "string", + valueHint: "", description: "Server code from `mcp list` (e.g. market-cmapi00073529)", required: true, }, - { flag: "--url ", description: "Override the MCP endpoint URL (for non-Bailian servers)" }, - ], + url: { + type: "string", + valueHint: "", + 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) { diff --git a/packages/commands/src/commands/memory/add.ts b/packages/commands/src/commands/memory/add.ts index 7794231..1c4c07f 100644 --- a/packages/commands/src/commands/memory/add.ts +++ b/packages/commands/src/commands/memory/add.ts @@ -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: "", description: "User ID (required)", required: true }, + messages: { + type: "string", + valueHint: "", + description: 'Messages JSON array: [{"role":"user","content":"..."},...]', + }, + content: { type: "string", valueHint: "", description: "Custom content text to memorize" }, + profileSchema: { + type: "string", + valueHint: "", + description: "Profile schema ID for user profiling", + }, + memoryLibraryId: { + type: "string", + valueHint: "", + description: "Memory library ID (isolate memory space)", + }, +} satisfies FlagsDef; +type AddFlags = Flags; + export default defineCommand({ description: "Add memory from messages or custom content", auth: "apiKey", usageArgs: "--user-id [--messages ] [--content ] [flags]", - options: [ - { flag: "--user-id ", description: "User ID (required)", required: true }, - { - flag: "--messages ", - description: 'Messages JSON array: [{"role":"user","content":"..."},...]', - }, - { flag: "--content ", description: "Custom content text to memorize" }, - { flag: "--profile-schema ", description: "Profile schema ID for user profiling" }, - { flag: "--memory-library-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); diff --git a/packages/commands/src/commands/memory/delete.ts b/packages/commands/src/commands/memory/delete.ts index 0641e9c..0375b7b 100644 --- a/packages/commands/src/commands/memory/delete.ts +++ b/packages/commands/src/commands/memory/delete.ts @@ -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 --user-id ", - options: [ - { flag: "--node-id ", description: "Memory node ID (required)", required: true }, - { flag: "--user-id ", description: "User ID (required)", required: true }, - { flag: "--memory-library-id ", description: "Memory library ID (non-default library)" }, - ], + flags: { + nodeId: { + type: "string", + valueHint: "", + description: "Memory node ID (required)", + required: true, + }, + userId: { + type: "string", + valueHint: "", + description: "User ID (required)", + required: true, + }, + memoryLibraryId: { + type: "string", + valueHint: "", + 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) { diff --git a/packages/commands/src/commands/memory/list.ts b/packages/commands/src/commands/memory/list.ts index d92e88e..ccc2a96 100644 --- a/packages/commands/src/commands/memory/list.ts +++ b/packages/commands/src/commands/memory/list.ts @@ -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 [flags]", - options: [ - { flag: "--user-id ", description: "User ID (required)", required: true }, - { flag: "--page-size ", description: "Results per page (default: 10)", type: "number" }, - { flag: "--page ", description: "Page number (default: 1)", type: "number" }, - { flag: "--memory-library-id ", description: "Memory library ID" }, - ], + flags: { + userId: { + type: "string", + valueHint: "", + description: "User ID (required)", + required: true, + }, + pageSize: { + type: "number", + valueHint: "", + description: "Results per page (default: 10)", + }, + page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + memoryLibraryId: { type: "string", valueHint: "", 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()}`; diff --git a/packages/commands/src/commands/memory/profile-create.ts b/packages/commands/src/commands/memory/profile-create.ts index 0389e60..ab7fd10 100644 --- a/packages/commands/src/commands/memory/profile-create.ts +++ b/packages/commands/src/commands/memory/profile-create.ts @@ -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 --attributes [flags]", - options: [ - { flag: "--name ", description: "Schema name (required)", required: true }, - { flag: "--description ", description: "Schema description" }, - { - flag: "--attributes ", + flags: { + name: { + type: "string", + valueHint: "", + description: "Schema name (required)", + required: true, + }, + description: { type: "string", valueHint: "", description: "Schema description" }, + attributes: { + type: "string", + valueHint: "", 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); diff --git a/packages/commands/src/commands/memory/profile-get.ts b/packages/commands/src/commands/memory/profile-get.ts index 68751bc..805a0e5 100644 --- a/packages/commands/src/commands/memory/profile-get.ts +++ b/packages/commands/src/commands/memory/profile-get.ts @@ -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 --user-id ", - options: [ - { flag: "--schema-id ", description: "Profile schema ID (required)", required: true }, - { flag: "--user-id ", description: "User ID (required)", required: true }, - ], + flags: { + schemaId: { + type: "string", + valueHint: "", + description: "Profile schema ID (required)", + required: true, + }, + userId: { + type: "string", + valueHint: "", + 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 }); diff --git a/packages/commands/src/commands/memory/search.ts b/packages/commands/src/commands/memory/search.ts index 724fe81..96acff4 100644 --- a/packages/commands/src/commands/memory/search.ts +++ b/packages/commands/src/commands/memory/search.ts @@ -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: "", description: "User ID (required)", required: true }, + query: { type: "string", valueHint: "", description: "Search query text" }, + messages: { + type: "string", + valueHint: "", + description: "Messages JSON array for context-based search", + }, + topK: { + type: "number", + valueHint: "", + description: "Number of results to return (default: 10)", + }, + memoryLibraryId: { type: "string", valueHint: "", description: "Memory library ID" }, +} satisfies FlagsDef; +type SearchFlags = Flags; + export default defineCommand({ description: "Search memory nodes by query or messages", auth: "apiKey", usageArgs: "--user-id [--query ] [flags]", - options: [ - { flag: "--user-id ", description: "User ID (required)", required: true }, - { flag: "--query ", description: "Search query text" }, - { flag: "--messages ", description: "Messages JSON array for context-based search" }, - { - flag: "--top-k ", - description: "Number of results to return (default: 10)", - type: "number", - }, - { flag: "--memory-library-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); diff --git a/packages/commands/src/commands/memory/update.ts b/packages/commands/src/commands/memory/update.ts index 472a269..5ea1b6a 100644 --- a/packages/commands/src/commands/memory/update.ts +++ b/packages/commands/src/commands/memory/update.ts @@ -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 --user-id --content ", - options: [ - { flag: "--node-id ", description: "Memory node ID (required)", required: true }, - { flag: "--user-id ", description: "User ID (required)", required: true }, - { - flag: "--content ", + flags: { + nodeId: { + type: "string", + valueHint: "", + description: "Memory node ID (required)", + required: true, + }, + userId: { + type: "string", + valueHint: "", + description: "User ID (required)", + required: true, + }, + content: { + type: "string", + valueHint: "", description: "New content for the memory node (required)", required: true, }, - { flag: "--memory-library-id ", description: "Memory library ID (non-default library)" }, - ], + memoryLibraryId: { + type: "string", + valueHint: "", + 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); diff --git a/packages/commands/src/commands/omni/chat.ts b/packages/commands/src/commands/omni/chat.ts index f7861aa..d204a31 100644 --- a/packages/commands/src/commands/omni/chat.ts +++ b/packages/commands/src/commands/omni/chat.ts @@ -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 [flags]", - options: [ - { - flag: "--message ", + flags: { + message: { + type: "array", + valueHint: "", description: "Message text (repeatable, prefix role: to set role)", required: true, - type: "array", }, - { flag: "--model ", description: "Model ID (default: qwen3.5-omni-plus)" }, - { flag: "--system ", description: "System prompt" }, - { flag: "--image ", description: "Image URL or local file (repeatable)", type: "array" }, - { - flag: "--audio ", + model: { + type: "string", + valueHint: "", + description: "Model ID (default: qwen3.5-omni-plus)", + }, + system: { type: "string", valueHint: "", description: "System prompt" }, + image: { + type: "array", + valueHint: "", + description: "Image URL or local file (repeatable)", + }, + audio: { + type: "array", + valueHint: "", description: "Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp)", - type: "array", }, - { - flag: "--video ", + video: { + type: "array", + valueHint: "", description: "Video file URL / local path, or comma-separated frame URLs", - type: "array", }, - { - flag: "--voice ", + voice: { + type: "string", + valueHint: "", description: `Output voice (default: Cherry). Options: ${OMNI_VOICES.join(", ")}`, }, - { flag: "--audio-format ", description: "Audio output format (default: wav)" }, - { flag: "--audio-out ", description: "Save audio to file (default: auto-generate)" }, - { flag: "--text-only", description: "Output text only, no audio generation" }, - { flag: "--max-tokens ", description: "Maximum tokens to generate", type: "number" }, - { flag: "--temperature ", description: "Sampling temperature (0.0, 2.0]", type: "number" }, - ], + audioFormat: { + type: "string", + valueHint: "", + description: "Audio output format (default: wav)", + }, + audioOut: { + type: "string", + valueHint: "", + description: "Save audio to file (default: auto-generate)", + }, + textOnly: { type: "switch", description: "Output text only, no audio generation" }, + maxTokens: { type: "number", valueHint: "", description: "Maximum tokens to generate" }, + temperature: { + type: "number", + valueHint: "", + 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"); diff --git a/packages/commands/src/commands/pipeline/run.ts b/packages/commands/src/commands/pipeline/run.ts index 9b03694..b221de0 100644 --- a/packages/commands/src/commands/pipeline/run.ts +++ b/packages/commands/src/commands/pipeline/run.ts @@ -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: "", + description: "Pipeline definition file (YAML/JSON)", + required: true, + }, + input: { type: "string", valueHint: "", description: "Runtime input as inline JSON" }, + inputFile: { + type: "string", + valueHint: "", + description: "Runtime input from a JSON file", + }, + concurrency: { + type: "number", + valueHint: "", + description: "Max parallel steps (default: 1)", + }, + events: { type: "string", valueHint: "", description: "Emit lifecycle events: jsonl" }, + timeout: { + type: "number", + valueHint: "", + description: "Default step timeout in seconds", + }, +} satisfies FlagsDef; +type RunFlags = Flags; + export default defineCommand({ description: "Run a pipeline workflow definition", auth: "none", usageArgs: "--file [flags]", - options: [ - { flag: "--file ", description: "Pipeline definition file (YAML/JSON)", required: true }, - { flag: "--input ", description: "Runtime input as inline JSON" }, - { flag: "--input-file ", description: "Runtime input from a JSON file" }, - { - flag: "--concurrency ", - description: "Max parallel steps (default: 1)", - type: "number", - }, - { flag: "--events ", description: "Emit lifecycle events: jsonl" }, - { - flag: "--timeout ", - 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> { - const inputJson = flags.input as string | undefined; - const inputFile = flags.inputFile as string | undefined; +async function resolveRuntimeInput(flags: RunFlags): Promise> { + 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); diff --git a/packages/commands/src/commands/pipeline/validate.ts b/packages/commands/src/commands/pipeline/validate.ts index a47eb23..a018241 100644 --- a/packages/commands/src/commands/pipeline/validate.ts +++ b/packages/commands/src/commands/pipeline/validate.ts @@ -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 ", - options: [ - { flag: "--file ", description: "Pipeline definition file (YAML/JSON)", required: true }, - ], + flags: { + file: { + type: "string", + valueHint: "", + 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(); diff --git a/packages/commands/src/commands/quota/check.ts b/packages/commands/src/commands/quota/check.ts index 4cc2a4f..15eda6d 100644 --- a/packages/commands/src/commands/quota/check.ts +++ b/packages/commands/src/commands/quota/check.ts @@ -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 ] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model name(s), comma-separated", }, - { - flag: "--period ", + period: { + type: "string", + valueHint: "", description: "Query usage for the last N minutes (default: 2)", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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"); diff --git a/packages/commands/src/commands/quota/history.ts b/packages/commands/src/commands/quota/history.ts index fc48342..44c0c77 100644 --- a/packages/commands/src/commands/quota/history.ts +++ b/packages/commands/src/commands/quota/history.ts @@ -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 ", + flags: { + page: { + type: "string", + valueHint: "", description: "Page number (default: 1)", }, - { - flag: "--page-size ", + pageSize: { + type: "string", + valueHint: "", description: "Page size (default: 10)", }, - { - flag: "--model ", + model: { + type: "string", + valueHint: "", description: "Filter by model name", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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 = { diff --git a/packages/commands/src/commands/quota/list.ts b/packages/commands/src/commands/quota/list.ts index 79113c3..88b5231 100644 --- a/packages/commands/src/commands/quota/list.ts +++ b/packages/commands/src/commands/quota/list.ts @@ -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 ] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model name(s), comma-separated", }, - { - flag: "--all", + all: { + type: "switch", description: "Show all models, not just self-service ones", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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); diff --git a/packages/commands/src/commands/quota/request.ts b/packages/commands/src/commands/quota/request.ts index 19f2545..e33a55e 100644 --- a/packages/commands/src/commands/quota/request.ts +++ b/packages/commands/src/commands/quota/request.ts @@ -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 --tpm [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model name (required)", required: true, }, - { - flag: "--tpm ", + tpm: { + type: "string", + valueHint: "", description: "Target TPM value (required)", required: true, }, - { - flag: "--yes", - description: "Skip downgrade confirmation", - }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + yes: { type: "switch", description: "Skip downgrade confirmation" }, + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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); diff --git a/packages/commands/src/commands/search/web.ts b/packages/commands/src/commands/search/web.ts index a122d6c..07822d0 100644 --- a/packages/commands/src/commands/search/web.ts +++ b/packages/commands/src/commands/search/web.ts @@ -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: "", description: "Search query text" }, + count: { + type: "number", + valueHint: "", + 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 [flags]", - options: [ - { flag: "--query ", description: "Search query text" }, - { flag: "--count ", 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 = { 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); diff --git a/packages/commands/src/commands/speech/recognize.ts b/packages/commands/src/commands/speech/recognize.ts index 366b326..e006a59 100644 --- a/packages/commands/src/commands/speech/recognize.ts +++ b/packages/commands/src/commands/speech/recognize.ts @@ -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: "", + description: "Audio file URL or local file path (repeatable, max 100)", + required: true, + }, + model: { type: "string", valueHint: "", description: "Model ID (default: fun-asr)" }, + language: { type: "string", valueHint: "", description: "Language hint (e.g. zh, en, ja)" }, + diarization: { type: "switch", description: "Enable automatic speaker diarization" }, + speakerCount: { + type: "number", + valueHint: "", + description: "Expected number of speakers (requires --diarization)", + }, + vocabularyId: { + type: "string", + valueHint: "", + description: "Hot-word vocabulary ID for improved accuracy", + }, + channelId: { type: "number", valueHint: "", description: "Audio channel ID (default: 0)" }, + out: { + type: "string", + valueHint: "", + description: "Save full transcription result to JSON file", + }, + noWait: { type: "switch", description: "Return task ID immediately without polling" }, + pollInterval: { + type: "number", + valueHint: "", + description: "Polling interval in seconds (default: 2)", + }, +} satisfies FlagsDef; +type RecognizeFlags = Flags; + export default defineCommand({ description: "Recognize speech from audio files (FunAudio-ASR)", auth: "apiKey", usageArgs: "--url [flags]", - options: [ - { - flag: "--url ", - description: "Audio file URL or local file path (repeatable, max 100)", - required: true, - type: "array", - }, - { flag: "--model ", description: "Model ID (default: fun-asr)" }, - { flag: "--language ", description: "Language hint (e.g. zh, en, ja)" }, - { flag: "--diarization", description: "Enable automatic speaker diarization" }, - { - flag: "--speaker-count ", - description: "Expected number of speakers (requires --diarization)", - type: "number", - }, - { flag: "--vocabulary-id ", description: "Hot-word vocabulary ID for improved accuracy" }, - { flag: "--channel-id ", description: "Audio channel ID (default: 0)", type: "number" }, - { flag: "--out ", description: "Save full transcription result to JSON file" }, - { flag: "--no-wait", description: "Return task ID immediately without polling" }, - { - flag: "--poll-interval ", - 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 { @@ -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(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) { diff --git a/packages/commands/src/commands/speech/synthesize.ts b/packages/commands/src/commands/speech/synthesize.ts index 4da0ac2..01a8621 100644 --- a/packages/commands/src/commands/speech/synthesize.ts +++ b/packages/commands/src/commands/speech/synthesize.ts @@ -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: "", + description: "Text to synthesize into speech (or use --text-file)", + }, + textFile: { + type: "string", + valueHint: "", + description: "Read text from a file instead of --text", + }, + model: { + type: "string", + valueHint: "", + description: + "Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash", + }, + voice: { + type: "string", + valueHint: "", + 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: "", + description: "Audio format: mp3, pcm, wav, opus (default: mp3)", + choices: ["mp3", "pcm", "wav", "opus"] as const, + }, + sampleRate: { + type: "string", + valueHint: "", + description: "Audio sample rate in Hz (e.g. 24000)", + }, + volume: { type: "string", valueHint: "", description: "Volume 0-100 (default: 50)" }, + rate: { type: "string", valueHint: "", description: "Speech rate 0.5-2.0 (default: 1.0)" }, + pitch: { + type: "string", + valueHint: "", + description: "Pitch multiplier 0.5-2.0 (default: 1.0)", + }, + seed: { + type: "string", + valueHint: "", + description: "Random seed 0-65535 for reproducible synthesis", + }, + language: { + type: "string", + valueHint: "", + description: "Language hint (e.g. zh, en, ja, ko, fr, de)", + }, + instruction: { + type: "string", + valueHint: "", + 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: "", + 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; + export default defineCommand({ description: "Synthesize speech from text (CosyVoice TTS)", auth: "apiKey", usageArgs: "--text [flags]", - options: [ - { flag: "--text ", description: "Text to synthesize into speech (or use --text-file)" }, - { flag: "--text-file ", description: "Read text from a file instead of --text" }, - { - flag: "--model ", - description: - "Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash", - }, - { - flag: "--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 ", description: "Audio format: mp3, pcm, wav, opus (default: mp3)" }, - { flag: "--sample-rate ", description: "Audio sample rate in Hz (e.g. 24000)" }, - { flag: "--volume ", description: "Volume 0-100 (default: 50)" }, - { flag: "--rate ", description: "Speech rate 0.5-2.0 (default: 1.0)" }, - { flag: "--pitch ", description: "Pitch multiplier 0.5-2.0 (default: 1.0)" }, - { flag: "--seed ", description: "Random seed 0-65535 for reproducible synthesis" }, - { flag: "--language ", description: "Language hint (e.g. zh, en, ja, ko, fr, de)" }, - { - flag: "--instruction ", - 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 ", - 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 ', @@ -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 { 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 { 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; diff --git a/packages/commands/src/commands/text/chat.ts b/packages/commands/src/commands/text/chat.ts index e130ff8..d4fe1f0 100644 --- a/packages/commands/src/commands/text/chat.ts +++ b/packages/commands/src/commands/text/chat.ts @@ -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: "", description: "Model ID (default: qwen3.7-max)" }, + message: { + type: "array", + valueHint: "", + description: "Message text (repeatable, prefix role: to set role); or use --messages-file", + }, + messagesFile: { + type: "string", + valueHint: "", + description: "JSON file with messages array (use - for stdin)", + }, + system: { type: "string", valueHint: "", description: "System prompt" }, + maxTokens: { + type: "number", + valueHint: "", + description: "Maximum tokens to generate (default: 4096)", + }, + temperature: { + type: "number", + valueHint: "", + description: "Sampling temperature (0.0, 2.0]", + }, + topP: { type: "number", valueHint: "", description: "Nucleus sampling threshold" }, + stream: { type: "switch", description: "Stream response tokens (default: on in TTY)" }, + tool: { + type: "array", + valueHint: "", + 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: "", + description: "Max tokens for thinking (default: 4096)", + }, +} satisfies FlagsDef; +type ChatFlags = 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 [flags]", - options: [ - { flag: "--model ", description: "Model ID (default: qwen3.7-max)" }, - { - flag: "--message ", - description: "Message text (repeatable, prefix role: to set role); or use --messages-file", - type: "array", - }, - { - flag: "--messages-file ", - description: "JSON file with messages array (use - for stdin)", - }, - { flag: "--system ", description: "System prompt" }, - { - flag: "--max-tokens ", - description: "Maximum tokens to generate (default: 4096)", - type: "number", - }, - { flag: "--temperature ", description: "Sampling temperature (0.0, 2.0]", type: "number" }, - { flag: "--top-p ", description: "Nucleus sampling threshold", type: "number" }, - { flag: "--stream", description: "Stream response tokens (default: on in TTY)" }, - { - flag: "--tool ", - 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 ", - 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 { diff --git a/packages/commands/src/commands/usage/free.ts b/packages/commands/src/commands/usage/free.ts index 360d567..dd8c0f3 100644 --- a/packages/commands/src/commands/usage/free.ts +++ b/packages/commands/src/commands/usage/free.ts @@ -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 [,model2,...]] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model name(s) to query, comma-separated for multiple; omit for all models", }, - { - flag: "--expiring ", + expiring: { + type: "string", + valueHint: "", description: "Only show quotas expiring within N days", }, - { - flag: "--sort ", + sort: { + type: "string", + valueHint: "", description: "Sort by: remaining (ascending), expires (ascending)", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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`, diff --git a/packages/commands/src/commands/usage/freetier.ts b/packages/commands/src/commands/usage/freetier.ts index 2302b5e..4823566 100644 --- a/packages/commands/src/commands/usage/freetier.ts +++ b/packages/commands/src/commands/usage/freetier.ts @@ -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 [,model2,...] | --all> [--off] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", 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 ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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 [,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); diff --git a/packages/commands/src/commands/usage/stats.ts b/packages/commands/src/commands/usage/stats.ts index cdc038d..9d67e8e 100644 --- a/packages/commands/src/commands/usage/stats.ts +++ b/packages/commands/src/commands/usage/stats.ts @@ -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 ] [--days ] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model name(s), comma-separated; omit for overview", }, - { - flag: "--days ", + days: { + type: "string", + valueHint: "", description: "Number of days (default: 7)", }, - { - flag: "--type ", + type: { + type: "string", + valueHint: "", description: "Model type: Text, Vision, Multimodal, Audio, Embedding", }, - { - flag: "--workspace-id ", + workspaceId: { + type: "string", + valueHint: "", description: "Workspace ID (env: BAILIAN_WORKSPACE_ID)", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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(); diff --git a/packages/commands/src/commands/video/download.ts b/packages/commands/src/commands/video/download.ts index 03f426d..ffc45a4 100644 --- a/packages/commands/src/commands/video/download.ts +++ b/packages/commands/src/commands/video/download.ts @@ -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 --out ", - options: [ - { flag: "--task-id ", description: "Task ID to download from", required: true }, - { flag: "--out ", description: "Output file path", required: true }, - ], + flags: { + taskId: { + type: "string", + valueHint: "", + description: "Task ID to download from", + required: true, + }, + out: { type: "string", valueHint: "", 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); diff --git a/packages/commands/src/commands/video/edit.ts b/packages/commands/src/commands/video/edit.ts index 1539405..2fed605 100644 --- a/packages/commands/src/commands/video/edit.ts +++ b/packages/commands/src/commands/video/edit.ts @@ -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 --prompt [flags]", - options: [ - { flag: "--model ", description: "Model ID (default: happyhorse-1.0-video-edit)" }, - { - flag: "--video ", + flags: { + model: { + type: "string", + valueHint: "", + description: "Model ID (default: happyhorse-1.0-video-edit)", + }, + video: { + type: "string", + valueHint: "", description: "Input video URL or local file (mp4/mov, 2-10s)", required: true, }, - { - flag: "--prompt ", + prompt: { + type: "string", + valueHint: "", description: 'Edit instruction (e.g. "Convert the scene to a claymation style")', }, - { flag: "--ref-image ", description: "Reference image URL (up to 4, comma-separated)" }, - { - flag: "--negative-prompt ", + refImage: { + type: "string", + valueHint: "", + description: "Reference image URL (up to 4, comma-separated)", + }, + negativePrompt: { + type: "string", + valueHint: "", description: "Negative prompt to exclude unwanted content", }, - { flag: "--resolution ", description: "Resolution: 720P or 1080P (default: 1080P)" }, - { flag: "--ratio ", description: "Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4)" }, - { - flag: "--duration ", - description: "Output video duration in seconds (2-10)", + resolution: { + type: "string", + valueHint: "", + description: "Resolution: 720P or 1080P (default: 1080P)", + }, + ratio: { + type: "string", + valueHint: "", + description: "Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4)", + }, + duration: { type: "number", + valueHint: "", + description: "Output video duration in seconds (2-10)", }, - { - flag: "--audio-setting ", + audioSetting: { + type: "string", + valueHint: "", description: "Audio: auto (default) or origin (keep original)", + choices: ["auto", "origin"] as const, }, - { - flag: "--prompt-extend ", + promptExtend: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, - type: "boolean", }, - { - flag: "--watermark ", + watermark: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_WATERMARK, - type: "boolean", }, - { flag: "--seed ", description: "Random seed for reproducible generation", type: "number" }, - { flag: "--download ", description: "Save video to file on completion" }, - { flag: "--no-wait", description: "Return task ID immediately without waiting" }, - { - flag: "--async", + seed: { + type: "number", + valueHint: "", + description: "Random seed for reproducible generation", + }, + download: { + type: "string", + valueHint: "", + description: "Save video to file on completion", + }, + noWait: { type: "switch", description: "Return task ID immediately without waiting" }, + pollInterval: { + type: "number", + valueHint: "", + 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 ", - 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) { diff --git a/packages/commands/src/commands/video/generate.ts b/packages/commands/src/commands/video/generate.ts index 0ce249a..bb17027 100644 --- a/packages/commands/src/commands/video/generate.ts +++ b/packages/commands/src/commands/video/generate.ts @@ -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 [--image ] [flags]", - options: [ - { - flag: "--model ", + flags: { + model: { + type: "string", + valueHint: "", description: "Model ID (default: happyhorse-1.0-t2v, or happyhorse-1.0-i2v with --image)", }, - { flag: "--prompt ", description: "Video description", required: true }, - { flag: "--image ", description: "Input image URL for image-to-video generation" }, - { - flag: "--negative-prompt ", + prompt: { + type: "string", + valueHint: "", + description: "Video description", + required: true, + }, + image: { + type: "string", + valueHint: "", + description: "Input image URL for image-to-video generation", + }, + negativePrompt: { + type: "string", + valueHint: "", description: "Negative prompt to exclude unwanted content", }, - { flag: "--resolution ", description: "Resolution: 720P or 1080P (default: 1080P)" }, - { flag: "--ratio ", description: "Aspect ratio (e.g. 16:9, 9:16, 1:1)" }, - { - flag: "--duration ", - description: "Video duration in seconds (default: 5)", + resolution: { + type: "string", + valueHint: "", + description: "Resolution: 720P or 1080P (default: 1080P)", + }, + ratio: { + type: "string", + valueHint: "", + description: "Aspect ratio (e.g. 16:9, 9:16, 1:1)", + }, + duration: { type: "number", + valueHint: "", + description: "Video duration in seconds (default: 5)", }, - { - flag: "--prompt-extend ", + promptExtend: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, - type: "boolean", }, - { - flag: "--watermark ", + watermark: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_WATERMARK, - type: "boolean", }, - { flag: "--seed ", description: "Random seed for reproducible generation", type: "number" }, - { flag: "--download ", description: "Save video to file on completion" }, - { flag: "--no-wait", description: "Return task ID immediately without waiting" }, - { - flag: "--async", + seed: { + type: "number", + valueHint: "", + description: "Random seed for reproducible generation", + }, + download: { + type: "string", + valueHint: "", + description: "Save video to file on completion", + }, + noWait: { type: "switch", description: "Return task ID immediately without waiting" }, + pollInterval: { + type: "number", + valueHint: "", + 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 ", - 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) { diff --git a/packages/commands/src/commands/video/ref.ts b/packages/commands/src/commands/video/ref.ts index cfadc22..d84d268 100644 --- a/packages/commands/src/commands/video/ref.ts +++ b/packages/commands/src/commands/video/ref.ts @@ -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 --image ... [--ref-video ...] [flags]", - options: [ - { flag: "--model ", description: "Model ID (default: happyhorse-1.0-r2v)" }, - { - flag: "--prompt ", + flags: { + model: { + type: "string", + valueHint: "", + description: "Model ID (default: happyhorse-1.0-r2v)", + }, + prompt: { + type: "string", + valueHint: "", description: "Video description with reference markers (image1, video1, etc.)", required: true, }, - { - flag: "--image ", + image: { + type: "array", + valueHint: "", description: "Reference image URL or local file (repeatable for multiple subjects)", - type: "array", }, - { - flag: "--ref-video ", + refVideo: { + type: "array", + valueHint: "", description: "Reference video URL or local file (repeatable)", - type: "array", }, - { - flag: "--image-voice ", + imageVoice: { + type: "array", + valueHint: "", description: "Voice URL for corresponding image (pairs by position)", - type: "array", }, - { - flag: "--video-voice ", + videoVoice: { + type: "array", + valueHint: "", description: "Voice URL for corresponding ref-video (pairs by position)", - type: "array", }, - { flag: "--resolution ", description: "Resolution: 720P or 1080P (default: 1080P)" }, - { flag: "--ratio ", description: "Aspect ratio (16:9, 9:16, 1:1)" }, - { - flag: "--duration ", - description: "Video duration in seconds (default: 5)", + resolution: { + type: "string", + valueHint: "", + description: "Resolution: 720P or 1080P (default: 1080P)", + }, + ratio: { type: "string", valueHint: "", description: "Aspect ratio (16:9, 9:16, 1:1)" }, + duration: { type: "number", + valueHint: "", + description: "Video duration in seconds (default: 5)", }, - { - flag: "--prompt-extend ", + promptExtend: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, - type: "boolean", }, - { - flag: "--watermark ", + watermark: { + type: "boolean", + valueHint: "", description: BOOL_FLAG_WATERMARK, - type: "boolean", }, - { flag: "--seed ", description: "Random seed for reproducible generation", type: "number" }, - { flag: "--download ", description: "Save video to file on completion" }, - { flag: "--no-wait", description: "Return task ID immediately without waiting" }, - { - flag: "--async", + seed: { + type: "number", + valueHint: "", + description: "Random seed for reproducible generation", + }, + download: { + type: "string", + valueHint: "", + description: "Save video to file on completion", + }, + noWait: { type: "switch", description: "Return task ID immediately without waiting" }, + pollInterval: { + type: "number", + valueHint: "", + 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 ", - 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) { diff --git a/packages/commands/src/commands/video/task-get.ts b/packages/commands/src/commands/video/task-get.ts index 5c1ea8f..75d8946 100644 --- a/packages/commands/src/commands/video/task-get.ts +++ b/packages/commands/src/commands/video/task-get.ts @@ -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 ", - options: [{ flag: "--task-id ", description: "Async task ID", required: true }], + flags: { + taskId: { type: "string", valueHint: "", 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); diff --git a/packages/commands/src/commands/vision/describe.ts b/packages/commands/src/commands/vision/describe.ts index 21fe701..f5489c8 100644 --- a/packages/commands/src/commands/vision/describe.ts +++ b/packages/commands/src/commands/vision/describe.ts @@ -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 [--video ] [--prompt ]", - options: [ - { flag: "--image ", description: "Local image path or URL" }, - { - flag: "--video ", - description: "Video file URL or local path (mp4/mov/avi/mkv/webm)", + flags: { + image: { type: "string", valueHint: "", description: "Local image path or URL" }, + video: { type: "array", + valueHint: "", + description: "Video file URL or local path (mp4/mov/avi/mkv/webm)", }, - { flag: "--prompt ", description: "Question about the content (default: auto-detected)" }, - { flag: "--model ", description: "Vision model (default: qwen3-vl-plus)" }, - ], + prompt: { + type: "string", + valueHint: "", + description: "Question about the content (default: auto-detected)", + }, + model: { + type: "string", + valueHint: "", + 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); diff --git a/packages/commands/src/commands/workspace/list.ts b/packages/commands/src/commands/workspace/list.ts index 01dc7cb..94753dc 100644 --- a/packages/commands/src/commands/workspace/list.ts +++ b/packages/commands/src/commands/workspace/list.ts @@ -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 ", + flags: { + list: { + type: "string", + valueHint: "", description: "Limit number of results", }, - { flag: "--console-region ", description: "Console region" }, - { - flag: "--console-site ", + consoleRegion: { type: "string", valueHint: "", description: "Console region" }, + consoleSite: { + type: "string", + valueHint: "", description: "Console site: domestic, international", }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID", - type: "number", - }, - ], + consoleSwitchAgent: { type: "number", valueHint: "", 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); diff --git a/packages/core/src/config/loader.ts b/packages/core/src/config/loader.ts index beaddcd..acd8b8f 100644 --- a/packages/core/src/config/loader.ts +++ b/packages/core/src/config/loader.ts @@ -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(); diff --git a/packages/core/src/telemetry/tracker.ts b/packages/core/src/telemetry/tracker.ts index 495ae8a..8e5fec2 100644 --- a/packages/core/src/telemetry/tracker.ts +++ b/packages/core/src/telemetry/tracker.ts @@ -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"; diff --git a/packages/core/src/types/command.ts b/packages/core/src/types/command.ts index 68da64d..1337ccb 100644 --- a/packages/core/src/types/command.ts +++ b/packages/core/src/types/command.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 ` → string (default). - * - number : `--flag ` → 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 `, `--n `, `--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; + +// ── Type inference: definition → parsed flag types ─────────────────────────── +type ParsedValue = 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 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 = { + [K in keyof F as IsRequired extends true ? K : never]: ParsedValue; +} & { + [K in keyof F as IsRequired extends true ? never : K]?: ParsedValue; +}; + export type AuthRequirement = "apiKey" | "console" | "none"; -export interface Command { - description: string; - /** - * Argument portion of the usage line, WITHOUT the ` ` prefix - * (e.g. "--index-id --query [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 ` ` prefix - * (e.g. '--index-id idx_xxx --query "..."'). The runtime prepends - * ` ` 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; -} - -export interface CommandSpec { - description: string; - /** See {@link Command.usageArgs} — argument portion only, no ` ` prefix. */ - usageArgs?: string; - options?: OptionDef[]; - /** See {@link Command.exampleArgs} — argument strings only, no ` ` 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; -} - -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 ", description: "API key" }, - { flag: "--base-url ", description: "API base URL" }, - { flag: "--output ", description: "Output format: text, json" }, - { flag: "--timeout ", 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 ", description: "Run N parallel requests (default: 1)", type: "number" }, - { - flag: "--console-region ", +// ── Global flags (single source: derived from GLOBAL_FLAGS) ────────────────── +export const GLOBAL_FLAGS = { + apiKey: { type: "string", valueHint: "", description: "API key" }, + baseUrl: { type: "string", valueHint: "", description: "API base URL" }, + output: { type: "string", valueHint: "", description: "Output format: text, json" }, + timeout: { type: "number", valueHint: "", description: "Request timeout" }, + concurrent: { + type: "number", + valueHint: "", + 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: "", description: "Console gateway region (e.g. cn-beijing, ap-southeast-1)", }, - { flag: "--console-site ", description: "Console site: domestic, international" }, - { - flag: "--console-switch-agent ", - description: "Switch agent UID for delegated access", - type: "number", + consoleSite: { + type: "string", + valueHint: "", + description: "Console site: domestic, international", }, - { flag: "--help", description: "Show help", type: "switch" }, - { flag: "--version", description: "Print version", type: "switch" }, -]; + consoleSwitchAgent: { + type: "number", + valueHint: "", + 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; +/** A command's full flags: global + its own flags, inferred in one pass. */ +export type Flags = ParsedFlags; + +// ── Command ────────────────────────────────────────────────────────────────── +/** + * A command. Generic over its flags `F` so `run`/`validate` receive precisely + * typed flags (`Flags` = global + own flags). Stored heterogeneously as + * {@link AnyCommand}; the precise typing lives at the `defineCommand` call site. + */ +export interface Command { + description: string; + /** Credential this command requires. See {@link AuthRequirement}. */ + auth: AuthRequirement; + /** Usage line arg portion, e.g. "--prompt [flags]". Manually written. */ + usageArgs?: string; + /** Example arg strings (without the ` ` 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) => string | undefined; + run: (config: Config, flags: Flags) => Promise; +} + +/** Type-erased command for heterogeneous storage (registry / context). */ +export type AnyCommand = Command; + +/** Identity wrapper whose only job is to infer `F` from `spec.flags`. */ +export function defineCommand(spec: Command): Command { + return spec; +} diff --git a/packages/core/src/types/flags.ts b/packages/core/src/types/flags.ts deleted file mode 100644 index 7b21177..0000000 --- a/packages/core/src/types/flags.ts +++ /dev/null @@ -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; -} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index fa1e40e..641abbf 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -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, diff --git a/packages/rag/src/main.ts b/packages/rag/src/main.ts index acfdc9c..d81ef56 100644 --- a/packages/rag/src/main.ts +++ b/packages/rag/src/main.ts @@ -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 = { +const commands: Record = { "auth login": authLogin, "auth status": authStatus, "auth logout": authLogout, diff --git a/packages/runtime/src/args.ts b/packages/runtime/src/args.ts index 6b1d229..bca2fb7 100644 --- a/packages/runtime/src/args.ts +++ b/packages/runtime/src/args.ts @@ -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 ' → '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; - booleans: Set; - numbers: Set; - arrays: Set; -} - -function buildAllowedFlagKeys(options: OptionDef[]): Set { - const keys = new Set(); - 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(); - const booleans = new Set(); - const numbers = new Set(); - const arrays = new Set(); - 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(rest: string[], defs: F): ParsedFlags { + const flags: Record = {}; const seen = new Set(); - 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)[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)[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)[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)[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)[camelKey] = true; - else if (v === "false") (flags as Record)[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)[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)[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; } diff --git a/packages/runtime/src/create-cli.ts b/packages/runtime/src/create-cli.ts index bf9b259..a3a4fe6 100644 --- a/packages/runtime/src/create-cli.ts +++ b/packages/runtime/src/create-cli.ts @@ -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, opts: CliOptions): Cli { +export function createCli(commands: Record, 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, 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, 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); diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 3ade7f5..3fc89e2 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -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"; diff --git a/packages/runtime/src/middleware.ts b/packages/runtime/src/middleware.ts index 3197653..c72bb3d 100644 --- a/packages/runtime/src/middleware.ts +++ b/packages/runtime/src/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); diff --git a/packages/runtime/src/pipeline/bl-config.ts b/packages/runtime/src/pipeline/bl-config.ts index 10007ec..ccb3088 100644 --- a/packages/runtime/src/pipeline/bl-config.ts +++ b/packages/runtime/src/pipeline/bl-config.ts @@ -9,6 +9,7 @@ const PIPELINE_FLAGS: GlobalFlags = { yes: false, dryRun: false, help: false, + version: false, async: false, }; diff --git a/packages/runtime/src/registry.ts b/packages/runtime/src/registry.ts index 5155ec2..6304159 100644 --- a/packages/runtime/src/registry.ts +++ b/packages/runtime/src/registry.ts @@ -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 " 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; } @@ -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, cliName: string) { + constructor(commands: Record, 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) { diff --git a/packages/runtime/src/resolve.ts b/packages/runtime/src/resolve.ts index 263f132..b043773 100644 --- a/packages/runtime/src/resolve.ts +++ b/packages/runtime/src/resolve.ts @@ -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 }; /** diff --git a/packages/runtime/tests/args.test.ts b/packages/runtime/tests/args.test.ts index 938e24c..53daec3 100644 --- a/packages/runtime/tests/args.test.ts +++ b/packages/runtime/tests/args.test.ts @@ -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 ", description: "Image description", required: true }, - { flag: "--model ", description: "Model ID" }, - { flag: "--image ", description: "Image URL (repeatable)", type: "array" }, - { flag: "--n ", description: "Number of images", type: "number" }, - { flag: "--watermark ", 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: "", description: "Image description", required: true }, + model: { type: "string", valueHint: "", description: "Model ID" }, + image: { type: "array", valueHint: "", description: "Image URL (repeatable)" }, + n: { type: "number", valueHint: "", description: "Number of images" }, + watermark: { type: "boolean", valueHint: "", 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) ---- diff --git a/skills/bailian-cli/reference/advisor.md b/skills/bailian-cli/reference/advisor.md index f1f231b..38c6a78 100644 --- a/skills/bailian-cli/reference/advisor.md +++ b/skills/bailian-cli/reference/advisor.md @@ -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 [flags]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------- | diff --git a/skills/bailian-cli/reference/app.md b/skills/bailian-cli/reference/app.md index 47ef1af..779f1e6 100644 --- a/skills/bailian-cli/reference/app.md +++ b/skills/bailian-cli/reference/app.md @@ -22,20 +22,20 @@ Index: [index.md](index.md) | **Description** | Call a Bailian application (agent or workflow) | | **Usage** | `bl app call --app-id --prompt [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ---------------------- | ------- | -------- | --------------------------------------------- | -| `--app-id ` | string | yes | Application ID (required) | -| `--prompt ` | string | yes | Input prompt text | -| `--image ` | array | no | Image URL(s) to pass to the app (repeatable) | -| `--file-id ` | array | no | Pre-uploaded file ID(s) (repeatable) | -| `--session-id ` | string | no | Session ID for multi-turn conversation | -| `--stream` | boolean | no | Stream response (default: on in TTY) | -| `--pipeline-ids ` | string | no | Knowledge base pipeline IDs (comma-separated) | -| `--memory-id ` | string | no | Memory ID for long-term memory | -| `--biz-params ` | string | no | Business parameters JSON (workflow variables) | -| `--has-thoughts` | boolean | no | Show agent thinking process | +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | --------------------------------------------- | +| `--app-id ` | string | yes | Application ID (required) | +| `--prompt ` | string | yes | Input prompt text | +| `--image ` | array | no | Image URL(s) to pass to the app (repeatable) | +| `--file-id ` | array | no | Pre-uploaded file ID(s) (repeatable) | +| `--session-id ` | string | no | Session ID for multi-turn conversation | +| `--stream` | switch | no | Stream response (default: on in TTY) | +| `--pipeline-ids ` | string | no | Knowledge base pipeline IDs (comma-separated) | +| `--memory-id ` | string | no | Memory ID for long-term memory | +| `--biz-params ` | 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 | | ------------------------------ | ------ | -------- | ------------------------------------- | diff --git a/skills/bailian-cli/reference/auth.md b/skills/bailian-cli/reference/auth.md index e4c9957..b4e954c 100644 --- a/skills/bailian-cli/reference/auth.md +++ b/skills/bailian-cli/reference/auth.md @@ -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 \| --console` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------ | ------- | -------- | ------------------------------------------------------------------------------------- | -| `--api-key ` | string | no | DashScope API key to store | -| `--base-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 ` | string | no | DashScope API key to store | +| `--base-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 | | ------------------------------ | ------ | -------- | ------------------------------------- | diff --git a/skills/bailian-cli/reference/config.md b/skills/bailian-cli/reference/config.md index 1822e1c..444a5ba 100644 --- a/skills/bailian-cli/reference/config.md +++ b/skills/bailian-cli/reference/config.md @@ -22,7 +22,7 @@ Index: [index.md](index.md) | **Description** | Set a config value | | **Usage** | `bl config set --key --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 diff --git a/skills/bailian-cli/reference/console.md b/skills/bailian-cli/reference/console.md index ee61c38..923da91 100644 --- a/skills/bailian-cli/reference/console.md +++ b/skills/bailian-cli/reference/console.md @@ -21,7 +21,7 @@ Index: [index.md](index.md) | **Description** | Call a Bailian console API via the CLI gateway | | **Usage** | `bl console call --api --data [flags]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------------------------------ | diff --git a/skills/bailian-cli/reference/file.md b/skills/bailian-cli/reference/file.md index 060e257..37d6f62 100644 --- a/skills/bailian-cli/reference/file.md +++ b/skills/bailian-cli/reference/file.md @@ -21,7 +21,7 @@ Index: [index.md](index.md) | **Description** | Upload a local file to DashScope temporary storage (48h) | | **Usage** | `bl file upload --file --model ` | -#### Options +#### Flags | Flag | Type | Required | Description | | ----------------- | ------ | -------- | ----------------------------------------------- | diff --git a/skills/bailian-cli/reference/image.md b/skills/bailian-cli/reference/image.md index 50e81ca..8fb255e 100644 --- a/skills/bailian-cli/reference/image.md +++ b/skills/bailian-cli/reference/image.md @@ -22,7 +22,7 @@ Index: [index.md](index.md) | **Description** | Edit an existing image with text instructions (Qwen-Image) | | **Usage** | `bl image edit --image --prompt [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 [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 ` | string | no | Negative prompt to exclude unwanted content | | `--prompt-extend ` | boolean | no | Enable prompt extend (true/false). Omit flag: true for qwen-image sync; parameter omitted on async models (API default). | | `--watermark ` | 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 ` | string | no | Download images to directory | | `--out-prefix ` | string | no | Filename prefix (default: image) | | `--poll-interval ` | number | no | Polling interval when waiting (default: 3) | diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index a9d5cc0..8538987 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -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 ` | string | no | API base URL | | `--output ` | string | no | Output format: text, json | | `--timeout ` | number | no | Request timeout | +| `--concurrent ` | 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 ` | 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 ` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) | | `--console-site ` | string | no | Console site: domestic, international | | `--console-switch-agent ` | number | no | Switch agent UID for delegated access | diff --git a/skills/bailian-cli/reference/knowledge.md b/skills/bailian-cli/reference/knowledge.md index d2a0d49..a51b162 100644 --- a/skills/bailian-cli/reference/knowledge.md +++ b/skills/bailian-cli/reference/knowledge.md @@ -21,23 +21,23 @@ Index: [index.md](index.md) | **Description** | Retrieve from a Bailian knowledge base | | **Usage** | `bl knowledge retrieve --index-id --query [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------------------- | ------- | -------- | ------------------------------------------------------------ | -| `--index-id ` | string | yes | Knowledge base index ID (required) | -| `--query ` | string | yes | Search query (required) | -| `--dense-similarity-top-k ` | number | no | Dense retrieval top K | -| `--sparse-similarity-top-k ` | number | no | Sparse retrieval top K | -| `--rerank` | boolean | no | Enable reranking | -| `--rerank-top-n ` | number | no | Rerank top N results | -| `--rerank-model ` | string | no | Rerank model, e.g. qwen3-rerank-hybrid | -| `--rerank-mode ` | string | no | Rerank mode: qa, similar, or custom | -| `--rerank-instruct ` | string | no | Custom rerank instruction, when mode=custom | -| `--top-k ` | number | no | Number of results (deprecated, use --rerank-top-n) | -| `--workspace-id ` | string | no | Bailian workspace ID (only needed for deprecated AK/SK auth) | -| `--access-key-id ` | string | no | Deprecated: use global --api-key instead | -| `--access-key-secret ` | string | no | Deprecated: use global --api-key instead | +| Flag | Type | Required | Description | +| ------------------------------- | ------ | -------- | ------------------------------------------------------------ | +| `--index-id ` | string | yes | Knowledge base index ID (required) | +| `--query ` | string | yes | Search query (required) | +| `--dense-similarity-top-k ` | number | no | Dense retrieval top K | +| `--sparse-similarity-top-k ` | number | no | Sparse retrieval top K | +| `--rerank` | switch | no | Enable reranking | +| `--rerank-top-n ` | number | no | Rerank top N results | +| `--rerank-model ` | string | no | Rerank model, e.g. qwen3-rerank-hybrid | +| `--rerank-mode ` | string | no | Rerank mode: qa, similar, or custom | +| `--rerank-instruct ` | string | no | Custom rerank instruction, when mode=custom | +| `--top-k ` | number | no | Number of results (deprecated, use --rerank-top-n) | +| `--workspace-id ` | string | no | Bailian workspace ID (only needed for deprecated AK/SK auth) | +| `--access-key-id ` | string | no | Deprecated: use global --api-key instead | +| `--access-key-secret ` | string | no | Deprecated: use global --api-key instead | #### Notes diff --git a/skills/bailian-cli/reference/mcp.md b/skills/bailian-cli/reference/mcp.md index c2a31d8..aba1162 100644 --- a/skills/bailian-cli/reference/mcp.md +++ b/skills/bailian-cli/reference/mcp.md @@ -23,7 +23,7 @@ Index: [index.md](index.md) | **Description** | Call a tool on an MCP server (tools/call) | | **Usage** | `bl mcp call --target [--arg k=v ...] [--json '{...}'] [--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 [--url ]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------- | diff --git a/skills/bailian-cli/reference/memory.md b/skills/bailian-cli/reference/memory.md index 6d7e561..914e074 100644 --- a/skills/bailian-cli/reference/memory.md +++ b/skills/bailian-cli/reference/memory.md @@ -27,7 +27,7 @@ Index: [index.md](index.md) | **Description** | Add memory from messages or custom content | | **Usage** | `bl memory add --user-id [--messages ] [--content ] [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 --user-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 [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 --attributes [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 --user-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 [--query ] [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 --user-id --content ` | -#### Options +#### Flags | Flag | Type | Required | Description | | -------------------------- | ------ | -------- | ------------------------------------------ | diff --git a/skills/bailian-cli/reference/omni.md b/skills/bailian-cli/reference/omni.md index d9aed23..4525a43 100644 --- a/skills/bailian-cli/reference/omni.md +++ b/skills/bailian-cli/reference/omni.md @@ -21,22 +21,22 @@ Index: [index.md](index.md) | **Description** | Multimodal chat with text + audio output (Qwen-Omni) | | **Usage** | `bl omni --message [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ---------------------- | ------- | -------- | ------------------------------------------------------------------------------------ | -| `--message ` | array | yes | Message text (repeatable, prefix role: to set role) | -| `--model ` | string | no | Model ID (default: qwen3.5-omni-plus) | -| `--system ` | string | no | System prompt | -| `--image ` | array | no | Image URL or local file (repeatable) | -| `--audio ` | array | no | Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp) | -| `--video ` | array | no | Video file URL / local path, or comma-separated frame URLs | -| `--voice ` | string | no | Output voice (default: Cherry). Options: Chelsie, Cherry, Ethan, Serena, Sunny, Tina | -| `--audio-format ` | string | no | Audio output format (default: wav) | -| `--audio-out ` | string | no | Save audio to file (default: auto-generate) | -| `--text-only` | boolean | no | Output text only, no audio generation | -| `--max-tokens ` | number | no | Maximum tokens to generate | -| `--temperature ` | number | no | Sampling temperature (0.0, 2.0] | +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | ------------------------------------------------------------------------------------ | +| `--message ` | array | yes | Message text (repeatable, prefix role: to set role) | +| `--model ` | string | no | Model ID (default: qwen3.5-omni-plus) | +| `--system ` | string | no | System prompt | +| `--image ` | array | no | Image URL or local file (repeatable) | +| `--audio ` | array | no | Audio URL or local file (.wav/.mp3/.amr/.aac/.m4a/.ogg/.3gp/.3gpp) | +| `--video ` | array | no | Video file URL / local path, or comma-separated frame URLs | +| `--voice ` | string | no | Output voice (default: Cherry). Options: Chelsie, Cherry, Ethan, Serena, Sunny, Tina | +| `--audio-format ` | string | no | Audio output format (default: wav) | +| `--audio-out ` | string | no | Save audio to file (default: auto-generate) | +| `--text-only` | switch | no | Output text only, no audio generation | +| `--max-tokens ` | number | no | Maximum tokens to generate | +| `--temperature ` | number | no | Sampling temperature (0.0, 2.0] | #### Examples diff --git a/skills/bailian-cli/reference/pipeline.md b/skills/bailian-cli/reference/pipeline.md index 285486e..d66bd3e 100644 --- a/skills/bailian-cli/reference/pipeline.md +++ b/skills/bailian-cli/reference/pipeline.md @@ -22,7 +22,7 @@ Index: [index.md](index.md) | **Description** | Run a pipeline workflow definition | | **Usage** | `bl pipeline run --file [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 ` | -#### Options +#### Flags | Flag | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------ | diff --git a/skills/bailian-cli/reference/quota.md b/skills/bailian-cli/reference/quota.md index 807e472..49a39ad 100644 --- a/skills/bailian-cli/reference/quota.md +++ b/skills/bailian-cli/reference/quota.md @@ -24,7 +24,7 @@ Index: [index.md](index.md) | **Description** | Check current usage against rate limits | | **Usage** | `bl quota check [--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 ] [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------------------ | ------- | -------- | ------------------------------------------- | -| `--model ` | string | no | Model name(s), comma-separated | -| `--all` | boolean | no | Show all models, not just self-service ones | -| `--console-region ` | string | no | Console region | -| `--console-site ` | string | no | Console site: domestic, international | -| `--console-switch-agent ` | number | no | Switch agent UID | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------- | +| `--model ` | string | no | Model name(s), comma-separated | +| `--all` | switch | no | Show all models, not just self-service ones | +| `--console-region ` | string | no | Console region | +| `--console-site ` | string | no | Console site: domestic, international | +| `--console-switch-agent ` | 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 --tpm [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------------------ | ------- | -------- | ------------------------------------- | -| `--model ` | string | yes | Model name (required) | -| `--tpm ` | string | yes | Target TPM value (required) | -| `--yes` | boolean | no | Skip downgrade confirmation | -| `--console-region ` | string | no | Console region | -| `--console-site ` | string | no | Console site: domestic, international | -| `--console-switch-agent ` | number | no | Switch agent UID | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------- | +| `--model ` | string | yes | Model name (required) | +| `--tpm ` | string | yes | Target TPM value (required) | +| `--yes` | switch | no | Skip downgrade confirmation | +| `--console-region ` | string | no | Console region | +| `--console-site ` | string | no | Console site: domestic, international | +| `--console-switch-agent ` | number | no | Switch agent UID | #### Examples diff --git a/skills/bailian-cli/reference/search.md b/skills/bailian-cli/reference/search.md index b7766ff..8cb08ed 100644 --- a/skills/bailian-cli/reference/search.md +++ b/skills/bailian-cli/reference/search.md @@ -21,13 +21,13 @@ Index: [index.md](index.md) | **Description** | Search the web using DashScope MCP WebSearch service | | **Usage** | `bl search web --query [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ---------------- | ------- | -------- | -------------------------------------- | -| `--query ` | string | no | Search query text | -| `--count ` | number | no | Number of search results (default: 10) | -| `--list-tools` | boolean | no | List available MCP tools and exit | +| Flag | Type | Required | Description | +| ---------------- | ------ | -------- | -------------------------------------- | +| `--query ` | string | no | Search query text | +| `--count ` | number | no | Number of search results (default: 10) | +| `--list-tools` | switch | no | List available MCP tools and exit | #### Examples diff --git a/skills/bailian-cli/reference/speech.md b/skills/bailian-cli/reference/speech.md index ea7c37f..99c77b7 100644 --- a/skills/bailian-cli/reference/speech.md +++ b/skills/bailian-cli/reference/speech.md @@ -22,20 +22,20 @@ Index: [index.md](index.md) | **Description** | Recognize speech from audio files (FunAudio-ASR) | | **Usage** | `bl speech recognize --url [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| --------------------------- | ------- | -------- | ------------------------------------------------------- | -| `--url ` | array | yes | Audio file URL or local file path (repeatable, max 100) | -| `--model ` | string | no | Model ID (default: fun-asr) | -| `--language ` | string | no | Language hint (e.g. zh, en, ja) | -| `--diarization` | boolean | no | Enable automatic speaker diarization | -| `--speaker-count ` | number | no | Expected number of speakers (requires --diarization) | -| `--vocabulary-id ` | string | no | Hot-word vocabulary ID for improved accuracy | -| `--channel-id ` | number | no | Audio channel ID (default: 0) | -| `--out ` | string | no | Save full transcription result to JSON file | -| `--no-wait` | boolean | no | Return task ID immediately without polling | -| `--poll-interval ` | number | no | Polling interval in seconds (default: 2) | +| Flag | Type | Required | Description | +| --------------------------- | ------ | -------- | ------------------------------------------------------- | +| `--url ` | array | yes | Audio file URL or local file path (repeatable, max 100) | +| `--model ` | string | no | Model ID (default: fun-asr) | +| `--language ` | string | no | Language hint (e.g. zh, en, ja) | +| `--diarization` | switch | no | Enable automatic speaker diarization | +| `--speaker-count ` | number | no | Expected number of speakers (requires --diarization) | +| `--vocabulary-id ` | string | no | Hot-word vocabulary ID for improved accuracy | +| `--channel-id ` | number | no | Audio channel ID (default: 0) | +| `--out ` | string | no | Save full transcription result to JSON file | +| `--no-wait` | switch | no | Return task ID immediately without polling | +| `--poll-interval ` | 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 [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ---------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | -| `--text ` | string | no | Text to synthesize into speech (or use --text-file) | -| `--text-file ` | string | no | Read text from a file instead of --text | -| `--model ` | string | no | Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash | -| `--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 ` | string | no | Audio format: mp3, pcm, wav, opus (default: mp3) | -| `--sample-rate ` | string | no | Audio sample rate in Hz (e.g. 24000) | -| `--volume ` | string | no | Volume 0-100 (default: 50) | -| `--rate ` | string | no | Speech rate 0.5-2.0 (default: 1.0) | -| `--pitch ` | string | no | Pitch multiplier 0.5-2.0 (default: 1.0) | -| `--seed ` | string | no | Random seed 0-65535 for reproducible synthesis | -| `--language ` | string | no | Language hint (e.g. zh, en, ja, ko, fr, de) | -| `--instruction ` | 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 ` | 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 ` | string | no | Text to synthesize into speech (or use --text-file) | +| `--text-file ` | string | no | Read text from a file instead of --text | +| `--model ` | string | no | Model ID (default: cosyvoice-v3-flash). System voices available for cosyvoice-v3-flash | +| `--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 ` | string | no | Audio format: mp3, pcm, wav, opus (default: mp3) | +| `--sample-rate ` | string | no | Audio sample rate in Hz (e.g. 24000) | +| `--volume ` | string | no | Volume 0-100 (default: 50) | +| `--rate ` | string | no | Speech rate 0.5-2.0 (default: 1.0) | +| `--pitch ` | string | no | Pitch multiplier 0.5-2.0 (default: 1.0) | +| `--seed ` | string | no | Random seed 0-65535 for reproducible synthesis | +| `--language ` | string | no | Language hint (e.g. zh, en, ja, ko, fr, de) | +| `--instruction ` | 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 ` | 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 diff --git a/skills/bailian-cli/reference/text.md b/skills/bailian-cli/reference/text.md index cd125a4..0467a23 100644 --- a/skills/bailian-cli/reference/text.md +++ b/skills/bailian-cli/reference/text.md @@ -21,21 +21,21 @@ Index: [index.md](index.md) | **Description** | Send a chat completion (OpenAI compatible, DashScope) | | **Usage** | `bl text chat --message [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------------ | ------- | -------- | --------------------------------------------------------------------------- | -| `--model ` | string | no | Model ID (default: qwen3.7-max) | -| `--message ` | array | no | Message text (repeatable, prefix role: to set role); or use --messages-file | -| `--messages-file ` | string | no | JSON file with messages array (use - for stdin) | -| `--system ` | string | no | System prompt | -| `--max-tokens ` | number | no | Maximum tokens to generate (default: 4096) | -| `--temperature ` | number | no | Sampling temperature (0.0, 2.0] | -| `--top-p ` | number | no | Nucleus sampling threshold | -| `--stream` | boolean | no | Stream response tokens (default: on in TTY) | -| `--tool ` | array | no | Tool definition as JSON or file path (repeatable) | -| `--enable-thinking` | boolean | no | Enable thinking/reasoning mode (for qwen3/qwq models) | -| `--thinking-budget ` | number | no | Max tokens for thinking (default: 4096) | +| Flag | Type | Required | Description | +| ------------------------ | ------ | -------- | --------------------------------------------------------------------------- | +| `--model ` | string | no | Model ID (default: qwen3.7-max) | +| `--message ` | array | no | Message text (repeatable, prefix role: to set role); or use --messages-file | +| `--messages-file ` | string | no | JSON file with messages array (use - for stdin) | +| `--system ` | string | no | System prompt | +| `--max-tokens ` | number | no | Maximum tokens to generate (default: 4096) | +| `--temperature ` | number | no | Sampling temperature (0.0, 2.0] | +| `--top-p ` | number | no | Nucleus sampling threshold | +| `--stream` | switch | no | Stream response tokens (default: on in TTY) | +| `--tool ` | array | no | Tool definition as JSON or file path (repeatable) | +| `--enable-thinking` | switch | no | Enable thinking/reasoning mode (for qwen3/qwq models) | +| `--thinking-budget ` | number | no | Max tokens for thinking (default: 4096) | #### Examples diff --git a/skills/bailian-cli/reference/update.md b/skills/bailian-cli/reference/update.md index f47effb..cbf440e 100644 --- a/skills/bailian-cli/reference/update.md +++ b/skills/bailian-cli/reference/update.md @@ -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 diff --git a/skills/bailian-cli/reference/usage.md b/skills/bailian-cli/reference/usage.md index b7f9369..b5e0c2e 100644 --- a/skills/bailian-cli/reference/usage.md +++ b/skills/bailian-cli/reference/usage.md @@ -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 [,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 [,model2,...] \| --all> [--off] [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| ------------------------------ | ------- | -------- | ------------------------------------------- | -| `--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 ` | string | no | Console region | -| `--console-site ` | string | no | Console site: domestic, international | -| `--console-switch-agent ` | number | no | Switch agent UID | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------- | +| `--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 ` | string | no | Console region | +| `--console-site ` | string | no | Console site: domestic, international | +| `--console-switch-agent ` | 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 ] [--days ] [flags]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------------ | diff --git a/skills/bailian-cli/reference/video.md b/skills/bailian-cli/reference/video.md index 2b94cec..64cffe7 100644 --- a/skills/bailian-cli/reference/video.md +++ b/skills/bailian-cli/reference/video.md @@ -25,7 +25,7 @@ Index: [index.md](index.md) | **Description** | Download a completed video by task ID | | **Usage** | `bl video download --task-id --out ` | -#### 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 --prompt [flags]` | -#### Options +#### Flags -| Flag | Type | Required | Description | -| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- | -| `--model ` | string | no | Model ID (default: happyhorse-1.0-video-edit) | -| `--video ` | string | yes | Input video URL or local file (mp4/mov, 2-10s) | -| `--prompt ` | string | no | Edit instruction (e.g. "Convert the scene to a claymation style") | -| `--ref-image ` | string | no | Reference image URL (up to 4, comma-separated) | -| `--negative-prompt ` | string | no | Negative prompt to exclude unwanted content | -| `--resolution ` | string | no | Resolution: 720P or 1080P (default: 1080P) | -| `--ratio ` | string | no | Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4) | -| `--duration ` | number | no | Output video duration in seconds (2-10) | -| `--audio-setting ` | string | no | Audio: auto (default) or origin (keep original) | -| `--prompt-extend ` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). | -| `--watermark ` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). | -| `--seed ` | number | no | Random seed for reproducible generation | -| `--download ` | 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 ` | number | no | Polling interval when waiting (default: 15) | +| Flag | Type | Required | Description | +| -------------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- | +| `--model ` | string | no | Model ID (default: happyhorse-1.0-video-edit) | +| `--video ` | string | yes | Input video URL or local file (mp4/mov, 2-10s) | +| `--prompt ` | string | no | Edit instruction (e.g. "Convert the scene to a claymation style") | +| `--ref-image ` | string | no | Reference image URL (up to 4, comma-separated) | +| `--negative-prompt ` | string | no | Negative prompt to exclude unwanted content | +| `--resolution ` | string | no | Resolution: 720P or 1080P (default: 1080P) | +| `--ratio ` | string | no | Aspect ratio (16:9, 9:16, 1:1, 4:3, 3:4) | +| `--duration ` | number | no | Output video duration in seconds (2-10) | +| `--audio-setting ` | string | no | Audio: auto (default) or origin (keep original) | +| `--prompt-extend ` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). | +| `--watermark ` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). | +| `--seed ` | number | no | Random seed for reproducible generation | +| `--download ` | string | no | Save video to file on completion | +| `--no-wait` | switch | no | Return task ID immediately without waiting | +| `--poll-interval ` | 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 [--image ] [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 ` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). | | `--seed ` | number | no | Random seed for reproducible generation | | `--download ` | 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 ` | 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 --image ... [--ref-video ...] [flags]` | -#### Options +#### Flags | Flag | Type | Required | Description | | --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- | @@ -163,9 +163,9 @@ bl video generate --prompt "A cat playing with a ball" --watermark false | `--watermark ` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). | | `--seed ` | number | no | Random seed for reproducible generation | | `--download ` | 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 ` | 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 ` | -#### Options +#### Flags | Flag | Type | Required | Description | | ---------------- | ------ | -------- | ------------- | diff --git a/skills/bailian-cli/reference/vision.md b/skills/bailian-cli/reference/vision.md index 6373bfb..8be3881 100644 --- a/skills/bailian-cli/reference/vision.md +++ b/skills/bailian-cli/reference/vision.md @@ -21,7 +21,7 @@ Index: [index.md](index.md) | **Description** | Describe an image or video using Qwen-VL | | **Usage** | `bl vision describe --image [--video ] [--prompt ]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ----------------------- | ------ | -------- | --------------------------------------------------- | diff --git a/skills/bailian-cli/reference/workspace.md b/skills/bailian-cli/reference/workspace.md index 27fc6b8..03bb69f 100644 --- a/skills/bailian-cli/reference/workspace.md +++ b/skills/bailian-cli/reference/workspace.md @@ -21,7 +21,7 @@ Index: [index.md](index.md) | **Description** | List all workspaces | | **Usage** | `bl workspace list [flags]` | -#### Options +#### Flags | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------- | diff --git a/tools/generate-reference.ts b/tools/generate-reference.ts index 675a4b2..4b44f92 100644 --- a/tools/generate-reference.ts +++ b/tools/generate-reference.ts @@ -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 " 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 { - const groups = new Map(); +function groupByTopLevel(entries: [string, AnyCommand][]): Map { + const groups = new Map(); 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, + entries: [string, AnyCommand][], + groups: Map, ): 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", "",