mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2389681ad6 | |||
| 946b7029c6 | |||
| b9ecd5c43b | |||
| 978f332fea | |||
| 4502424200 | |||
| 03839766bc | |||
| 1f8b9ace7e | |||
| 6338df36be | |||
| cb6740965f | |||
| b68ff45fb9 | |||
| 4990b27436 |
@@ -37,6 +37,7 @@ tools/generated
|
||||
.claude/settings.local.json
|
||||
.claude/scheduled_tasks.lock
|
||||
.cursor/
|
||||
.qoder/
|
||||
.qwen/
|
||||
.qoder
|
||||
.playwright-mcp/
|
||||
|
||||
@@ -6,6 +6,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
|
||||
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
|
||||
|
||||
## [1.14.1] - 2026-08-05
|
||||
|
||||
### Added
|
||||
|
||||
- **Focused Bailian Skills** — `npx skills add modelstudioai/cli --all -g` now installs dedicated skills for media generation, fine-tuning, Managed Agent, and shared execution rules, improving task routing while reducing irrelevant context.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Default image model upgraded to Qwen-Image 3.0** — image generation, image editing, pipelines, the config UI, and related documentation now default to `qwen-image-3.0` for API Key users.
|
||||
- **Broader coding-agent compatibility** — Skill installation and updates now detect more coding agents, preserve existing installation links, and automatically backfill skills into newly detected agents.
|
||||
|
||||
## [1.14.0] - 2026-08-04
|
||||
|
||||
### Added
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
|
||||
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
|
||||
|
||||
## [1.14.1] - 2026-08-05
|
||||
|
||||
### 新增
|
||||
|
||||
- **百炼 Skill 按领域拆分** —— 通过 `npx skills add modelstudioai/cli --all -g` 可统一安装图片与视频生成、模型微调、Managed Agent 和共享执行协议等专用 Skill,提升任务路由准确性并减少无关上下文。
|
||||
|
||||
### 变更
|
||||
|
||||
- **默认图片模型升级至 Qwen-Image 3.0** —— 普通 API Key 用户的图片生成、图片编辑、Pipeline、配置 UI 和相关文档现在默认使用 `qwen-image-3.0`。
|
||||
- **扩展 Coding Agent 兼容范围** —— Skill 安装与更新现在能够识别更多 Coding Agent,保留已有安装链接,并自动将 Skill 补充到新识别的 Agent。
|
||||
|
||||
## [1.14.0] - 2026-08-04
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
---
|
||||
|
||||
_Chat with Qwen, generate images & videos, understand images, call agents,_
|
||||
_manage memory, search the web — all from your terminal._
|
||||
_Chat with Qwen, generate and edit images and videos, understand images, synthesize_
|
||||
_and recognize speech, call apps, manage memory, retrieve knowledge, search the web —_
|
||||
_every AI capability, one command away._
|
||||
|
||||
_Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
@@ -22,28 +23,16 @@ _Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
## Features
|
||||
|
||||
Equip your AI Agent out-of-the-box with these capabilities, composable across complex tasks:
|
||||
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
|
||||
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
|
||||
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
|
||||
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
|
||||
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
|
||||
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step
|
||||
|
||||
- **Text chat** — Qwen3.8-max: major gains in agentic coding, frontend coding, and vibe coding
|
||||
- **Multimodal (Omni)** — Full omni-modal support across text + image + audio + video
|
||||
- **Image generation & editing** — Qwen-Image 2.0: pro text rendering, photorealism, strong semantic adherence, multi-image composition
|
||||
- **Video generation & editing** — happyhorse-1.1 series: text-/image-/reference-to-video and natural-language video editing (up to 9-image reference)
|
||||
- **Speech synthesis & recognition** — CosyVoice streaming TTS, voice cloning from 5–20s samples; FunAudio-ASR covers 30 languages including 7 Chinese dialects and 20+ Mandarin accents
|
||||
- **Image & video understanding** — Qwen-VL: long-form video analysis, chart/document parsing, visual reasoning, multilingual OCR
|
||||
- **Coding agent setup** — Configure Claude Code, Qwen Code, OpenCode, OpenClaw, Hermes Agent, or Codex to use DashScope with `bl config agent`
|
||||
> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
> **Note:** The features below are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
- **Knowledge base & memory** — Multimodal RAG retrieval and cross-session memory for personalized, coherent dialogue
|
||||
- **App calls** — Invoke agents and workflows already published on Aliyun Model Studio
|
||||
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
|
||||
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
|
||||
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
|
||||
- **Fine-tuning & deployment** — Upload datasets, create text/audio/image fine-tune jobs (`finetune text|audio|image create`; text covers SFT/LoRA/DPO/CPT), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy text|audio|image create`)
|
||||
- **Console capabilities** — Browse the model marketplace (`model list`) and Bailian apps (`app list`), review a unified usage view (`usage summary`), check free-tier quota (`usage free`), view model usage statistics (`usage stats`), manage workspaces (`workspace list`), and manage rate limits (`quota list/request/check/history`)
|
||||
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
|
||||
|
||||
## Showcase: One-Sentence Cinematic Video
|
||||
## Showcase 1: A Cinematic Short Film from One Sentence
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -56,129 +45,77 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
|
||||
A complete **2-minute, 16:9 cinematic short film** — produced end-to-end from a single natural-language sentence, with **zero manual editing**. This showcase demonstrates how an AI Agent can compose a multi-step creative pipeline by orchestrating three primitives:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — the agentic coding model that interprets the user's intent and drives the workflow
|
||||
- **[Aliyun Model Studio CLI](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** — handles scene decomposition, storyboarding, shot continuity, and final stitching
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Generate a roughly 2-minute video in Japanese cinematic style — a sweet, innocent first-love story about a high-school girl. The plot should be heart-fluttering enough to make viewers want to fall in love. Aspect ratio: 16:9."_
|
||||
>
|
||||
> _(Original: "帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2分钟左右的视频,尺寸是16:9")_
|
||||
|
||||
### How it works
|
||||
## Showcase 2: A Short-Film Director Managed Agent from One Sentence
|
||||
|
||||
1. **Qwen Code** parses the request, plans the narrative beats, and decides which tools to call.
|
||||
2. The **spark-video Skill** breaks the story into shots, writes per-shot prompts, and enforces visual continuity (characters, lighting, palette, lens language).
|
||||
3. **`bl video generate`** dispatches each shot to **HappyHorse 1.1** in parallel.
|
||||
4. The skill stitches all clips back together into a single 16:9 / ~2-min deliverable.
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="Click to play the demo video" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
No timeline scrubbing. No frame-by-frame editing. Just one sentence → one video.
|
||||
<p align="center"><i>👆 Click the cover to play the full demo</i></p>
|
||||
|
||||
One sentence builds a reusable cloud-side short-film director for storyboarding, storyboard image generation, and video creation:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — understands the requirement and generates the agent configuration
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — validates the configuration, previews the changes, and completes the deployment
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** — runs the director role along with its skills and tools in the cloud
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Build me a Managed Agent app that can produce short films — a director expert that generates videos and can also design the matching storyboards."_
|
||||
|
||||
## Installation
|
||||
|
||||
**Agent install (recommended)**
|
||||
|
||||
Send the following to your Agent — it will detect your environment, then install and verify the CLI for you:
|
||||
|
||||
```text
|
||||
Please read https://bailian.aliyun.com/cli/install.md and install the Aliyun Model Studio CLI for me
|
||||
```
|
||||
|
||||
**Manual install (npm)**
|
||||
|
||||
```bash
|
||||
# Recommended — no Node required
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows (PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node users / developers (Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> Binary install does not require Node.js. `npm install -g` remains fully supported.
|
||||
> Requires Node.js >= 18.17.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Authenticate, recommended
|
||||
bl auth login --console
|
||||
Once installed, just describe your task to your AI Agent — no need to assemble commands by hand.
|
||||
|
||||
# Or authenticate with an API key
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Or use Token Plan (Base URL built in; the key is tested during login)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# Configure a coding agent to use DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# Chat with Qwen
|
||||
bl text chat --message "What is DashScope?"
|
||||
|
||||
# Multimodal chat (text + image + audio + video)
|
||||
bl omni --message "Describe this image" --image ./photo.jpg
|
||||
|
||||
# Generate an image
|
||||
bl image generate --prompt "A cat in a spacesuit" --out-dir ./images/
|
||||
|
||||
# Generate a video from local image
|
||||
bl video generate --image ./cat.png --prompt "Make the cat move" --download cat.mp4
|
||||
|
||||
# Model recommendation — find the best model for your use case
|
||||
bl advisor recommend --message "I need a visual-understanding chatbot"
|
||||
|
||||
# Compare specific models
|
||||
bl advisor recommend --message "qwen-max vs deepseek-v3 for code generation"
|
||||
|
||||
# Browser login (required for console capability commands)
|
||||
bl auth login --console
|
||||
|
||||
# Fine-tune & deploy — a one-shot train-to-serve workflow
|
||||
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
|
||||
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
|
||||
bl finetune capability --model qwen3-8b # Which training types a model supports
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
|
||||
|
||||
# Browse models / apps / free-tier quota / usage statistics / workspaces
|
||||
bl model list # Browse model families and pricing
|
||||
bl app list
|
||||
bl usage summary # Unified view: free-tier quota + recent usage overview
|
||||
bl usage free # Free-tier quota across models (add --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # Model usage statistics (add --model for per-model)
|
||||
bl workspace list # List all workspaces
|
||||
|
||||
# Rate limit management (list / check / request / history)
|
||||
bl quota list # View RPM/TPM limits (add --model to filter)
|
||||
bl quota check # Current usage vs rate limits (add --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # Request a temporary TPM increase
|
||||
bl quota history # View quota-change history
|
||||
|
||||
# Token Plan team management (requires AK/SK, see auth below)
|
||||
bl token-plan list-seats # View subscription seat details
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| Scenario | What to say to your Agent |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- |
|
||||
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
|
||||
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
|
||||
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
|
||||
| Model selection | "Recommend a model for image understanding and customer support." |
|
||||
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
|
||||
|
||||
> More examples and scenarios: [Aliyun Model Studio CLI Site](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## Authentication
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
Required for most commands. Get your key from the [DashScope Console](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key).
|
||||
|
||||
```bash
|
||||
# Option 1: Environment variable
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# Option 2: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Option 3: Per-command flag
|
||||
bl text chat --api-key sk-xxxxx --message "Hello"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
Get or copy the API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
The CLI has the default Token Plan Base URL built in. Login tests the key first, then saves and activates the `token-plan` config only when validation succeeds.
|
||||
Get or copy your Token Plan API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -186,26 +123,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### Console Login (OAuth)
|
||||
|
||||
Required for console capability commands (`model list`, `app list`, `usage summary/free/stats`, `workspace list`, `quota list/request/check/history`). Opens the Bailian console in your browser to sign in.
|
||||
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### Alibaba Cloud OpenAPI AK/SK (Token Plan only)
|
||||
### Alibaba Cloud OpenAPI AK/SK
|
||||
|
||||
Required for the `token-plan` command group. Get your AccessKey from [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
Token Plan seat and member management requires an Alibaba Cloud AccessKey. Get yours from the [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
|
||||
> Recommended: create a RAM sub-account with minimum privileges instead of using the root account's AK/SK.
|
||||
|
||||
```bash
|
||||
# Option 1: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# Option 2: Environment variables
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -214,18 +145,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# View current config
|
||||
bl config show
|
||||
|
||||
# Set defaults
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# List all config profiles
|
||||
bl config list
|
||||
|
||||
# Self-update to latest or a specific version
|
||||
bl update
|
||||
bl update --to 0.1.14
|
||||
# Switch config profile
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
Config file location: `~/.bailian/config.json`
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
Upgrades the CLI to the latest version and refreshes the installed Agent Skills. Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
Scan the QR code to join the Aliyun Model Studio CLI DingTalk user group for usage help, troubleshooting, bug reports, and tips from other users.
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="Aliyun Model Studio CLI DingTalk user group" width="240" />
|
||||
|
||||
## Links
|
||||
|
||||
| Resource | URL |
|
||||
@@ -237,11 +181,3 @@ Config file location: `~/.bailian/config.json`
|
||||
| Get API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| Get Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| Get AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## Changelog
|
||||
|
||||
Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
+73
-138
@@ -22,28 +22,16 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
## 功能特性
|
||||
|
||||
让您的 AI Agent 开箱即具备以下能力,并可在复杂任务中自动组合调用:
|
||||
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
|
||||
- **素材理解** — 图像、文档、音频、长视频的解析与问答
|
||||
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
|
||||
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
|
||||
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
|
||||
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent
|
||||
|
||||
- **文本对话** — Qwen3.8-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
|
||||
- **全模态对话** — 文本 + 图像 + 音频 + 视频全模态支持
|
||||
- **图像生成与编辑** — Qwen-Image 2.0:专业文字渲染、真实质感、强语义遵循、多图合成
|
||||
- **视频生成与编辑** — happyhorse-1.1 系列,支持文生 / 图生 / 参考生(最多 9 张图参考)/ 自然语言视频编辑
|
||||
- **语音合成与识别** — CosyVoice 实时流式合成,5-20s 样本即可克隆;FunAudio-ASR 覆盖 30 种语种,含汉语七大方言与 20+ 口音官话
|
||||
- **图像与视频理解** — Qwen-VL:长视频解析、复杂图表与文档识别、视觉推理、多语种 OCR
|
||||
- **Coding Agent 配置** — 使用 `bl config agent` 将 Claude Code、Qwen Code、OpenCode、OpenClaw、Hermes Agent 或 Codex 配置为使用 DashScope
|
||||
> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
|
||||
|
||||
> **注意:** 以下功能目前仅对中国站(aliyun.com)账号开放,国际站 / 全球站账号暂不支持。
|
||||
|
||||
- **知识库与记忆库** — 多模态 RAG 检索 + 跨会话记忆,提供个性化连贯对话体验
|
||||
- **应用调用** — 调用已发布在阿里云百炼平台上的智能体与工作流应用
|
||||
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
|
||||
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
|
||||
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
|
||||
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务(`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`)
|
||||
- **控制台能力** — 浏览模型市场(`model list`)和百炼应用(`app list`),查看统一用量视图(`usage summary`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`)
|
||||
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
|
||||
|
||||
## 示例:一句话生成一部电影短片
|
||||
## 示例 1:一句话生成一部电影短片
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -53,130 +41,80 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
<p align="center"><i>👆 点击封面播放完整 2 分钟演示</i></p>
|
||||
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** —— 负责场景拆分、分镜设计、镜头连贯性和最终拼接
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _"帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9"_
|
||||
> _“帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9。”_
|
||||
|
||||
### 工作流程
|
||||
## 示例 2:一句话构建短片导演 Managed Agent
|
||||
|
||||
1. **Qwen Code** 解析需求、规划叙事节奏,决定要调用哪些工具。
|
||||
2. **spark-video Skill** 把故事拆成镜头、为每个镜头写提示词,并保证视觉连贯性(角色、光线、色调、镜头语言)。
|
||||
3. **`bl video generate`** 把每个镜头并行下发给 **HappyHorse 1.1**。
|
||||
4. Skill 把所有片段拼成最终的 16:9 / 约 2 分钟成片。
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="点击播放演示视频" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
没有时间线拖拽,没有逐帧剪辑。一句话 → 一部短片。
|
||||
<p align="center"><i>👆 点击封面播放完整演示</i></p>
|
||||
|
||||
一句话构建一个可复用的云端短片导演,用于分镜设计、分镜图生成和视频创作:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— 理解需求并生成 Agent 配置
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 校验配置、预览变更并完成部署
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** —— 在云端运行导演角色及其 Skill 和工具
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _“帮我构建一个 managedagent 应用,能够实现短片拍摄,导演专家生成视频,然后也能进行设计对应的分镜图。”_
|
||||
|
||||
## 安装
|
||||
|
||||
**Agent 安装(推荐)**
|
||||
|
||||
把下面这句话发给你的 Agent,它会自行判断环境并完成安装与校验:
|
||||
|
||||
```text
|
||||
请阅读:https://bailian.aliyun.com/cli/install.md 并按照说明为我安装阿里云百炼 CLI
|
||||
```
|
||||
|
||||
**手动安装(npm)**
|
||||
|
||||
```bash
|
||||
# 推荐 — 无需本机 Node.js
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows(PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node 用户 / 开发者(需要 Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> 二进制安装不依赖 Node.js。`npm install -g` 长期保留。
|
||||
> 需要预先安装 Node.js >= 18.17。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 认证(推荐浏览器登录)
|
||||
bl auth login --console
|
||||
安装完成后,直接在 AI Agent 中描述你的任务,无需手动拼接命令。
|
||||
|
||||
# 或使用 API key 认证
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 或使用 Token Plan(已内置 Base URL,登录时自动测试 Key)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# 配置 Coding Agent 使用 DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# 和通义千问对话
|
||||
bl text chat --message "你好,介绍一下阿里云百炼平台"
|
||||
|
||||
# 多模态对话(文本 + 图片 + 音频 + 视频)
|
||||
bl omni --message "描述这张图片" --image ./photo.jpg
|
||||
|
||||
# 生成图片
|
||||
bl image generate --prompt "一只穿太空服的猫在火星上" --out-dir ./images/
|
||||
|
||||
# 图生视频(本地文件自动上传)
|
||||
bl video generate --image ./cat.png --prompt "让画面中的猫动起来" --download cat.mp4
|
||||
|
||||
# 模型推荐 — 根据场景推荐最适合的模型
|
||||
bl advisor recommend --message "我要做一个能理解图片的客服机器人"
|
||||
|
||||
# 对比特定模型
|
||||
bl advisor recommend --message "qwen-max 和 deepseek-v3 哪个更适合做代码生成"
|
||||
|
||||
# 浏览器登录(控制台能力相关命令需要)
|
||||
bl auth login --console
|
||||
|
||||
# 微调与部署 — 从训练到服务的一站式流程
|
||||
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
|
||||
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
|
||||
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
|
||||
|
||||
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
|
||||
bl model list # 浏览模型系列与价格信息
|
||||
bl app list
|
||||
bl usage summary # 统一视图:免费额度 + 近期用量概览
|
||||
bl usage free # 各模型免费额度(可加 --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # 模型用量统计(加 --model 查单模型)
|
||||
bl workspace list # 列出所有业务空间
|
||||
|
||||
# 限流管理与提额(list / check / request / history)
|
||||
bl quota list # 查看 RPM/TPM 限额(加 --model 过滤)
|
||||
bl quota check # 当前用量 vs 限流阈值(加 --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # 申请临时 TPM 提额
|
||||
bl quota history # 查看提额历史记录
|
||||
|
||||
# Token Plan 团队版管理(需 AK/SK,见下方认证说明)
|
||||
bl token-plan list-seats # 查看订阅席位明细
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| 场景 | 可以这样对 Agent 说 |
|
||||
| ---------------- | ----------------------------------------------------------------------- |
|
||||
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
|
||||
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
|
||||
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
|
||||
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
|
||||
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
|
||||
|
||||
> 更多案例与使用场景:[阿里云百炼 CLI 官方主页](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## 认证方式
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
大部分命令均需要 API Key。前往 [DashScope 控制台](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key) 获取。
|
||||
|
||||
```bash
|
||||
# 方式一:环境变量
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# 方式二:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 方式三:命令行参数
|
||||
bl text chat --api-key sk-xxxxx --message "你好"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制 API Key。
|
||||
CLI 已内置 Token Plan 的默认 Base URL;登录命令会先测试 Key,通过后才保存并激活 `token-plan` 配置。
|
||||
Token Plan 的 API Key 前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制。
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -184,26 +122,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### 控制台登录(OAuth)
|
||||
|
||||
控制台能力命令(`model list`、`app list`、`usage summary/free/stats`、`workspace list`、`quota list/request/check/history`)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### 阿里云 OpenAPI AK/SK(仅 Token Plan)
|
||||
### 阿里云 OpenAPI AK/SK
|
||||
|
||||
`token-plan` 命令组需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
Token Plan 的席位与成员管理需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
|
||||
> 建议:创建 RAM 子账号并授予最小权限,避免使用主账号 AK/SK。
|
||||
|
||||
```bash
|
||||
# 方式一:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# 方式二:环境变量
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## 配置
|
||||
@@ -212,20 +144,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# 查看当前配置
|
||||
bl config show
|
||||
|
||||
# 设置默认值
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# 查看全部配置档
|
||||
bl config list
|
||||
|
||||
# 自更新到最新版本
|
||||
bl update
|
||||
|
||||
# 安装指定版本
|
||||
bl update --to 0.1.14
|
||||
# 切换配置档
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
配置文件位置:`~/.bailian/config.json`
|
||||
|
||||
## 更新
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
升级 CLI 至最新版本,并同步更新已安装的 Agent Skills。每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
欢迎扫码加入阿里云百炼 CLI 钉钉用户交流群,获取使用答疑、问题排查、Bug 反馈和使用经验交流支持。
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="阿里云百炼 CLI 钉钉用户交流群" width="240" />
|
||||
|
||||
## 相关链接
|
||||
|
||||
| 资源 | 地址 |
|
||||
@@ -237,11 +180,3 @@ bl update --to 0.1.14
|
||||
| 获取 API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| 获取 Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| 获取 AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## 更新日志
|
||||
|
||||
每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
@@ -25,7 +25,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
当前 command 鉴权域(`AuthRequirement`):
|
||||
|
||||
- `apiKey` — DashScope / OpenAI-compatible 模型域,用 API key 与 model base URL
|
||||
- `console` — Bailian Console Gateway,用 console access token + region/site/switchAgent/workspace
|
||||
- `console` — Bailian Console Gateway,用 console access token + region/site/switchAgent;`workspace_id` 是独立的 Settings 作用域,不属于 credential
|
||||
- `openapi` — 阿里云 OpenAPI 签名域,用 AccessKey ID/Secret 调用 Token Plan 等 OpenAPI
|
||||
- `none` — 本地命令、登录/配置类命令、无需 credential 的命令
|
||||
|
||||
@@ -35,7 +35,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
|
||||
- `bl auth login --api-key ...` 只更新 `api_key` / `base_url`
|
||||
- `bl auth login --console` 只更新 `access_token` 以及回调携带的 console 作用域字段
|
||||
- `bl auth login --open-api ...` 只更新 `access_key_id` / `access_key_secret`
|
||||
- `bl auth login --open-api ...` 更新 `access_key_id` / `access_key_secret`,同时会调用 OpenAPI 生成 CLI `access_token` 并一并写入;即一次 `--open-api` 登录同时产生 `openapi` 与 `console` 域凭证
|
||||
- `bl auth logout --console` 只清 `access_token`
|
||||
- `bl auth logout --open-api` 只清 `access_key_id` / `access_key_secret` / `security_token`
|
||||
- `bl auth logout` 清 `api_key` + `base_url` + `access_token` + `access_key_*`
|
||||
@@ -78,6 +78,9 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
- 如新增鉴权域,扩展 `AuthRequirement`
|
||||
- 更新 `credentialFlagDefs()` 暴露该域可见的 flag
|
||||
- 必要时新增 `*_AUTH_FLAGS`
|
||||
- `workspace_id` 是作用域字段而非 credential,不要把它放进 `ConsoleCredential`;读取方式按命令 `auth` 域区分:
|
||||
- `auth: "console"` 命令通过 `CONSOLE_AUTH_FLAGS` 自动获得 `--workspace-id`,由 `buildSettings()` 解析到 `settings.workspaceId`,命令统一从 `settings.workspaceId` 读取
|
||||
- `auth: "apiKey"`/`"openapi"`/`"none"` 命令如需 `--workspace-id`,必须自声明 flag;因它不会进入 credential/global flags,命令从 `ctx.flags.workspaceId` 读取(可回退到 `settings.workspaceId`)
|
||||
- [ ] `packages/core/src/auth/types.ts`:
|
||||
- 新增 credential 类型 / source / scope 字段
|
||||
- [ ] `packages/core/src/auth/resolver.ts`:
|
||||
@@ -131,6 +134,8 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
|
||||
## 完成后自查
|
||||
|
||||
本仓库同时存在 `bl`(packages/cli) 与 `kscli`(packages/kscli) 两个入口,二者共享 core/runtime 鉴权链路,但暴露的命令不同。如果改动会影响两个入口共用的命令或错误提示,再分别验证它们各自实际暴露的路径;不要假设 `kscli` 也有 `bl auth *` 命令。
|
||||
|
||||
```sh
|
||||
# 各种凭证组合
|
||||
unset DASHSCOPE_API_KEY ALIBABA_CLOUD_ACCESS_KEY_ID ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
||||
@@ -150,9 +155,11 @@ Console 登录/网关相关改动:
|
||||
|
||||
```sh
|
||||
pnpm -F bailian-cli exec tsx src/main.ts auth login --console
|
||||
pnpm -F bailian-cli exec tsx src/main.ts usage stats --dry-run --output json
|
||||
pnpm -F bailian-cli exec tsx src/main.ts usage stats --dry-run --output json --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
注意:`usage stats --dry-run` 仍会先校验 workspace,必须传入 `--workspace-id`(或 `BAILIAN_WORKSPACE_ID` / config `workspace_id`)。
|
||||
|
||||
## 常见漏点
|
||||
|
||||
- ✗ 加了新 token 来源但忘了改 resolver 优先级,实际不生效
|
||||
|
||||
+73
-137
@@ -13,8 +13,9 @@
|
||||
|
||||
---
|
||||
|
||||
_Chat with Qwen, generate images & videos, understand images, call agents,_
|
||||
_manage memory, search the web — all from your terminal._
|
||||
_Chat with Qwen, generate and edit images and videos, understand images, synthesize_
|
||||
_and recognize speech, call apps, manage memory, retrieve knowledge, search the web —_
|
||||
_every AI capability, one command away._
|
||||
|
||||
_Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
@@ -22,28 +23,16 @@ _Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
## Features
|
||||
|
||||
Equip your AI Agent out-of-the-box with these capabilities, composable across complex tasks:
|
||||
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
|
||||
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
|
||||
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
|
||||
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
|
||||
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
|
||||
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step
|
||||
|
||||
- **Text chat** — Qwen3.8-max: major gains in agentic coding, frontend coding, and vibe coding
|
||||
- **Multimodal (Omni)** — Full omni-modal support across text + image + audio + video
|
||||
- **Image generation & editing** — Qwen-Image 2.0: pro text rendering, photorealism, strong semantic adherence, multi-image composition
|
||||
- **Video generation & editing** — happyhorse-1.1 series: text-/image-/reference-to-video and natural-language video editing (up to 9-image reference)
|
||||
- **Speech synthesis & recognition** — CosyVoice streaming TTS, voice cloning from 5–20s samples; FunAudio-ASR covers 30 languages including 7 Chinese dialects and 20+ Mandarin accents
|
||||
- **Image & video understanding** — Qwen-VL: long-form video analysis, chart/document parsing, visual reasoning, multilingual OCR
|
||||
- **Coding agent setup** — Configure Claude Code, Qwen Code, OpenCode, OpenClaw, Hermes Agent, or Codex to use DashScope with `bl config agent`
|
||||
> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
> **Note:** The features below are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
- **Knowledge base & memory** — Multimodal RAG retrieval and cross-session memory for personalized, coherent dialogue
|
||||
- **App calls** — Invoke agents and workflows already published on Aliyun Model Studio
|
||||
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
|
||||
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
|
||||
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
|
||||
- **Fine-tuning & deployment** — Upload datasets, create text/audio/image fine-tune jobs (`finetune text|audio|image create`; text covers SFT/LoRA/DPO/CPT), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy text|audio|image create`)
|
||||
- **Console capabilities** — Browse the model marketplace (`model list`) and Bailian apps (`app list`), review a unified usage view (`usage summary`), check free-tier quota (`usage free`), view model usage statistics (`usage stats`), manage workspaces (`workspace list`), and manage rate limits (`quota list/request/check/history`)
|
||||
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
|
||||
|
||||
## Showcase: One-Sentence Cinematic Video
|
||||
## Showcase 1: A Cinematic Short Film from One Sentence
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -56,129 +45,77 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
|
||||
A complete **2-minute, 16:9 cinematic short film** — produced end-to-end from a single natural-language sentence, with **zero manual editing**. This showcase demonstrates how an AI Agent can compose a multi-step creative pipeline by orchestrating three primitives:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — the agentic coding model that interprets the user's intent and drives the workflow
|
||||
- **[Aliyun Model Studio CLI](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** — handles scene decomposition, storyboarding, shot continuity, and final stitching
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Generate a roughly 2-minute video in Japanese cinematic style — a sweet, innocent first-love story about a high-school girl. The plot should be heart-fluttering enough to make viewers want to fall in love. Aspect ratio: 16:9."_
|
||||
>
|
||||
> _(Original: "帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2分钟左右的视频,尺寸是16:9")_
|
||||
|
||||
### How it works
|
||||
## Showcase 2: A Short-Film Director Managed Agent from One Sentence
|
||||
|
||||
1. **Qwen Code** parses the request, plans the narrative beats, and decides which tools to call.
|
||||
2. The **spark-video Skill** breaks the story into shots, writes per-shot prompts, and enforces visual continuity (characters, lighting, palette, lens language).
|
||||
3. **`bl video generate`** dispatches each shot to **HappyHorse 1.1** in parallel.
|
||||
4. The skill stitches all clips back together into a single 16:9 / ~2-min deliverable.
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="Click to play the demo video" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
No timeline scrubbing. No frame-by-frame editing. Just one sentence → one video.
|
||||
<p align="center"><i>👆 Click the cover to play the full demo</i></p>
|
||||
|
||||
One sentence builds a reusable cloud-side short-film director for storyboarding, storyboard image generation, and video creation:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — understands the requirement and generates the agent configuration
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — validates the configuration, previews the changes, and completes the deployment
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** — runs the director role along with its skills and tools in the cloud
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Build me a Managed Agent app that can produce short films — a director expert that generates videos and can also design the matching storyboards."_
|
||||
|
||||
## Installation
|
||||
|
||||
**Agent install (recommended)**
|
||||
|
||||
Send the following to your Agent — it will detect your environment, then install and verify the CLI for you:
|
||||
|
||||
```text
|
||||
Please read https://bailian.aliyun.com/cli/install.md and install the Aliyun Model Studio CLI for me
|
||||
```
|
||||
|
||||
**Manual install (npm)**
|
||||
|
||||
```bash
|
||||
# Recommended — no Node required
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows (PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node users / developers (Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> Binary install does not require Node.js. `npm install -g` remains fully supported.
|
||||
> Requires Node.js >= 18.17.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Authenticate, recommended
|
||||
bl auth login --console
|
||||
Once installed, just describe your task to your AI Agent — no need to assemble commands by hand.
|
||||
|
||||
# Or authenticate with an API key
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Or use Token Plan (Base URL built in; the key is tested during login)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# Configure a coding agent to use DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# Chat with Qwen
|
||||
bl text chat --message "What is DashScope?"
|
||||
|
||||
# Multimodal chat (text + image + audio + video)
|
||||
bl omni --message "Describe this image" --image ./photo.jpg
|
||||
|
||||
# Generate an image
|
||||
bl image generate --prompt "A cat in a spacesuit" --out-dir ./images/
|
||||
|
||||
# Generate a video from local image
|
||||
bl video generate --image ./cat.png --prompt "Make the cat move" --download cat.mp4
|
||||
|
||||
# Model recommendation — find the best model for your use case
|
||||
bl advisor recommend --message "I need a visual-understanding chatbot"
|
||||
|
||||
# Compare specific models
|
||||
bl advisor recommend --message "qwen-max vs deepseek-v3 for code generation"
|
||||
|
||||
# Browser login (required for console capability commands)
|
||||
bl auth login --console
|
||||
|
||||
# Fine-tune & deploy — a one-shot train-to-serve workflow
|
||||
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
|
||||
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
|
||||
bl finetune capability --model qwen3-8b # Which training types a model supports
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
|
||||
|
||||
# Browse models / apps / free-tier quota / usage statistics / workspaces
|
||||
bl model list # Browse model families and pricing
|
||||
bl app list
|
||||
bl usage summary # Unified view: free-tier quota + recent usage overview
|
||||
bl usage free # Free-tier quota across models (add --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # Model usage statistics (add --model for per-model)
|
||||
bl workspace list # List all workspaces
|
||||
|
||||
# Rate limit management (list / check / request / history)
|
||||
bl quota list # View RPM/TPM limits (add --model to filter)
|
||||
bl quota check # Current usage vs rate limits (add --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # Request a temporary TPM increase
|
||||
bl quota history # View quota-change history
|
||||
|
||||
# Token Plan team management (requires AK/SK, see auth below)
|
||||
bl token-plan list-seats # View subscription seat details
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| Scenario | What to say to your Agent |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- |
|
||||
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
|
||||
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
|
||||
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
|
||||
| Model selection | "Recommend a model for image understanding and customer support." |
|
||||
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
|
||||
|
||||
> More examples and scenarios: [Aliyun Model Studio CLI Site](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## Authentication
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
Required for most commands. Get your key from the [DashScope Console](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key).
|
||||
|
||||
```bash
|
||||
# Option 1: Environment variable
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# Option 2: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Option 3: Per-command flag
|
||||
bl text chat --api-key sk-xxxxx --message "Hello"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
Get or copy the API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
The CLI has the default Token Plan Base URL built in. Login tests the key first, then saves and activates the `token-plan` config only when validation succeeds.
|
||||
Get or copy your Token Plan API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -186,26 +123,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### Console Login (OAuth)
|
||||
|
||||
Required for console capability commands (`model list`, `app list`, `usage summary/free/stats`, `workspace list`, `quota list/request/check/history`). Opens the Bailian console in your browser to sign in.
|
||||
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### Alibaba Cloud OpenAPI AK/SK (Token Plan only)
|
||||
### Alibaba Cloud OpenAPI AK/SK
|
||||
|
||||
Required for the `token-plan` command group. Get your AccessKey from [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
Token Plan seat and member management requires an Alibaba Cloud AccessKey. Get yours from the [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
|
||||
> Recommended: create a RAM sub-account with minimum privileges instead of using the root account's AK/SK.
|
||||
|
||||
```bash
|
||||
# Option 1: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# Option 2: Environment variables
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -214,18 +145,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# View current config
|
||||
bl config show
|
||||
|
||||
# Set defaults
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# List all config profiles
|
||||
bl config list
|
||||
|
||||
# Self-update to latest or a specific version
|
||||
bl update
|
||||
bl update --to 0.1.14
|
||||
# Switch config profile
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
Config file location: `~/.bailian/config.json`
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
Upgrades the CLI to the latest version and refreshes the installed Agent Skills. Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
Scan the QR code to join the Aliyun Model Studio CLI DingTalk user group for usage help, troubleshooting, bug reports, and tips from other users.
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="Aliyun Model Studio CLI DingTalk user group" width="240" />
|
||||
|
||||
## Links
|
||||
|
||||
| Resource | URL |
|
||||
@@ -237,11 +181,3 @@ Config file location: `~/.bailian/config.json`
|
||||
| Get API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| Get Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| Get AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## Changelog
|
||||
|
||||
Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
+73
-138
@@ -22,28 +22,16 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
## 功能特性
|
||||
|
||||
让您的 AI Agent 开箱即具备以下能力,并可在复杂任务中自动组合调用:
|
||||
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
|
||||
- **素材理解** — 图像、文档、音频、长视频的解析与问答
|
||||
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
|
||||
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
|
||||
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
|
||||
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent
|
||||
|
||||
- **文本对话** — Qwen3.8-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
|
||||
- **全模态对话** — 文本 + 图像 + 音频 + 视频全模态支持
|
||||
- **图像生成与编辑** — Qwen-Image 2.0:专业文字渲染、真实质感、强语义遵循、多图合成
|
||||
- **视频生成与编辑** — happyhorse-1.1 系列,支持文生 / 图生 / 参考生(最多 9 张图参考)/ 自然语言视频编辑
|
||||
- **语音合成与识别** — CosyVoice 实时流式合成,5-20s 样本即可克隆;FunAudio-ASR 覆盖 30 种语种,含汉语七大方言与 20+ 口音官话
|
||||
- **图像与视频理解** — Qwen-VL:长视频解析、复杂图表与文档识别、视觉推理、多语种 OCR
|
||||
- **Coding Agent 配置** — 使用 `bl config agent` 将 Claude Code、Qwen Code、OpenCode、OpenClaw、Hermes Agent 或 Codex 配置为使用 DashScope
|
||||
> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
|
||||
|
||||
> **注意:** 以下功能目前仅对中国站(aliyun.com)账号开放,国际站 / 全球站账号暂不支持。
|
||||
|
||||
- **知识库与记忆库** — 多模态 RAG 检索 + 跨会话记忆,提供个性化连贯对话体验
|
||||
- **应用调用** — 调用已发布在阿里云百炼平台上的智能体与工作流应用
|
||||
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
|
||||
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
|
||||
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
|
||||
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务(`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`)
|
||||
- **控制台能力** — 浏览模型市场(`model list`)和百炼应用(`app list`),查看统一用量视图(`usage summary`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`)
|
||||
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
|
||||
|
||||
## 示例:一句话生成一部电影短片
|
||||
## 示例 1:一句话生成一部电影短片
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -53,130 +41,80 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
<p align="center"><i>👆 点击封面播放完整 2 分钟演示</i></p>
|
||||
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** —— 负责场景拆分、分镜设计、镜头连贯性和最终拼接
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _"帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9"_
|
||||
> _“帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9。”_
|
||||
|
||||
### 工作流程
|
||||
## 示例 2:一句话构建短片导演 Managed Agent
|
||||
|
||||
1. **Qwen Code** 解析需求、规划叙事节奏,决定要调用哪些工具。
|
||||
2. **spark-video Skill** 把故事拆成镜头、为每个镜头写提示词,并保证视觉连贯性(角色、光线、色调、镜头语言)。
|
||||
3. **`bl video generate`** 把每个镜头并行下发给 **HappyHorse 1.1**。
|
||||
4. Skill 把所有片段拼成最终的 16:9 / 约 2 分钟成片。
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="点击播放演示视频" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
没有时间线拖拽,没有逐帧剪辑。一句话 → 一部短片。
|
||||
<p align="center"><i>👆 点击封面播放完整演示</i></p>
|
||||
|
||||
一句话构建一个可复用的云端短片导演,用于分镜设计、分镜图生成和视频创作:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— 理解需求并生成 Agent 配置
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 校验配置、预览变更并完成部署
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** —— 在云端运行导演角色及其 Skill 和工具
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _“帮我构建一个 managedagent 应用,能够实现短片拍摄,导演专家生成视频,然后也能进行设计对应的分镜图。”_
|
||||
|
||||
## 安装
|
||||
|
||||
**Agent 安装(推荐)**
|
||||
|
||||
把下面这句话发给你的 Agent,它会自行判断环境并完成安装与校验:
|
||||
|
||||
```text
|
||||
请阅读:https://bailian.aliyun.com/cli/install.md 并按照说明为我安装阿里云百炼 CLI
|
||||
```
|
||||
|
||||
**手动安装(npm)**
|
||||
|
||||
```bash
|
||||
# 推荐 — 无需本机 Node.js
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows(PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node 用户 / 开发者(需要 Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> 二进制安装不依赖 Node.js。`npm install -g` 长期保留。
|
||||
> 需要预先安装 Node.js >= 18.17。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 认证(推荐浏览器登录)
|
||||
bl auth login --console
|
||||
安装完成后,直接在 AI Agent 中描述你的任务,无需手动拼接命令。
|
||||
|
||||
# 或使用 API key 认证
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 或使用 Token Plan(已内置 Base URL,登录时自动测试 Key)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# 配置 Coding Agent 使用 DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# 和通义千问对话
|
||||
bl text chat --message "你好,介绍一下阿里云百炼平台"
|
||||
|
||||
# 多模态对话(文本 + 图片 + 音频 + 视频)
|
||||
bl omni --message "描述这张图片" --image ./photo.jpg
|
||||
|
||||
# 生成图片
|
||||
bl image generate --prompt "一只穿太空服的猫在火星上" --out-dir ./images/
|
||||
|
||||
# 图生视频(本地文件自动上传)
|
||||
bl video generate --image ./cat.png --prompt "让画面中的猫动起来" --download cat.mp4
|
||||
|
||||
# 模型推荐 — 根据场景推荐最适合的模型
|
||||
bl advisor recommend --message "我要做一个能理解图片的客服机器人"
|
||||
|
||||
# 对比特定模型
|
||||
bl advisor recommend --message "qwen-max 和 deepseek-v3 哪个更适合做代码生成"
|
||||
|
||||
# 浏览器登录(控制台能力相关命令需要)
|
||||
bl auth login --console
|
||||
|
||||
# 微调与部署 — 从训练到服务的一站式流程
|
||||
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
|
||||
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
|
||||
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
|
||||
|
||||
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
|
||||
bl model list # 浏览模型系列与价格信息
|
||||
bl app list
|
||||
bl usage summary # 统一视图:免费额度 + 近期用量概览
|
||||
bl usage free # 各模型免费额度(可加 --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # 模型用量统计(加 --model 查单模型)
|
||||
bl workspace list # 列出所有业务空间
|
||||
|
||||
# 限流管理与提额(list / check / request / history)
|
||||
bl quota list # 查看 RPM/TPM 限额(加 --model 过滤)
|
||||
bl quota check # 当前用量 vs 限流阈值(加 --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # 申请临时 TPM 提额
|
||||
bl quota history # 查看提额历史记录
|
||||
|
||||
# Token Plan 团队版管理(需 AK/SK,见下方认证说明)
|
||||
bl token-plan list-seats # 查看订阅席位明细
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| 场景 | 可以这样对 Agent 说 |
|
||||
| ---------------- | ----------------------------------------------------------------------- |
|
||||
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
|
||||
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
|
||||
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
|
||||
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
|
||||
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
|
||||
|
||||
> 更多案例与使用场景:[阿里云百炼 CLI 官方主页](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## 认证方式
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
大部分命令均需要 API Key。前往 [DashScope 控制台](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key) 获取。
|
||||
|
||||
```bash
|
||||
# 方式一:环境变量
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# 方式二:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 方式三:命令行参数
|
||||
bl text chat --api-key sk-xxxxx --message "你好"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制 API Key。
|
||||
CLI 已内置 Token Plan 的默认 Base URL;登录命令会先测试 Key,通过后才保存并激活 `token-plan` 配置。
|
||||
Token Plan 的 API Key 前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制。
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -184,26 +122,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### 控制台登录(OAuth)
|
||||
|
||||
控制台能力命令(`model list`、`app list`、`usage summary/free/stats`、`workspace list`、`quota list/request/check/history`)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### 阿里云 OpenAPI AK/SK(仅 Token Plan)
|
||||
### 阿里云 OpenAPI AK/SK
|
||||
|
||||
`token-plan` 命令组需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
Token Plan 的席位与成员管理需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
|
||||
> 建议:创建 RAM 子账号并授予最小权限,避免使用主账号 AK/SK。
|
||||
|
||||
```bash
|
||||
# 方式一:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# 方式二:环境变量
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## 配置
|
||||
@@ -212,20 +144,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# 查看当前配置
|
||||
bl config show
|
||||
|
||||
# 设置默认值
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# 查看全部配置档
|
||||
bl config list
|
||||
|
||||
# 自更新到最新版本
|
||||
bl update
|
||||
|
||||
# 安装指定版本
|
||||
bl update --to 0.1.14
|
||||
# 切换配置档
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
配置文件位置:`~/.bailian/config.json`
|
||||
|
||||
## 更新
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
升级 CLI 至最新版本,并同步更新已安装的 Agent Skills。每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
欢迎扫码加入阿里云百炼 CLI 钉钉用户交流群,获取使用答疑、问题排查、Bug 反馈和使用经验交流支持。
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="阿里云百炼 CLI 钉钉用户交流群" width="240" />
|
||||
|
||||
## 相关链接
|
||||
|
||||
| 资源 | 地址 |
|
||||
@@ -237,11 +180,3 @@ bl update --to 0.1.14
|
||||
| 获取 API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| 获取 Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| 获取 AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## 更新日志
|
||||
|
||||
每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
|
||||
"keywords": [
|
||||
"agent",
|
||||
|
||||
@@ -93,10 +93,9 @@ import {
|
||||
skillUpdate,
|
||||
skillRemove,
|
||||
skillList,
|
||||
skillInit,
|
||||
managedAgentInit,
|
||||
managedAgentValidate,
|
||||
managedAgentSync,
|
||||
managedAgentMigrate,
|
||||
managedAgentPlan,
|
||||
managedAgentApply,
|
||||
managedAgentDestroy,
|
||||
@@ -213,10 +212,9 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"skill update": skillUpdate,
|
||||
"skill remove": skillRemove,
|
||||
"skill list": skillList,
|
||||
"skill init": skillInit,
|
||||
"managed-agent init": managedAgentInit,
|
||||
"managed-agent validate": managedAgentValidate,
|
||||
"managed-agent sync": managedAgentSync,
|
||||
"managed-agent migrate": managedAgentMigrate,
|
||||
"managed-agent plan": managedAgentPlan,
|
||||
"managed-agent apply": managedAgentApply,
|
||||
"managed-agent destroy": managedAgentDestroy,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-commands",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -61,7 +61,7 @@ export const UI_BOOLEAN_KEYS = new Set<string>(["telemetry"]);
|
||||
// without persisting a value that would pin the model.
|
||||
export const UI_MODEL_DEFAULTS: Record<string, string> = {
|
||||
default_text_model: "qwen3.8-max",
|
||||
default_image_model: "qwen-image-2.0",
|
||||
default_image_model: "qwen-image-3.0",
|
||||
default_video_model: "happyhorse-1.1-t2v",
|
||||
default_speech_model: "cosyvoice-v3-flash",
|
||||
default_omni_model: "qwen3.5-omni-plus",
|
||||
@@ -86,7 +86,8 @@ export const UI_MODEL_CATALOG: Record<string, ModelOption[]> = {
|
||||
{ id: "qwen3.6-flash", role: "fast · advisor intent" },
|
||||
],
|
||||
default_image_model: [
|
||||
{ id: "qwen-image-2.0", role: "image/generate default · sync" },
|
||||
{ id: "qwen-image-3.0", role: "image/generate default · sync" },
|
||||
{ id: "qwen-image-2.0", role: "image/generate · sync" },
|
||||
{ id: "qwen-image-max", role: "image/generate · sync" },
|
||||
{ id: "qwen-image-edit-2.0", role: "image/edit · sync" },
|
||||
{ id: "wanx2.x", role: "image/generate · async series" },
|
||||
|
||||
@@ -23,7 +23,7 @@ export default defineCommand({
|
||||
"--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",
|
||||
"--file cat.png --model qwen-image-3.0",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
|
||||
@@ -47,7 +47,7 @@ const EDIT_FLAGS = {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: qwen-image-2.0)",
|
||||
description: "Model ID (default: qwen-image-3.0)",
|
||||
},
|
||||
size: {
|
||||
type: "string",
|
||||
@@ -123,7 +123,7 @@ export default defineCommand({
|
||||
}
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model = flags.model || settings.defaultImageModel || "qwen-image-2.0";
|
||||
const model = flags.model || settings.defaultImageModel || "qwen-image-3.0";
|
||||
const route = resolveImageEditApi(model);
|
||||
|
||||
// Auto-upload local files (resolve all images in parallel)
|
||||
|
||||
@@ -35,7 +35,7 @@ const GENERATE_FLAGS = {
|
||||
model: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Model ID (default: qwen-image-2.0)",
|
||||
description: "Model ID (default: qwen-image-3.0)",
|
||||
},
|
||||
size: {
|
||||
type: "string",
|
||||
@@ -105,7 +105,7 @@ export default defineCommand({
|
||||
const { settings, flags } = ctx;
|
||||
const prompt = flags.prompt;
|
||||
|
||||
const model = flags.model || settings.defaultImageModel || "qwen-image-2.0";
|
||||
const model = flags.model || settings.defaultImageModel || "qwen-image-3.0";
|
||||
const route = resolveImageGenerateApi(model);
|
||||
const defaultSize = "1:1";
|
||||
const sizeInput = flags.size || defaultSize;
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
import {
|
||||
listCloudEnvironments,
|
||||
listCloudVaults,
|
||||
listFiles,
|
||||
listSkills,
|
||||
type ProjectRuntimeContext,
|
||||
type SyncProjectResult,
|
||||
} from "@openagentpack/sdk";
|
||||
import { BailianError, ExitCode } from "bailian-cli-core";
|
||||
|
||||
/** Resource types the SDK can reverse-export (its syncable whitelist). */
|
||||
export const SYNCABLE_TYPES = ["environment", "vault", "file", "skill", "agent"] as const;
|
||||
|
||||
export type SyncableType = (typeof SYNCABLE_TYPES)[number];
|
||||
|
||||
/**
|
||||
* Types selectable by remote ID through the shared resolution below. Agents are
|
||||
* excluded: their synced yaml keys ARE remote IDs, so the sync command narrows
|
||||
* them with a direct key hit (plus referenced-skill handling) instead.
|
||||
*/
|
||||
export type SelectableResourceType = Exclude<SyncableType, "agent">;
|
||||
|
||||
/** Maps a syncable type to its top-level group key in the synced config. */
|
||||
const GROUP_KEY: Record<SyncableType, string> = {
|
||||
environment: "environments",
|
||||
vault: "vaults",
|
||||
file: "files",
|
||||
skill: "skills",
|
||||
agent: "agents",
|
||||
};
|
||||
|
||||
/** Remote id lookup outcome: human label + optional `agents.resource` yaml key tag. */
|
||||
interface LocatedRemote {
|
||||
label?: string;
|
||||
taggedKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow one resource group of a synced config to the single entry identified
|
||||
* by its remote ID; returns the kept yaml key.
|
||||
*
|
||||
* Group keys are only guaranteed to be remote IDs for agents; the other types
|
||||
* derive their key from the `agents.resource` metadata tag or a display-name
|
||||
* slug. Resolution therefore tries, in order: direct key hit → remote lookup
|
||||
* by ID (provider list API) → metadata-tag key → label match against the
|
||||
* exported declarations. Misses and ambiguity fail loudly instead of guessing.
|
||||
*/
|
||||
export async function narrowGroupToRemoteId(
|
||||
runtime: ProjectRuntimeContext,
|
||||
provider: string,
|
||||
result: SyncProjectResult,
|
||||
type: SelectableResourceType,
|
||||
remoteId: string,
|
||||
): Promise<string> {
|
||||
const groupKey = GROUP_KEY[type];
|
||||
const group = (result.config[groupKey] ?? {}) as Record<string, Record<string, unknown>>;
|
||||
|
||||
let keptKey: string;
|
||||
if (remoteId in group) {
|
||||
keptKey = remoteId;
|
||||
} else {
|
||||
const located = await locateRemote(runtime, provider, type, remoteId);
|
||||
const resolved =
|
||||
located.taggedKey && located.taggedKey in group
|
||||
? located.taggedKey
|
||||
: matchByLabel(type, group, located.label);
|
||||
if (!resolved) {
|
||||
throw new BailianError(
|
||||
`Remote ${type} '${remoteId}'${located.label ? ` (${located.label})` : ""} has no matching entry in the synced output.`,
|
||||
ExitCode.GENERAL,
|
||||
"The resource may be archived or renamed; run a full sync (without the id flag) to inspect the exported keys.",
|
||||
);
|
||||
}
|
||||
keptKey = resolved;
|
||||
}
|
||||
|
||||
result.config[groupKey] = { [keptKey]: group[keptKey]! };
|
||||
if (type in result.counts) result.counts[type] = 1;
|
||||
if (type === "skill" && result.skillFiles) {
|
||||
for (const skillName of result.skillFiles.keys()) {
|
||||
if (skillName !== keptKey) result.skillFiles.delete(skillName);
|
||||
}
|
||||
}
|
||||
return keptKey;
|
||||
}
|
||||
|
||||
/** Look a remote resource up by ID via the provider's list API. */
|
||||
async function locateRemote(
|
||||
runtime: ProjectRuntimeContext,
|
||||
provider: string,
|
||||
type: SelectableResourceType,
|
||||
remoteId: string,
|
||||
): Promise<LocatedRemote> {
|
||||
if (type === "environment") {
|
||||
const environments = await listCloudEnvironments(runtime, { provider });
|
||||
const hit = environments.find((environment) => environment.id === remoteId);
|
||||
if (!hit) {
|
||||
throw notFound(
|
||||
type,
|
||||
remoteId,
|
||||
environments.map((environment) => formatCandidate(environment.id, environment.name)),
|
||||
);
|
||||
}
|
||||
return { label: hit.name, taggedKey: hit.metadata?.["agents.resource"] };
|
||||
}
|
||||
if (type === "vault") {
|
||||
const vaults = await listCloudVaults(runtime, { provider });
|
||||
const hit = vaults.find((vault) => vault.id === remoteId);
|
||||
if (!hit) {
|
||||
throw notFound(
|
||||
type,
|
||||
remoteId,
|
||||
vaults.map((vault) => formatCandidate(vault.id, vault.display_name)),
|
||||
);
|
||||
}
|
||||
return { label: hit.display_name, taggedKey: hit.metadata?.["agents.resource"] };
|
||||
}
|
||||
if (type === "file") {
|
||||
const files = await listFiles(runtime, { provider });
|
||||
const hit = files.find((fileInfo) => fileInfo.id === remoteId);
|
||||
if (!hit) {
|
||||
throw notFound(
|
||||
type,
|
||||
remoteId,
|
||||
files.map((fileInfo) => formatCandidate(fileInfo.id, fileInfo.filename)),
|
||||
);
|
||||
}
|
||||
return { label: hit.filename };
|
||||
}
|
||||
// Synced skills come from the workspace's custom catalog (the raw /skills listing).
|
||||
const skills = await listSkills(runtime, { provider, source: "custom" });
|
||||
const hit = skills.find((skill) => skill.id === remoteId);
|
||||
if (!hit) {
|
||||
throw notFound(
|
||||
type,
|
||||
remoteId,
|
||||
skills.map((skill) => formatCandidate(skill.id, skill.name)),
|
||||
);
|
||||
}
|
||||
return { label: hit.name };
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-resolve remote skill IDs to synced skills-group keys via the custom
|
||||
* skill catalog: look each ID up for its display name, then match group keys
|
||||
* on normalized label (skills keys are display-name slugs). IDs that cannot be
|
||||
* resolved — or whose label matches more than one key — come back as
|
||||
* unmatched for the caller to surface. One catalog call serves the whole batch.
|
||||
*/
|
||||
export async function resolveSkillKeysByIds(
|
||||
runtime: ProjectRuntimeContext,
|
||||
provider: string,
|
||||
group: Record<string, Record<string, unknown>>,
|
||||
remoteIds: string[],
|
||||
): Promise<{ resolved: Map<string, string>; unmatched: string[] }> {
|
||||
const resolved = new Map<string, string>();
|
||||
const unmatched: string[] = [];
|
||||
if (remoteIds.length === 0) return { resolved, unmatched };
|
||||
|
||||
const catalog = await listSkills(runtime, { provider, source: "custom" });
|
||||
for (const remoteId of remoteIds) {
|
||||
const hit = catalog.find((skill) => skill.id === remoteId);
|
||||
const matches = hit
|
||||
? Object.keys(group).filter((key) => normalizeLabel(key) === normalizeLabel(hit.name))
|
||||
: [];
|
||||
if (matches.length === 1) {
|
||||
resolved.set(remoteId, matches[0]!);
|
||||
} else {
|
||||
unmatched.push(remoteId);
|
||||
}
|
||||
}
|
||||
return { resolved, unmatched };
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a group entry by the remote resource's human label. Vaults and files
|
||||
* carry the label verbatim in their exported decl; environment and skill keys
|
||||
* are display-name slugs, so those compare on normalized alphanumerics.
|
||||
*/
|
||||
function matchByLabel(
|
||||
type: SelectableResourceType,
|
||||
group: Record<string, Record<string, unknown>>,
|
||||
label: string | undefined,
|
||||
): string | undefined {
|
||||
if (!label) return undefined;
|
||||
const matches = Object.entries(group).filter(([key, decl]) => {
|
||||
if (type === "vault") return decl.display_name === label;
|
||||
if (type === "file") return decl.name === label || decl.source === label;
|
||||
return normalizeLabel(key) === normalizeLabel(label);
|
||||
});
|
||||
if (matches.length > 1) {
|
||||
throw new BailianError(
|
||||
`Multiple synced ${type} entries match '${label}': ${matches.map(([key]) => key).join(", ")}.`,
|
||||
ExitCode.GENERAL,
|
||||
"Run a full sync (without the id flag) and narrow the output manually.",
|
||||
);
|
||||
}
|
||||
return matches[0]?.[0];
|
||||
}
|
||||
|
||||
/** Compare display labels and slug-derived keys on lowercase alphanumerics only. */
|
||||
function normalizeLabel(value: string): string {
|
||||
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
function notFound(type: string, remoteId: string, candidates: string[]): BailianError {
|
||||
const shown = candidates.slice(0, 20);
|
||||
const suffix =
|
||||
candidates.length > shown.length ? `, … ${candidates.length - shown.length} more` : "";
|
||||
return new BailianError(
|
||||
`${type} '${remoteId}' not found on the remote workspace.`,
|
||||
ExitCode.USAGE,
|
||||
candidates.length > 0
|
||||
? `Available: ${shown.join(", ")}${suffix}.`
|
||||
: `The workspace has no syncable ${type} resources.`,
|
||||
);
|
||||
}
|
||||
|
||||
function formatCandidate(id: string, label?: string): string {
|
||||
return label && label !== id ? `${id} (${label})` : id;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import {
|
||||
BailianError,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import { migrateConfig } from "@openagentpack/sdk";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { withAgentErrors } from "./_engine/errors.ts";
|
||||
|
||||
/** bl's migrate is bailian-only: the merge target must resolve to bailian. */
|
||||
const MIGRATE_PROVIDER = "bailian";
|
||||
|
||||
const MIGRATE_FLAGS = {
|
||||
from: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Synced config to migrate from (default: agents.synced.yaml)",
|
||||
},
|
||||
to: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Target agents.yaml to merge into (default: agents.yaml)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Merge a synced config into a bailian agents.yaml",
|
||||
auth: "apiKey",
|
||||
usageArgs: "[--from <path>] [--to <path>]",
|
||||
flags: MIGRATE_FLAGS,
|
||||
exampleArgs: ["", "--from agents.synced.yaml --to agents.yaml"],
|
||||
notes: [
|
||||
"The merge itself runs against local files; bl's unified apiKey gate still applies — login via `bl auth login`, pass --api-key, or set DASHSCOPE_API_KEY.",
|
||||
"Only a bailian-target agents.yaml is supported: migrated resources are re-pointed to provider bailian, with models/tools/environments normalized to Bailian-supported values.",
|
||||
"Resources whose YAML key already exists in the target are skipped, never overwritten.",
|
||||
"Run `bl managed-agent plan` afterwards to review the merged config before apply.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const fromPath = flags.from ?? "agents.synced.yaml";
|
||||
const toPath = flags.to ?? "agents.yaml";
|
||||
|
||||
// Aligned with the other managed-agent commands: dry-run short-circuits
|
||||
// first and only echoes the planned action — no file I/O, no validation.
|
||||
if (settings.dryRun) {
|
||||
emitResult({ would_migrate: { from: fromPath, to: toPath } }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await withAgentErrors(async () => {
|
||||
await assertBailianTarget(toPath);
|
||||
return migrateConfig({ fromPath, toPath });
|
||||
});
|
||||
|
||||
await writeFile(toPath, result.yaml, "utf8");
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{ migrated: toPath, from: fromPath, added: result.added, skipped: result.skipped },
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const addedParts = Object.entries(result.added).map(([group, count]) => `${count} ${group}`);
|
||||
const skippedParts = Object.entries(result.skipped).map(
|
||||
([group, count]) => `${count} ${group}`,
|
||||
);
|
||||
|
||||
if (addedParts.length > 0) {
|
||||
emitBare(`Migrated ${addedParts.join(", ")} into ${toPath}.`);
|
||||
} else {
|
||||
emitBare("No new resources to migrate (all already exist in target).");
|
||||
}
|
||||
if (skippedParts.length > 0) {
|
||||
emitBare(`Skipped (already exist): ${skippedParts.join(", ")}.`);
|
||||
}
|
||||
if (addedParts.length > 0) {
|
||||
// `bl` prefix is safe: agent commands ship on `bl` only.
|
||||
emitBare("Next: run `bl managed-agent plan` to review the merged config.");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Enforce the bailian-only contract before merging: the target file must exist
|
||||
* and its provider (defaults.provider, else the first providers key) must be
|
||||
* bailian. An undeterminable provider is left to the SDK's own error.
|
||||
*/
|
||||
async function assertBailianTarget(toPath: string): Promise<void> {
|
||||
if (!existsSync(toPath)) {
|
||||
throw new BailianError(
|
||||
`Target file '${toPath}' not found.`,
|
||||
ExitCode.USAGE,
|
||||
// `bl` prefix is safe: agent commands ship on `bl` only.
|
||||
"Create it first with `bl managed-agent init`, then re-run migrate.",
|
||||
);
|
||||
}
|
||||
const parsed: unknown = parseYaml(await readFile(toPath, "utf8"));
|
||||
if (!parsed || typeof parsed !== "object") return;
|
||||
const config = parsed as Record<string, unknown>;
|
||||
|
||||
const defaults = config.defaults as Record<string, unknown> | undefined;
|
||||
const providers = config.providers as Record<string, unknown> | undefined;
|
||||
const targetProvider =
|
||||
typeof defaults?.provider === "string" && defaults.provider
|
||||
? defaults.provider
|
||||
: Object.keys(providers ?? {})[0];
|
||||
|
||||
if (targetProvider && targetProvider !== MIGRATE_PROVIDER) {
|
||||
throw new BailianError(
|
||||
`Target provider '${targetProvider}' is not supported: migrate only targets the ${MIGRATE_PROVIDER} provider.`,
|
||||
ExitCode.USAGE,
|
||||
`Set defaults.provider to ${MIGRATE_PROVIDER} (or make ${MIGRATE_PROVIDER} the providers block) in '${toPath}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
import { existsSync, readdirSync, statSync } from "node:fs";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import {
|
||||
BailianError,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import {
|
||||
type ProjectRuntimeContext,
|
||||
type SyncProjectResult,
|
||||
syncProviderResourcesFromContext,
|
||||
} from "@openagentpack/sdk";
|
||||
import { stringify as stringifyYaml } from "yaml";
|
||||
import {
|
||||
assertProviderConfigured,
|
||||
buildAgentRuntime,
|
||||
CREDENTIALS_NOTE,
|
||||
} from "./_engine/config-loader.ts";
|
||||
import { withStdoutProtected } from "./_engine/console-capture.ts";
|
||||
import { withAgentErrors } from "./_engine/errors.ts";
|
||||
import {
|
||||
narrowGroupToRemoteId,
|
||||
resolveSkillKeysByIds,
|
||||
type SelectableResourceType,
|
||||
SYNCABLE_TYPES,
|
||||
type SyncableType,
|
||||
} from "./_engine/sync-selection.ts";
|
||||
|
||||
/** bl's sync is bailian-only: the reverse-export source is always AgentStudio. */
|
||||
const SYNC_PROVIDER = "bailian";
|
||||
|
||||
const DEFAULT_SYNC_OUTPUT = "agents.synced.yaml";
|
||||
|
||||
const SYNC_FLAGS = {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Config file path (default: agents.yaml)",
|
||||
},
|
||||
out: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: `Output path for the synced config (default: ${DEFAULT_SYNC_OUTPUT})`,
|
||||
},
|
||||
types: {
|
||||
type: "string",
|
||||
valueHint: "<list>",
|
||||
description: `Comma-separated resource types to sync: ${SYNCABLE_TYPES.join(", ")} (default: all)`,
|
||||
},
|
||||
agentId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Sync a single agent by its remote ID (plus the skills it references)",
|
||||
},
|
||||
environmentId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Sync a single environment by its remote ID",
|
||||
},
|
||||
vaultId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Sync a single vault by its remote ID",
|
||||
},
|
||||
fileId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Sync a single file resource by its remote ID",
|
||||
},
|
||||
skillId: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Sync a single skill by its remote ID (overrides --agent-id skill narrowing)",
|
||||
},
|
||||
force: {
|
||||
type: "switch",
|
||||
description: "Overwrite an existing output file",
|
||||
},
|
||||
skipMissingFiles: {
|
||||
type: "switch",
|
||||
description: "Drop file resources whose local source is missing instead of keeping them",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Export remote bailian resources into a local synced config",
|
||||
auth: "apiKey",
|
||||
usageArgs:
|
||||
"[--types <list>] [--agent-id|--environment-id|--vault-id|--file-id|--skill-id <id>] [--file <path>] [--out <path>] [--force] [--skip-missing-files]",
|
||||
flags: SYNC_FLAGS,
|
||||
exampleArgs: [
|
||||
"",
|
||||
"--types agent,skill",
|
||||
"--agent-id agent-abc123",
|
||||
"--skill-id skill-xyz --types skill",
|
||||
"--force --skip-missing-files",
|
||||
],
|
||||
notes: [
|
||||
...CREDENTIALS_NOTE,
|
||||
"Syncs from the bailian provider only: remote AgentStudio resources (environments, vaults, files, skills, agents) are exported into a local synced config for review.",
|
||||
"--types narrows the export to the listed resource types (plural spellings accepted); sessions are runtime instances, not syncable resources.",
|
||||
"With --agent-id, the agents group keeps only that agent (yaml key = remote agent ID) and its referenced custom skills are synced along — the skill group is exported even when --types omits it; official skill references need no local entry.",
|
||||
"Bailian binds environments/vaults/files at the session level, not on the agent, so --agent-id keeps those groups as shared infrastructure.",
|
||||
"Each --environment-id/--vault-id/--file-id/--skill-id narrows its own resource group to the single remote resource (yaml key resolved from the remote listing); combine with --types for a minimal output.",
|
||||
"Requires an agents.yaml with a bailian provider block — run `bl managed-agent init` first.",
|
||||
"Secrets are never exported: vault credentials keep ${ENV} placeholders; set those env vars locally before apply.",
|
||||
"Merge the synced config into agents.yaml with `bl managed-agent migrate`.",
|
||||
],
|
||||
validate: (flagValues) => {
|
||||
if (!flagValues.types) return undefined;
|
||||
const { types, invalid } = parseSyncTypes(flagValues.types);
|
||||
if (invalid.length > 0) {
|
||||
return `--types contains unsupported values: ${invalid.join(", ")}. Valid types: ${SYNCABLE_TYPES.join(", ")}.`;
|
||||
}
|
||||
if (types.length === 0) {
|
||||
return `--types must list at least one of: ${SYNCABLE_TYPES.join(", ")}.`;
|
||||
}
|
||||
const idFlagByType: Record<SyncableType, string | undefined> = {
|
||||
agent: flagValues.agentId,
|
||||
environment: flagValues.environmentId,
|
||||
vault: flagValues.vaultId,
|
||||
file: flagValues.fileId,
|
||||
skill: flagValues.skillId,
|
||||
};
|
||||
for (const [selectionType, value] of Object.entries(idFlagByType)) {
|
||||
if (value && !types.includes(selectionType as SyncableType)) {
|
||||
return `--${selectionType}-id requires --types to include ${selectionType}.`;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const file = flags.file ?? "agents.yaml";
|
||||
const out = flags.out ?? DEFAULT_SYNC_OUTPUT;
|
||||
// undefined → the SDK exports every syncable type.
|
||||
let types = flags.types ? parseSyncTypes(flags.types).types : undefined;
|
||||
// --agent-id pulls the agent's referenced skills along, so the skill group
|
||||
// is exported even when --types omits it.
|
||||
if (types && flags.agentId && !types.includes("skill")) types = [...types, "skill"];
|
||||
// Per-group single-resource selections, resolved by remote ID after export.
|
||||
const resourceSelections: Partial<Record<SelectableResourceType, string>> = {
|
||||
environment: flags.environmentId,
|
||||
vault: flags.vaultId,
|
||||
file: flags.fileId,
|
||||
skill: flags.skillId,
|
||||
};
|
||||
|
||||
// Aligned with the other managed-agent commands: dry-run short-circuits
|
||||
// first and only echoes the planned action — no guards, no file I/O.
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
would_sync: {
|
||||
provider: SYNC_PROVIDER,
|
||||
config_file: file,
|
||||
out,
|
||||
types,
|
||||
agent_id: flags.agentId,
|
||||
environment_id: flags.environmentId,
|
||||
vault_id: flags.vaultId,
|
||||
file_id: flags.fileId,
|
||||
skill_id: flags.skillId,
|
||||
},
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existsSync(out) && !flags.force) {
|
||||
throw new BailianError(
|
||||
`${out} already exists.`,
|
||||
ExitCode.USAGE,
|
||||
"Pass --force to overwrite, or --out to write elsewhere.",
|
||||
);
|
||||
}
|
||||
|
||||
const { result, agentFilter, selectedKeys } = await withAgentErrors(() =>
|
||||
withStdoutProtected(async () => {
|
||||
const runtime = await buildAgentRuntime(ctx, file);
|
||||
assertProviderConfigured(runtime, SYNC_PROVIDER);
|
||||
const synced = await syncProviderResourcesFromContext(runtime, {
|
||||
provider: SYNC_PROVIDER,
|
||||
types,
|
||||
});
|
||||
// An explicit --skill-id wins over the agent's referenced-skill narrowing.
|
||||
const filtered = flags.agentId
|
||||
? await filterConfigToAgent(runtime, synced, flags.agentId, {
|
||||
narrowSkills: !flags.skillId,
|
||||
})
|
||||
: undefined;
|
||||
const keptKeys: Partial<Record<SelectableResourceType, string>> = {};
|
||||
for (const [selectionType, remoteId] of Object.entries(resourceSelections)) {
|
||||
if (!remoteId) continue;
|
||||
keptKeys[selectionType as SelectableResourceType] = await narrowGroupToRemoteId(
|
||||
runtime,
|
||||
SYNC_PROVIDER,
|
||||
synced,
|
||||
selectionType as SelectableResourceType,
|
||||
remoteId,
|
||||
);
|
||||
}
|
||||
return { result: synced, agentFilter: filtered, selectedKeys: keptKeys };
|
||||
}),
|
||||
);
|
||||
|
||||
const narrowed = Boolean(agentFilter) || Object.keys(selectedKeys).length > 0;
|
||||
const baseDir = dirname(out);
|
||||
const removedFiles = flags.skipMissingFiles
|
||||
? removeMissingFileSources(result.config, baseDir)
|
||||
: [];
|
||||
const yamlContent =
|
||||
narrowed || removedFiles.length > 0
|
||||
? stringifyYaml(result.config, { lineWidth: 0 })
|
||||
: result.yaml;
|
||||
await writeFile(out, yamlContent, "utf8");
|
||||
|
||||
const downloadedSkillFiles = await writeDownloadedSkillFiles(result, baseDir);
|
||||
// Custom skills whose content could not be downloaded need local files
|
||||
// before an apply would round-trip; surface them instead of prompting.
|
||||
const missingSkillSources = collectMissingSkillSources(result.config, baseDir);
|
||||
const secretEnvVars = (result.secretPlaceholders ?? []).map(
|
||||
(placeholder) => placeholder.envVar,
|
||||
);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{
|
||||
synced: out,
|
||||
provider: SYNC_PROVIDER,
|
||||
agent_id: flags.agentId,
|
||||
selected_keys: Object.keys(selectedKeys).length > 0 ? selectedKeys : undefined,
|
||||
counts: result.counts,
|
||||
skill_files_downloaded: downloadedSkillFiles,
|
||||
removed_files: removedFiles,
|
||||
missing_skill_sources: missingSkillSources,
|
||||
secret_env_vars: secretEnvVars,
|
||||
unmatched_skill_refs: agentFilter?.unmatchedSkillIds,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const countParts = Object.entries(result.counts).map(([type, count]) => `${count} ${type}(s)`);
|
||||
emitBare(
|
||||
`Synced ${countParts.length > 0 ? countParts.join(", ") : "0 resources"} from ${SYNC_PROVIDER} into ${out}.`,
|
||||
);
|
||||
if (agentFilter) {
|
||||
emitBare(
|
||||
agentFilter.keptSkills
|
||||
? `Narrowed to agent ${flags.agentId} (kept ${agentFilter.keptSkills.length} referenced skill(s)).`
|
||||
: `Narrowed to agent ${flags.agentId}.`,
|
||||
);
|
||||
if (agentFilter.unmatchedSkillIds.length > 0) {
|
||||
emitBare(
|
||||
`Skill references without a matching skills entry (kept on the agent as-is): ${agentFilter.unmatchedSkillIds.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const [selectionType, keptKey] of Object.entries(selectedKeys)) {
|
||||
const requestedId = resourceSelections[selectionType as SelectableResourceType];
|
||||
emitBare(
|
||||
`Narrowed ${selectionType} to ${requestedId}${keptKey !== requestedId ? ` (key: ${keptKey})` : ""}.`,
|
||||
);
|
||||
}
|
||||
if (downloadedSkillFiles > 0) {
|
||||
emitBare(`Downloaded ${downloadedSkillFiles} skill file(s) into ./skills/.`);
|
||||
}
|
||||
if (removedFiles.length > 0) {
|
||||
emitBare(
|
||||
`Removed ${removedFiles.length} file resource(s) with missing local sources: ${removedFiles.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
if (missingSkillSources.length > 0) {
|
||||
emitBare(
|
||||
`Missing local skill sources (provide the files before apply): ${missingSkillSources.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
if (secretEnvVars.length > 0) {
|
||||
emitBare(`Set these env vars locally before apply: ${secretEnvVars.join(", ")}.`);
|
||||
}
|
||||
// `bl` prefix is safe: agent commands ship on `bl` only.
|
||||
emitBare(
|
||||
`Next: review ${out}, then run \`bl managed-agent migrate\` to merge it into agents.yaml.`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse the --types list. Accepts singular and plural spellings ("skills" →
|
||||
* "skill") and dedupes; invalid tokens are returned verbatim so validate()
|
||||
* can reject them with the original user input.
|
||||
*/
|
||||
function parseSyncTypes(raw: string): { types: SyncableType[]; invalid: string[] } {
|
||||
const types: SyncableType[] = [];
|
||||
const invalid: string[] = [];
|
||||
const tokens = raw
|
||||
.split(",")
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
for (const token of tokens) {
|
||||
const singular = token.endsWith("s") ? token.slice(0, -1) : token;
|
||||
const matched = SYNCABLE_TYPES.find(
|
||||
(candidate) => candidate === token || candidate === singular,
|
||||
);
|
||||
if (!matched) {
|
||||
invalid.push(token);
|
||||
} else if (!types.includes(matched)) {
|
||||
types.push(matched);
|
||||
}
|
||||
}
|
||||
return { types, invalid };
|
||||
}
|
||||
|
||||
/** Result of narrowing a synced config to a single agent. */
|
||||
interface AgentFilterResult {
|
||||
/** Undefined when skills narrowing was skipped (--skill-id takes over). */
|
||||
keptSkills?: string[];
|
||||
unmatchedSkillIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow a full synced config to a single agent: the agents group keeps only
|
||||
* the entry whose yaml key equals the remote agent ID, and — unless an
|
||||
* explicit --skill-id selection takes over (`narrowSkills: false`) — the
|
||||
* skills group keeps only the custom skills that agent references (official
|
||||
* skills live in the provider catalog and need no local entry). Skill
|
||||
* references carry remote skill IDs while skills-group keys are
|
||||
* metadata/display-name derived, so key misses are resolved through the
|
||||
* custom skill catalog before being surfaced as unmatched (never silently
|
||||
* dropped from the agent itself). Downloaded skill files and counts are
|
||||
* re-scoped to what remains.
|
||||
*/
|
||||
async function filterConfigToAgent(
|
||||
runtime: ProjectRuntimeContext,
|
||||
result: SyncProjectResult,
|
||||
agentId: string,
|
||||
options: { narrowSkills: boolean },
|
||||
): Promise<AgentFilterResult> {
|
||||
const config = result.config;
|
||||
const agents = (config.agents ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const selected = agents[agentId];
|
||||
if (!selected) {
|
||||
const available = Object.keys(agents);
|
||||
throw new BailianError(
|
||||
`Agent '${agentId}' not found on the remote workspace.`,
|
||||
ExitCode.USAGE,
|
||||
available.length > 0
|
||||
? `Available agent IDs: ${available.join(", ")}.`
|
||||
: "The workspace has no syncable (non-archived) agents.",
|
||||
);
|
||||
}
|
||||
config.agents = { [agentId]: selected };
|
||||
if ("agent" in result.counts) result.counts.agent = 1;
|
||||
if (!options.narrowSkills) {
|
||||
return { unmatchedSkillIds: [] };
|
||||
}
|
||||
|
||||
const skillRefs = Array.isArray(selected.skills)
|
||||
? (selected.skills as Array<Record<string, unknown>>)
|
||||
: [];
|
||||
// Official skills resolve against the provider catalog at apply time; only
|
||||
// custom references map to synced skills-group declarations.
|
||||
const referencedIds = new Set(
|
||||
skillRefs
|
||||
.filter((skillRef) => skillRef.type !== "official")
|
||||
.map((skillRef) => skillRef.skill_id)
|
||||
.filter((skillId): skillId is string => typeof skillId === "string"),
|
||||
);
|
||||
const skills = (config.skills ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const keptSkills = Object.keys(skills).filter((key) => referencedIds.has(key));
|
||||
const pendingIds = Array.from(referencedIds).filter((skillId) => !(skillId in skills));
|
||||
// Key misses: the ref carries a remote ID while the group key is a
|
||||
// display-name slug — resolve through the custom catalog before declaring
|
||||
// the reference unmatched.
|
||||
const { resolved, unmatched: unmatchedSkillIds } = await resolveSkillKeysByIds(
|
||||
runtime,
|
||||
SYNC_PROVIDER,
|
||||
skills,
|
||||
pendingIds,
|
||||
);
|
||||
for (const resolvedKey of resolved.values()) {
|
||||
if (!keptSkills.includes(resolvedKey)) keptSkills.push(resolvedKey);
|
||||
}
|
||||
if (keptSkills.length > 0) {
|
||||
config.skills = Object.fromEntries(keptSkills.map((key) => [key, skills[key]!]));
|
||||
} else {
|
||||
delete config.skills;
|
||||
}
|
||||
|
||||
if (result.skillFiles) {
|
||||
const kept = new Set(keptSkills);
|
||||
for (const skillName of result.skillFiles.keys()) {
|
||||
if (!kept.has(skillName)) result.skillFiles.delete(skillName);
|
||||
}
|
||||
}
|
||||
if ("skill" in result.counts) result.counts.skill = keptSkills.length;
|
||||
|
||||
return { keptSkills, unmatchedSkillIds };
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop file resources whose `source` does not exist locally — the remote
|
||||
* platform cannot hand file content back, so keeping them would make the
|
||||
* synced config un-appliable. Returns the removed YAML keys.
|
||||
*/
|
||||
function removeMissingFileSources(config: Record<string, unknown>, baseDir: string): string[] {
|
||||
const files = (config.files ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const removed = Object.entries(files)
|
||||
.filter(
|
||||
([, decl]) => typeof decl.source === "string" && !existsSync(join(baseDir, decl.source)),
|
||||
)
|
||||
.map(([key]) => key);
|
||||
for (const key of removed) {
|
||||
delete files[key];
|
||||
}
|
||||
if (Object.keys(files).length === 0) {
|
||||
delete config.files;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/** Persist provider-downloaded skill files under ./skills/<name>/; returns the file count. */
|
||||
async function writeDownloadedSkillFiles(
|
||||
result: SyncProjectResult,
|
||||
baseDir: string,
|
||||
): Promise<number> {
|
||||
if (!result.skillFiles?.size) return 0;
|
||||
let written = 0;
|
||||
for (const [skillName, skillFileList] of result.skillFiles) {
|
||||
for (const skillFile of skillFileList) {
|
||||
const filePath = join(baseDir, "skills", skillName, skillFile.relativePath);
|
||||
await mkdir(dirname(filePath), { recursive: true });
|
||||
await writeFile(filePath, skillFile.content);
|
||||
written++;
|
||||
}
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
/** Custom skills whose local source dir/file is absent or empty after sync. */
|
||||
function collectMissingSkillSources(config: Record<string, unknown>, baseDir: string): string[] {
|
||||
const skills = (config.skills ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const missing: string[] = [];
|
||||
for (const [key, decl] of Object.entries(skills)) {
|
||||
if (decl.origin !== "custom" || typeof decl.source !== "string") continue;
|
||||
const sourcePath = join(baseDir, decl.source);
|
||||
if (!existsSync(sourcePath)) {
|
||||
missing.push(key);
|
||||
continue;
|
||||
}
|
||||
const sourceStat = statSync(sourcePath);
|
||||
if (sourceStat.isDirectory() && readdirSync(sourcePath).length === 0) {
|
||||
missing.push(key);
|
||||
}
|
||||
}
|
||||
return missing;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -28,22 +27,31 @@ const INSTALL_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Install skills from the Bailian skill registry into local agents",
|
||||
auth: "none",
|
||||
usageArgs: "--name <all|name,...>",
|
||||
usageArgs: "--all | --name <name,...>",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Install all skills from the registry",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description: "Skills to install: all or comma-separated skill names",
|
||||
required: true,
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to install",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--name all", "--name spark-video,bailian-model-recommend"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
if (!flags.all && !flags.name)
|
||||
return "Specify --all to install everything or --name <name,...> for specific skills";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["--all", "--name spark-video,bailian-model-recommend"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
const remoteNames = Object.keys(index.skills);
|
||||
const names = requested === "all" ? remoteNames : requested;
|
||||
const parsed = ctx.flags.all ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const names = parsed === "all" ? remoteNames : parsed;
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
installSkillWithFanout,
|
||||
readSkillLock,
|
||||
runWithConcurrency,
|
||||
writeSkillLock,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
|
||||
|
||||
interface InitOutcome {
|
||||
name: string;
|
||||
status: "installed" | "failed";
|
||||
publishedAt?: string;
|
||||
agents?: string[];
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/** Prefix used to identify first-party Bailian skills in the registry. */
|
||||
const BAILIAN_PREFIX = "bailian-";
|
||||
|
||||
/** Max number of skills downloading/installing at the same time. */
|
||||
const INIT_CONCURRENCY = 3;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Install all bailian-* skills (one-shot bootstrap for new environments)",
|
||||
auth: "none",
|
||||
usageArgs: "",
|
||||
exampleArgs: [""],
|
||||
notes: [
|
||||
"Fetches the registry index and installs every skill whose name starts with bailian-",
|
||||
"Equivalent to: bl skill add --all (filtered to bailian-* skills)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
|
||||
// Discover all bailian-* skills from the live registry index
|
||||
const names = Object.keys(index.skills).filter((name) => name.startsWith(BAILIAN_PREFIX));
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
const tasks = names.map((name) => async (): Promise<InitOutcome> => {
|
||||
const entry = index.skills[name];
|
||||
try {
|
||||
const record = await installSkillWithFanout(
|
||||
name,
|
||||
entry,
|
||||
agents,
|
||||
lock.skills[name]?.links ?? [],
|
||||
);
|
||||
lock.skills[name] = record.lockEntry;
|
||||
return {
|
||||
name,
|
||||
status: "installed",
|
||||
publishedAt: entry.publishedAt,
|
||||
agents: record.linkedAgents,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
name,
|
||||
status: "failed",
|
||||
reason: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
});
|
||||
const results = await runWithConcurrency(tasks, INIT_CONCURRENCY);
|
||||
writeSkillLock(lock);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{
|
||||
registry: getSkillRegistryBaseUrl(),
|
||||
agents: agents.map((agent) => agent.id),
|
||||
skills: results,
|
||||
},
|
||||
format,
|
||||
);
|
||||
} else if (results.length === 0) {
|
||||
emitBare("No bailian-* skills found in the registry.");
|
||||
} else {
|
||||
const rows = results.map((result) => [
|
||||
result.name,
|
||||
result.status,
|
||||
result.publishedAt ? result.publishedAt.slice(0, 10) : "-",
|
||||
result.status === "installed" ? result.agents?.join(", ") || "-" : (result.reason ?? "-"),
|
||||
]);
|
||||
for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "AGENTS / REASON"], rows)) {
|
||||
emitBare(line);
|
||||
}
|
||||
}
|
||||
|
||||
const failed = results.filter((result) => result.status === "failed");
|
||||
if (failed.length > 0) {
|
||||
throw new BailianError(
|
||||
`${failed.length}/${results.length} skill(s) failed to install`,
|
||||
ExitCode.GENERAL,
|
||||
"Check the reason for failed skills in the output; network failures can be retried with bl skill init",
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
computeSkillStatuses,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -24,7 +23,7 @@ export default defineCommand({
|
||||
"STATUS: installed | outdated | not-installed | missing (lock has it, dir deleted) | untracked (dir exists, not managed)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
// Three-way reconciliation: live remote index × skill-lock.json (installation facts) × disk
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listSkillDirsOnDisk,
|
||||
parseSkillNames,
|
||||
readSkillLock,
|
||||
@@ -34,7 +33,7 @@ export default defineCommand({
|
||||
exampleArgs: ["--name spark-video", "--name all"],
|
||||
async run(ctx) {
|
||||
// Purely local operation: no remote access, works offline
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const lock = readSkillLock();
|
||||
const names = requested === "all" ? Object.keys(lock.skills) : requested;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fanOutSkillToAgents,
|
||||
fetchSkillsIndex,
|
||||
@@ -29,19 +28,27 @@ const UPDATE_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Update installed skills to the latest registry versions",
|
||||
auth: "none",
|
||||
usageArgs: "[--name <all|name,...>]",
|
||||
usageArgs: "[--all] [--name <name,...>]",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Update all installed skills (default when neither --all nor --name is given)",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description:
|
||||
"Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills)",
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to update (must be already installed)",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["", "--name spark-video"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["", "--all", "--name spark-video"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, true);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const updateAll = ctx.flags.all || !ctx.flags.name;
|
||||
const requested = updateAll ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
const disk = new Set(listSkillDirsOnDisk());
|
||||
|
||||
@@ -93,8 +93,6 @@ export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-se
|
||||
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
|
||||
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
|
||||
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
|
||||
export { default as managedAgentSync } from "./commands/managed-agent/sync.ts";
|
||||
export { default as managedAgentMigrate } from "./commands/managed-agent/migrate.ts";
|
||||
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
|
||||
export { default as managedAgentApply } from "./commands/managed-agent/apply.ts";
|
||||
export { default as managedAgentDestroy } from "./commands/managed-agent/destroy.ts";
|
||||
@@ -119,3 +117,4 @@ export { default as skillAdd } from "./commands/skill/add.ts";
|
||||
export { default as skillUpdate } from "./commands/skill/update.ts";
|
||||
export { default as skillRemove } from "./commands/skill/remove.ts";
|
||||
export { default as skillList } from "./commands/skill/list.ts";
|
||||
export { default as skillInit } from "./commands/skill/init.ts";
|
||||
|
||||
@@ -89,13 +89,13 @@ test("GET /api/config 返回全部 profile、明文密钥与持久化激活项",
|
||||
expect(res.json.enums.console_site).toEqual(["domestic", "international"]);
|
||||
expect(res.json.booleanKeys).toContain("telemetry");
|
||||
// Default field hints are surfaced as prefilled values in the UI.
|
||||
expect(res.json.fieldDefaults.default_image_model).toBe("qwen-image-2.0");
|
||||
expect(res.json.fieldDefaults.default_image_model).toBe("qwen-image-3.0");
|
||||
expect(res.json.fieldDefaults.default_text_model).toBe("qwen3.8-max");
|
||||
expect(res.json.fieldDefaults.output_dir).toContain("bailian-output");
|
||||
expect(res.json.fieldDefaults.timeout).toBe("300");
|
||||
expect(res.json.fieldDefaults.base_url).toBe("https://dashscope.aliyuncs.com");
|
||||
// Per-category model catalog (click-to-fill suggestions) is exposed too.
|
||||
expect(res.json.modelCatalog.default_image_model[0]).toMatchObject({ id: "qwen-image-2.0" });
|
||||
expect(res.json.modelCatalog.default_image_model[0]).toMatchObject({ id: "qwen-image-3.0" });
|
||||
expect(res.json.modelCatalog.default_video_model.map((m: { id: string }) => m.id)).toContain(
|
||||
"happyhorse-1.1-i2v",
|
||||
);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
version: "1"
|
||||
|
||||
providers:
|
||||
claude:
|
||||
api_key: ${ANTHROPIC_API_KEY}
|
||||
|
||||
defaults:
|
||||
provider: claude
|
||||
|
||||
agents:
|
||||
assistant:
|
||||
description: "E2E claude-target fixture"
|
||||
model: claude-sonnet-4-6
|
||||
instructions: |
|
||||
You are a helpful assistant.
|
||||
@@ -1,30 +0,0 @@
|
||||
version: "1"
|
||||
|
||||
providers:
|
||||
bailian:
|
||||
api_key: ${DASHSCOPE_API_KEY}
|
||||
base_url: ${BAILIAN_BASE_URL}
|
||||
|
||||
environments:
|
||||
dev:
|
||||
config:
|
||||
type: cloud
|
||||
networking:
|
||||
type: unrestricted
|
||||
provider: bailian
|
||||
|
||||
agents:
|
||||
assistant:
|
||||
description: "E2E synced fixture (already exists in target)"
|
||||
model: qwen3.7-max
|
||||
instructions: |
|
||||
You are a helpful assistant.
|
||||
environment: dev
|
||||
provider: bailian
|
||||
reviewer:
|
||||
description: "E2E synced fixture (new agent)"
|
||||
model: qwen3.7-max
|
||||
instructions: |
|
||||
You review code.
|
||||
environment: dev
|
||||
provider: bailian
|
||||
@@ -194,13 +194,13 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())("e2e: ima
|
||||
expect(stderr).toMatch(/--prompt|Usage:/i);
|
||||
});
|
||||
|
||||
test("【qwen-image-2.0】图片编辑", async () => {
|
||||
test("【qwen-image-3.0】图片编辑", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const gen = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
"--prompt",
|
||||
"一只简笔画小猫,白底",
|
||||
"--out-dir",
|
||||
@@ -220,7 +220,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())("e2e: ima
|
||||
"image",
|
||||
"edit",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
"--image",
|
||||
imagePath!,
|
||||
"--prompt",
|
||||
|
||||
@@ -202,19 +202,19 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--prompt|Usage:/i);
|
||||
});
|
||||
|
||||
test("【qwen-image-2.0】图片生成", async () => {
|
||||
test("【qwen-image-3.0】图片生成", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
"--prompt",
|
||||
"一只简笔画小猫,白底",
|
||||
"--out-dir",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { e2eFixturesDir, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { MANAGED_AGENT_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
@@ -246,191 +245,3 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () =>
|
||||
expect(data.remote_id).toBe("agent-e2e");
|
||||
});
|
||||
});
|
||||
|
||||
describe("e2e: managed-agent sync / migrate(bailian-only)", () => {
|
||||
const fixturesDir = join(e2eFixturesDir, "managed-agent");
|
||||
const agentsYaml = join(fixturesDir, "agents.yaml");
|
||||
const agentsSyncedYaml = join(fixturesDir, "agents-synced.yaml");
|
||||
const agentsClaudeYaml = join(fixturesDir, "agents-claude.yaml");
|
||||
|
||||
test("managed-agent sync --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--out|--force|--skip-missing-files/i);
|
||||
expect(stderr).toMatch(/--agent-id/i);
|
||||
expect(stderr).toMatch(/--types/i);
|
||||
expect(stderr).toMatch(/--environment-id/i);
|
||||
expect(stderr).toMatch(/--vault-id/i);
|
||||
expect(stderr).toMatch(/--file-id/i);
|
||||
expect(stderr).toMatch(/--skill-id/i);
|
||||
});
|
||||
|
||||
test("sync 非法 --types 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--types",
|
||||
"agent,sessions",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--types contains unsupported values: sessions/i);
|
||||
});
|
||||
|
||||
test("sync --agent-id 搭配不含 agent 的 --types 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--agent-id",
|
||||
"agent-e2e",
|
||||
"--types",
|
||||
"skill",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--agent-id requires --types to include agent/i);
|
||||
});
|
||||
|
||||
test("sync --vault-id 搭配不含 vault 的 --types 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--vault-id",
|
||||
"vault-e2e",
|
||||
"--types",
|
||||
"agent",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--vault-id requires --types to include vault/i);
|
||||
});
|
||||
|
||||
test("sync 输出文件已存在且未 --force 时退出为用法错误 (2)", async () => {
|
||||
// auth: "apiKey" 的凭证解析先于 run() 执行;注入假 key 让用例不依赖环境凭证,
|
||||
// 命令仍会在覆盖写守卫处短路(先于构建 SDK runtime),不产生任何网络请求。
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
MANAGED_AGENT_ROUTES,
|
||||
["managed-agent", "sync", "--out", agentsYaml, "--quiet"],
|
||||
{ DASHSCOPE_API_KEY: "sk-e2e-sync" },
|
||||
);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/already exists/i);
|
||||
});
|
||||
|
||||
test("sync --dry-run 仅回显计划,即使 --out 已存在也不报错", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--dry-run",
|
||||
"--agent-id",
|
||||
"agent-e2e",
|
||||
"--skill-id",
|
||||
"skill-e2e",
|
||||
"--types",
|
||||
"agents,skills",
|
||||
"--out",
|
||||
agentsYaml,
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
would_sync?: {
|
||||
provider?: string;
|
||||
out?: string;
|
||||
agent_id?: string;
|
||||
skill_id?: string;
|
||||
types?: string[];
|
||||
};
|
||||
}>(stdout);
|
||||
expect(data.would_sync?.provider).toBe("bailian");
|
||||
expect(data.would_sync?.out).toBe(agentsYaml);
|
||||
expect(data.would_sync?.agent_id).toBe("agent-e2e");
|
||||
expect(data.would_sync?.skill_id).toBe("skill-e2e");
|
||||
// 复数拼写归一化为 SDK 的单数资源类型
|
||||
expect(data.would_sync?.types).toEqual(["agent", "skill"]);
|
||||
});
|
||||
|
||||
test("sync --agent-id 时 --types 自动补充 skill(关联技能联动导出)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"sync",
|
||||
"--dry-run",
|
||||
"--agent-id",
|
||||
"agent-e2e",
|
||||
"--types",
|
||||
"agent",
|
||||
"--out",
|
||||
"agents.synced.e2e-missing.yaml",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{ would_sync?: { types?: string[] } }>(stdout);
|
||||
expect(data.would_sync?.types).toEqual(["agent", "skill"]);
|
||||
});
|
||||
|
||||
test("managed-agent migrate --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"migrate",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--from|--to/i);
|
||||
});
|
||||
|
||||
test("migrate 目标文件缺失时退出为用法错误 (2)", async () => {
|
||||
// auth: "apiKey" 的凭证解析先于 run() 执行;注入假 key 让用例不依赖环境凭证,
|
||||
// 命令仍会在目标文件守卫处短路,不产生任何网络请求。
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
MANAGED_AGENT_ROUTES,
|
||||
[
|
||||
"managed-agent",
|
||||
"migrate",
|
||||
"--from",
|
||||
agentsSyncedYaml,
|
||||
"--to",
|
||||
"agents.e2e-missing.yaml",
|
||||
"--quiet",
|
||||
],
|
||||
{ DASHSCOPE_API_KEY: "sk-e2e-migrate" },
|
||||
);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/Target file .*agents\.e2e-missing\.yaml.*not found/i);
|
||||
});
|
||||
|
||||
test("migrate 目标 provider 非 bailian 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
MANAGED_AGENT_ROUTES,
|
||||
["managed-agent", "migrate", "--from", agentsSyncedYaml, "--to", agentsClaudeYaml, "--quiet"],
|
||||
{ DASHSCOPE_API_KEY: "sk-e2e-migrate" },
|
||||
);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/only targets the bailian provider/i);
|
||||
});
|
||||
|
||||
test("migrate --dry-run 仅回显计划,目标缺失也不报错", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"migrate",
|
||||
"--dry-run",
|
||||
"--from",
|
||||
agentsSyncedYaml,
|
||||
"--to",
|
||||
"agents.e2e-missing.yaml",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
would_migrate?: { from?: string; to?: string };
|
||||
}>(stdout);
|
||||
expect(data.would_migrate?.from).toBe(agentsSyncedYaml);
|
||||
expect(data.would_migrate?.to).toBe("agents.e2e-missing.yaml");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,12 +17,14 @@ describe("e2e: skill", () => {
|
||||
test("skill add --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "add", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
test("skill update --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "update", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
@@ -37,18 +39,37 @@ describe("e2e: skill", () => {
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/list|registry/i);
|
||||
});
|
||||
|
||||
test("skill init --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "init", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/bailian/i);
|
||||
});
|
||||
});
|
||||
|
||||
// Local-only cases: auth "none" + validation happens before any network access, no gating needed
|
||||
describe("e2e: skill (local, no credentials)", () => {
|
||||
test("skill add without --name errors as usage error (2)", async () => {
|
||||
test("skill add without --all or --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add with both --all and --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--all",
|
||||
"--name",
|
||||
"spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|either/i);
|
||||
});
|
||||
|
||||
test("skill remove without --name errors as usage error (2)", async () => {
|
||||
@@ -61,19 +82,6 @@ describe("e2e: skill (local, no credentials)", () => {
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add rejects mixing all with specific names (2)", async () => {
|
||||
// parseSkillNames throws UsageError before fetchSkillsIndex — offline-safe
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--name",
|
||||
"all,spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/all/i);
|
||||
});
|
||||
|
||||
test("skill remove of a not-installed skill fails with reason (1)", async () => {
|
||||
const configDir = makeTempConfigDir();
|
||||
const { stdout, exitCode } = await runCommandE2e(
|
||||
|
||||
@@ -165,13 +165,12 @@ export const SKILL_ROUTES: E2eRouteExports = {
|
||||
"skill update": "skillUpdate",
|
||||
"skill remove": "skillRemove",
|
||||
"skill list": "skillList",
|
||||
"skill init": "skillInit",
|
||||
};
|
||||
|
||||
export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
|
||||
"managed-agent init": "managedAgentInit",
|
||||
"managed-agent validate": "managedAgentValidate",
|
||||
"managed-agent sync": "managedAgentSync",
|
||||
"managed-agent migrate": "managedAgentMigrate",
|
||||
"managed-agent plan": "managedAgentPlan",
|
||||
"managed-agent apply": "managedAgentApply",
|
||||
"managed-agent destroy": "managedAgentDestroy",
|
||||
|
||||
@@ -159,7 +159,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
"--prompt",
|
||||
"一只简笔画小猫,白底",
|
||||
"--out-dir",
|
||||
|
||||
@@ -169,7 +169,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-3.0",
|
||||
"--prompt",
|
||||
"一片绿色的树叶,白底",
|
||||
"--out-dir",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { image2ImagePath, imagePath, imageSyncPath, imageText2ImagePath } from "
|
||||
* - async text2image + prompt: wan2.5/2.2/2.1-t2i*, wanx*-t2i*
|
||||
*
|
||||
* Edit (I2I):
|
||||
* - sync multimodal + messages(+images): qwen-image-2.0*, qwen-image-edit*, wan2.6-image*, wan2.7-image*
|
||||
* - sync multimodal + messages(+images): qwen-image-3.0*, qwen-image-2.0*, qwen-image-edit*, wan2.6-image*, wan2.7-image*
|
||||
* (pure T2I models such as z-image / qwen-image-plus / qwen-image-max are NOT edit models)
|
||||
* - async image2image + prompt/images: wan2.5-i2i*
|
||||
* - async image2image + function/base_image_url: *imageedit* (e.g. wanx2.1-imageedit)
|
||||
@@ -60,6 +60,7 @@ const SYNC_GENERATE_PREFIXES = ["qwen-image", "wan2.7-image", "z-image"] as cons
|
||||
* Pure T2I models (z-image / qwen-image-plus / qwen-image-max) are excluded.
|
||||
*/
|
||||
const SYNC_EDIT_PREFIXES = [
|
||||
"qwen-image-3.0",
|
||||
"qwen-image-2.0",
|
||||
"qwen-image-edit",
|
||||
"wan2.7-image",
|
||||
@@ -109,7 +110,12 @@ export function isWanxFunctionImageEditModel(model: string): boolean {
|
||||
}
|
||||
|
||||
export function resolveImageSizeProfile(model: string): ImageSizeProfile {
|
||||
if (model.startsWith("qwen-image-2.0") || model.startsWith("qwen-image-edit")) {
|
||||
// 3.0 暂复用 2.0 高分比例表(CLI ratio→像素便捷映射);不做独立 3.0 profile。
|
||||
if (
|
||||
model.startsWith("qwen-image-3.0") ||
|
||||
model.startsWith("qwen-image-2.0") ||
|
||||
model.startsWith("qwen-image-edit")
|
||||
) {
|
||||
return "qwen-image-2.0";
|
||||
}
|
||||
// Remaining qwen-image* (plus / max / bare qwen-image) share the fixed table.
|
||||
@@ -133,7 +139,13 @@ export function resolveImageSizeProfile(model: string): ImageSizeProfile {
|
||||
|
||||
/** Official / CLI defaults for prompt_extend when the flag is omitted. */
|
||||
export function resolvePromptExtendDefault(model: string): boolean | undefined {
|
||||
if (model.startsWith("qwen-image-2.0") || model.startsWith("qwen-image-max")) return true;
|
||||
if (
|
||||
model.startsWith("qwen-image-3.0") ||
|
||||
model.startsWith("qwen-image-2.0") ||
|
||||
model.startsWith("qwen-image-max")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Z-Image docs default prompt_extend to false.
|
||||
if (model.startsWith("z-image")) return false;
|
||||
return undefined;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../src/client/image-routes.ts";
|
||||
|
||||
test("sync multimodal family covers qwen-image, wan2.6/2.7 image, and z-image", () => {
|
||||
expect(isSyncMultimodalImageModel("qwen-image-3.0")).toBe(true);
|
||||
expect(isSyncMultimodalImageModel("qwen-image-2.0")).toBe(true);
|
||||
expect(isSyncMultimodalImageModel("qwen-image-2.0-pro")).toBe(true);
|
||||
expect(isSyncMultimodalImageModel("qwen-image-plus")).toBe(true);
|
||||
@@ -41,6 +42,7 @@ test("legacy image2image is wan2.5-i2i only; wanx imageedit uses function protoc
|
||||
});
|
||||
|
||||
test("size profiles are model-specific, not sync/async", () => {
|
||||
expect(resolveImageSizeProfile("qwen-image-3.0")).toBe("qwen-image-2.0");
|
||||
expect(resolveImageSizeProfile("qwen-image-2.0")).toBe("qwen-image-2.0");
|
||||
expect(resolveImageSizeProfile("qwen-image")).toBe("qwen-image-fixed");
|
||||
expect(resolveImageSizeProfile("qwen-image-plus")).toBe("qwen-image-fixed");
|
||||
@@ -55,6 +57,7 @@ test("size profiles are model-specific, not sync/async", () => {
|
||||
});
|
||||
|
||||
test("prompt_extend defaults follow model docs", () => {
|
||||
expect(resolvePromptExtendDefault("qwen-image-3.0")).toBe(true);
|
||||
expect(resolvePromptExtendDefault("qwen-image-2.0")).toBe(true);
|
||||
expect(resolvePromptExtendDefault("qwen-image-max")).toBe(true);
|
||||
expect(resolvePromptExtendDefault("z-image-turbo")).toBe(false);
|
||||
@@ -85,6 +88,11 @@ test("resolveImageGenerateApi picks path, input style, and size profile", () =>
|
||||
kind: "async-image-generation",
|
||||
sizeProfile: "wan26",
|
||||
});
|
||||
expect(resolveImageGenerateApi("qwen-image-3.0")).toMatchObject({
|
||||
kind: "sync-multimodal",
|
||||
sizeProfile: "qwen-image-2.0",
|
||||
promptExtendDefault: true,
|
||||
});
|
||||
expect(resolveImageGenerateApi("qwen-image-2.0")).toMatchObject({
|
||||
kind: "sync-multimodal",
|
||||
sizeProfile: "qwen-image-2.0",
|
||||
@@ -115,6 +123,10 @@ test("resolveImageEditApi excludes pure T2I models from sync edit", () => {
|
||||
kind: "sync-multimodal",
|
||||
useSync: true,
|
||||
});
|
||||
expect(resolveImageEditApi("qwen-image-3.0")).toMatchObject({
|
||||
kind: "sync-multimodal",
|
||||
useSync: true,
|
||||
});
|
||||
expect(resolveImageEditApi("qwen-image-2.0")).toMatchObject({
|
||||
kind: "sync-multimodal",
|
||||
useSync: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "knowledge-studio-cli",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
|
||||
"keywords": [
|
||||
"alibaba-cloud",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-runtime",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -171,7 +171,7 @@ export async function imageGenerate(
|
||||
});
|
||||
}
|
||||
|
||||
const model = input.model || "qwen-image-2.0";
|
||||
const model = input.model || "qwen-image-3.0";
|
||||
const route = resolveImageGenerateApi(model);
|
||||
const n = input.n ?? 1;
|
||||
|
||||
@@ -269,7 +269,7 @@ export async function imageEdit(
|
||||
}
|
||||
|
||||
const images = Array.isArray(input.image) ? input.image : input.image ? [input.image] : [];
|
||||
const model = input.model || "qwen-image-2.0";
|
||||
const model = input.model || "qwen-image-3.0";
|
||||
const route = resolveImageEditApi(model);
|
||||
const n = input.n ?? 1;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { ImageSizeProfile } from "bailian-cli-core";
|
||||
* Do not infer size from sync/async — that mismatches model constraints.
|
||||
*/
|
||||
|
||||
/** qwen-image-2.0 / qwen-image-edit recommended high-res presets. */
|
||||
/** qwen-image-2.0 / qwen-image-edit recommended high-res presets(3.0 经 profile 复用此表). */
|
||||
export const QWEN_IMAGE_20_RATIO_MAP: Record<string, string> = {
|
||||
"16:9": "2688*1536",
|
||||
"9:16": "1536*2688",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-cli
|
||||
metadata:
|
||||
version: "1.14.0"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -45,5 +45,5 @@ bl file upload --file audio.wav --model qwen3-asr-flash
|
||||
```
|
||||
|
||||
```bash
|
||||
bl file upload --file cat.png --model qwen-image-2.0
|
||||
bl file upload --file cat.png --model qwen-image-3.0
|
||||
```
|
||||
|
||||
@@ -52,6 +52,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
|
||||
| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) |
|
||||
@@ -86,7 +87,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `skill` | `add`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
|
||||
@@ -7,12 +7,13 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Description |
|
||||
| ----------------- | ---------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
| Command | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
|
||||
## Command details
|
||||
|
||||
@@ -22,24 +23,48 @@ Index: [index.md](index.md)
|
||||
| --------------- | ---------------------------------------------------------------- |
|
||||
| **Name** | `skill add` |
|
||||
| **Description** | Install skills from the Bailian skill registry into local agents |
|
||||
| **Usage** | `bl skill add --name <all\|name,...>` |
|
||||
| **Usage** | `bl skill add --all \| --name <name,...>` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ----------------------------------------------------- |
|
||||
| `--name <all\|name,...>` | string | yes | Skills to install: all or comma-separated skill names |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | -------------------------------------- |
|
||||
| `--all` | switch | no | Install all skills from the registry |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to install |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill add --name all
|
||||
bl skill add --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill add --name spark-video,bailian-model-recommend
|
||||
```
|
||||
|
||||
### `bl skill init`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------- |
|
||||
| **Name** | `skill init` |
|
||||
| **Description** | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| **Usage** | `bl skill init` |
|
||||
|
||||
#### Flags
|
||||
|
||||
_No command-specific flags._
|
||||
|
||||
#### Notes
|
||||
|
||||
- Fetches the registry index and installs every skill whose name starts with bailian-
|
||||
- Equivalent to: bl skill add --all (filtered to bailian-\* skills)
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill init
|
||||
```
|
||||
|
||||
### `bl skill list`
|
||||
|
||||
| Field | Value |
|
||||
@@ -96,13 +121,14 @@ bl skill remove --name all
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| **Name** | `skill update` |
|
||||
| **Description** | Update installed skills to the latest registry versions |
|
||||
| **Usage** | `bl skill update [--name <all\|name,...>]` |
|
||||
| **Usage** | `bl skill update [--all] [--name <name,...>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills) |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
|
||||
| `--all` | switch | no | Update all installed skills (default when neither --all nor --name is given) |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to update (must be already installed) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -110,6 +136,10 @@ bl skill remove --name all
|
||||
bl skill update
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --name spark-video
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-finetune
|
||||
metadata:
|
||||
version: "1.14.0"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-gen
|
||||
metadata:
|
||||
version: "1.14.0"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
@@ -29,8 +29,8 @@ description: >-
|
||||
|
||||
| User intent | Command | Default model |
|
||||
| --------------------------------------------- | ---------------------------------- | ---------------------------------------------- |
|
||||
| Text-to-image | `bl image generate` | `qwen-image-2.0` |
|
||||
| Image edit / multi-image merge | `bl image edit` (repeat `--image`) | `qwen-image-2.0` |
|
||||
| Text-to-image | `bl image generate` | `qwen-image-3.0` |
|
||||
| Image edit / multi-image merge | `bl image edit` (repeat `--image`) | `qwen-image-3.0` |
|
||||
| Text-to-video / image-to-video | `bl video generate` | `happyhorse-1.1-t2v` / `-i2v` (with `--image`) |
|
||||
| Video edit / style transfer | `bl video edit` | `happyhorse-1.0-video-edit` |
|
||||
| Reference-to-video + voice | `bl video ref` | `happyhorse-1.1-r2v` |
|
||||
|
||||
@@ -28,7 +28,7 @@ Index: [index.md](index.md)
|
||||
| --------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------- |
|
||||
| `--image <url>` | array | yes | Source image URL or local file path (repeatable for multi-image merge) |
|
||||
| `--prompt <text>` | string | yes | Edit instruction text |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen-image-2.0) |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen-image-3.0) |
|
||||
| `--size <W*H>` | string | no | Output image size: ratio (3:4, 16:9) or pixels (2048\*2048) |
|
||||
| `--n <count>` | number | no | Number of images (default: 1, max: 6) |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible results |
|
||||
@@ -91,7 +91,7 @@ bl image edit --image ./photo.png --prompt "Replace the background with a beach"
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--prompt <text>` | string | yes | Image description |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen-image-2.0) |
|
||||
| `--model <model>` | string | no | Model ID (default: qwen-image-3.0) |
|
||||
| `--size <W*H>` | string | no | Image size: ratio (3:4, 16:9, 1:1) or pixels (2048\*2048) |
|
||||
| `--n <count>` | number | no | Number of images per request (default: 1, max: 6) |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-managed-agent
|
||||
metadata:
|
||||
version: "1.14.0"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -14,7 +14,6 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl managed-agent apply` | Apply planned changes to create/update/delete agent resources | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent destroy` | Destroy all managed agent resources tracked in state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent init` | Create a new agents.yaml template | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent migrate` | Merge a synced config into a bailian agents.yaml | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent plan` | Show what changes would be applied to agent infrastructure | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session create` | Create a new session for an agent | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session delete` | Delete a session | [managed-agent.md](managed-agent.md) |
|
||||
@@ -28,14 +27,13 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl managed-agent state list` | List resources tracked in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state rm` | Remove a resource from state without destroying it remotely | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state show` | Show details of a resource in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent sync` | Export remote bailian resources into a local synced config | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent validate` | Validate an agents.yaml configuration (offline) | [managed-agent.md](managed-agent.md) |
|
||||
|
||||
## By group
|
||||
|
||||
| Group | Commands | Reference |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `managed-agent` | `apply`, `destroy`, `init`, `migrate`, `plan`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `sync`, `validate` | [managed-agent.md](managed-agent.md) |
|
||||
| Group | Commands | Reference |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `managed-agent` | `apply`, `destroy`, `init`, `plan`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate` | [managed-agent.md](managed-agent.md) |
|
||||
|
||||
## Global flags
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ Index: [index.md](index.md)
|
||||
| `bl managed-agent apply` | Apply planned changes to create/update/delete agent resources |
|
||||
| `bl managed-agent destroy` | Destroy all managed agent resources tracked in state |
|
||||
| `bl managed-agent init` | Create a new agents.yaml template |
|
||||
| `bl managed-agent migrate` | Merge a synced config into a bailian agents.yaml |
|
||||
| `bl managed-agent plan` | Show what changes would be applied to agent infrastructure |
|
||||
| `bl managed-agent session create` | Create a new session for an agent |
|
||||
| `bl managed-agent session delete` | Delete a session |
|
||||
@@ -26,7 +25,6 @@ Index: [index.md](index.md)
|
||||
| `bl managed-agent state list` | List resources tracked in agents state |
|
||||
| `bl managed-agent state rm` | Remove a resource from state without destroying it remotely |
|
||||
| `bl managed-agent state show` | Show details of a resource in agents state |
|
||||
| `bl managed-agent sync` | Export remote bailian resources into a local synced config |
|
||||
| `bl managed-agent validate` | Validate an agents.yaml configuration (offline) |
|
||||
|
||||
## Command details
|
||||
@@ -132,40 +130,6 @@ bl managed-agent init --provider bailian --agent-name assistant
|
||||
bl managed-agent init --provider all
|
||||
```
|
||||
|
||||
### `bl managed-agent migrate`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | -------------------------------------------------------- |
|
||||
| **Name** | `managed-agent migrate` |
|
||||
| **Description** | Merge a synced config into a bailian agents.yaml |
|
||||
| **Usage** | `bl managed-agent migrate [--from <path>] [--to <path>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | ----------------------------------------------------------- |
|
||||
| `--from <path>` | string | no | Synced config to migrate from (default: agents.synced.yaml) |
|
||||
| `--to <path>` | string | no | Target agents.yaml to merge into (default: agents.yaml) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- The merge itself runs against local files; bl's unified apiKey gate still applies — login via `bl auth login`, pass --api-key, or set DASHSCOPE_API_KEY.
|
||||
- Only a bailian-target agents.yaml is supported: migrated resources are re-pointed to provider bailian, with models/tools/environments normalized to Bailian-supported values.
|
||||
- Resources whose YAML key already exists in the target are skipped, never overwritten.
|
||||
- Run `bl managed-agent plan` afterwards to review the merged config before apply.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent migrate
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent migrate --from agents.synced.yaml --to agents.yaml
|
||||
```
|
||||
|
||||
### `bl managed-agent plan`
|
||||
|
||||
| Field | Value |
|
||||
@@ -610,67 +574,6 @@ bl managed-agent state rm --address bailian.agent.assistant
|
||||
bl managed-agent state show --address bailian.agent.assistant
|
||||
```
|
||||
|
||||
### `bl managed-agent sync`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent sync` |
|
||||
| **Description** | Export remote bailian resources into a local synced config |
|
||||
| **Usage** | `bl managed-agent sync [--types <list>] [--agent-id\|--environment-id\|--vault-id\|--file-id\|--skill-id <id>] [--file <path>] [--out <path>] [--force] [--skip-missing-files]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--out <path>` | string | no | Output path for the synced config (default: agents.synced.yaml) |
|
||||
| `--types <list>` | string | no | Comma-separated resource types to sync: environment, vault, file, skill, agent (default: all) |
|
||||
| `--agent-id <id>` | string | no | Sync a single agent by its remote ID (plus the skills it references) |
|
||||
| `--environment-id <id>` | string | no | Sync a single environment by its remote ID |
|
||||
| `--vault-id <id>` | string | no | Sync a single vault by its remote ID |
|
||||
| `--file-id <id>` | string | no | Sync a single file resource by its remote ID |
|
||||
| `--skill-id <id>` | string | no | Sync a single skill by its remote ID (overrides --agent-id skill narrowing) |
|
||||
| `--force` | switch | no | Overwrite an existing output file |
|
||||
| `--skip-missing-files` | switch | no | Drop file resources whose local source is missing instead of keeping them |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
|
||||
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
|
||||
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
|
||||
- Syncs from the bailian provider only: remote AgentStudio resources (environments, vaults, files, skills, agents) are exported into a local synced config for review.
|
||||
- --types narrows the export to the listed resource types (plural spellings accepted); sessions are runtime instances, not syncable resources.
|
||||
- With --agent-id, the agents group keeps only that agent (yaml key = remote agent ID) and its referenced custom skills are synced along — the skill group is exported even when --types omits it; official skill references need no local entry.
|
||||
- Bailian binds environments/vaults/files at the session level, not on the agent, so --agent-id keeps those groups as shared infrastructure.
|
||||
- Each --environment-id/--vault-id/--file-id/--skill-id narrows its own resource group to the single remote resource (yaml key resolved from the remote listing); combine with --types for a minimal output.
|
||||
- Requires an agents.yaml with a bailian provider block — run `bl managed-agent init` first.
|
||||
- Secrets are never exported: vault credentials keep ${ENV} placeholders; set those env vars locally before apply.
|
||||
- Merge the synced config into agents.yaml with `bl managed-agent migrate`.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent sync
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent sync --types agent,skill
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent sync --agent-id agent-abc123
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent sync --skill-id skill-xyz --types skill
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent sync --force --skip-missing-files
|
||||
```
|
||||
|
||||
### `bl managed-agent validate`
|
||||
|
||||
| Field | Value |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-protocol
|
||||
metadata:
|
||||
version: "1.14.0"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
Reference in New Issue
Block a user