mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 495532e7a4 | |||
| 575110e62d | |||
| 882bc6becb | |||
| 78ca730100 | |||
| 539247b10d | |||
| 10ddd0a948 | |||
| 7b5af2c205 | |||
| 2a1c96fb43 | |||
| 788d3faafc | |||
| f32bd2323f |
@@ -123,7 +123,7 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
|||||||
|
|
||||||
### Console Login (OAuth)
|
### Console Login (OAuth)
|
||||||
|
|
||||||
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.
|
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, asset center, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bl auth login --console
|
bl auth login --console
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
|||||||
|
|
||||||
### 控制台登录(OAuth)
|
### 控制台登录(OAuth)
|
||||||
|
|
||||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、资产中心、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bl auth login --console
|
bl auth login --console
|
||||||
|
|||||||
@@ -0,0 +1,574 @@
|
|||||||
|
# bailian-cli 快速上手指南
|
||||||
|
|
||||||
|
> 本文档面向新加入项目的开发者,帮助你理解 monorepo 的整体架构、代码组织方式和日常开发流程。
|
||||||
|
> AI Agent 维护契约见根目录 [`AGENTS.md`](../AGENTS.md);各场景的详细清单见 [`docs/agents/`](agents/)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 项目是什么
|
||||||
|
|
||||||
|
**bailian-cli** 是阿里云百炼(DashScope / Model Studio)平台的命令行工具,让用户和 AI Agent 通过终端调用平台的全部 AI 能力:
|
||||||
|
|
||||||
|
- 文本/全模态对话、图像/视频生成与编辑、语音合成与识别
|
||||||
|
- 知识库检索、记忆管理、应用调用、MCP 集成
|
||||||
|
- 微调与部署、数据集管理、配额与业务空间
|
||||||
|
- 控制台能力(用量统计、限流提额、资产中心等)
|
||||||
|
|
||||||
|
仓库以 **pnpm monorepo** 组织,产出两个 npm 产品:
|
||||||
|
|
||||||
|
| 产品 | 包名 | 二进制 | 定位 |
|
||||||
|
| -------------- | ---------------------- | ---------------- | ------------------------------------ |
|
||||||
|
| 百炼全量 CLI | `bailian-cli` | `bl` / `bailian` | 暴露全部命令 |
|
||||||
|
| 知识库轻量 CLI | `knowledge-studio-cli` | `kscli` | 仅 config + knowledge 命令,路径拍平 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 技术栈
|
||||||
|
|
||||||
|
| 类别 | 选型 |
|
||||||
|
| --------- | -------------------------------------------------------------------------------------------- |
|
||||||
|
| 语言 | TypeScript(strict) |
|
||||||
|
| 运行时 | Node.js ≥ 22.12 |
|
||||||
|
| 包管理 | pnpm 10 + workspace catalog |
|
||||||
|
| 构建/测试 | [vite-plus](https://github.com/voidzero-dev/vite-plus)(`vp check` / `vp test` / `vp pack`) |
|
||||||
|
| HTTP | undici(经 core client 封装) |
|
||||||
|
| 模块 | ESM(`"type": "module"`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 核心架构:四层分层
|
||||||
|
|
||||||
|
项目按 **「纯逻辑 → 运行时框架 → 命令库 → 产品入口」** 严格分层,职责边界清晰:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 产品入口层 │
|
||||||
|
│ packages/cli (bl) packages/kscli (kscli) │
|
||||||
|
│ 决定命令路径 map、产品 identity、README、技能 reference │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│ createCli(commands, identity)
|
||||||
|
┌────────────────────────────▼────────────────────────────────────┐
|
||||||
|
│ 运行时框架层 packages/runtime (bailian-cli-runtime) │
|
||||||
|
│ 参数解析、命令树/registry、help、middleware、错误处理、输出 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│ 调用 defineCommand 的 run()
|
||||||
|
┌────────────────────────────▼────────────────────────────────────┐
|
||||||
|
│ 命令库层 packages/commands (bailian-cli-commands) │
|
||||||
|
│ 96+ 命令实现;只导出 command,不决定产品路径 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│ client / settings / auth
|
||||||
|
┌────────────────────────────▼────────────────────────────────────┐
|
||||||
|
│ 纯逻辑层 packages/core (bailian-cli-core) │
|
||||||
|
│ 鉴权、配置、HTTP client、错误、类型、文件工具、领域 API │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分层边界(必须遵守)
|
||||||
|
|
||||||
|
| 层 | 可以做 | 不能做 |
|
||||||
|
| --------------- | --------------------------- | ------------------------------------------------------------- |
|
||||||
|
| **core** | 纯库逻辑、HTTP、鉴权解析 | 依赖 runtime/commands;硬编码 `bl`/`kscli`;调 `process.exit` |
|
||||||
|
| **runtime** | TTY、help、middleware、输出 | 写具体业务命令逻辑 |
|
||||||
|
| **commands** | 命令元数据 + `run` 实现 | 决定产品路径;在 usage 里写 bin 前缀 |
|
||||||
|
| **cli / kscli** | 命令路径 map、产品 identity | 不写命令业务逻辑 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 包详解
|
||||||
|
|
||||||
|
### 4.1 `packages/core` — `bailian-cli-core`
|
||||||
|
|
||||||
|
纯逻辑层,被所有上层依赖。主要模块:
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/core/src/
|
||||||
|
├── auth/ # API Key / Console token 解析与落盘
|
||||||
|
├── client/ # HTTP client、endpoints、MCP、流式解析
|
||||||
|
├── config/ # ~/.bailian/config.json、Settings、来源优先级
|
||||||
|
├── console/ # Console Gateway 调用
|
||||||
|
├── dataset/ # 数据集校验(ChatML/DPO/CPT schema)
|
||||||
|
├── finetune/ # 微调 API 与能力探测
|
||||||
|
├── deploy/ # 部署 API
|
||||||
|
├── advisor/ # 模型推荐(意图识别 + 召回)
|
||||||
|
├── errors/ # BailianError、UsageError、退出码
|
||||||
|
├── output/ # JSON/text 格式化(命令层也可用 runtime 的 emit)
|
||||||
|
├── files/ # 本地文件上传、URL 解析
|
||||||
|
├── telemetry/ # 命令执行遥测
|
||||||
|
└── types/ # Command、FlagsDef、defineCommand
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键类型** — 每个命令通过 `defineCommand` 声明:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
defineCommand({
|
||||||
|
description: "…",
|
||||||
|
auth: "apiKey" | "console" | "none",
|
||||||
|
flags: {
|
||||||
|
/* camelCase key → kebab-case CLI flag */
|
||||||
|
},
|
||||||
|
usageArgs: "--prompt <text> [flags]", // 不含 bl/kscli 前缀
|
||||||
|
exampleArgs: ['--prompt "hello"'],
|
||||||
|
validate: (flags) => string | undefined, // 跨 flag 校验
|
||||||
|
run: async (ctx) => {
|
||||||
|
/* ctx.client / ctx.flags / ctx.settings */
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client** 是命令的网络入口,凭证已注入,命令层不碰 token:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
ctx.client.requestJson({ path: "/…", method: "POST", body });
|
||||||
|
ctx.client.console({ product: "…", action: "…", params });
|
||||||
|
ctx.client.uploadFile(localPath);
|
||||||
|
ctx.client.mcp(…);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 `packages/runtime` — `bailian-cli-runtime`
|
||||||
|
|
||||||
|
通用 CLI 框架,与具体业务无关。核心文件:
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
| ------------------ | ----------------------------------------------- |
|
||||||
|
| `create-cli.ts` | 入口工厂:`createCli(commands, identity).run()` |
|
||||||
|
| `registry.ts` | 从 `Record<string, AnyCommand>` 建树,动态 help |
|
||||||
|
| `args.ts` | 路径 + flag 解析 |
|
||||||
|
| `middleware.ts` | auth → telemetry → versionCheck → runCommand |
|
||||||
|
| `error-handler.ts` | 统一错误输出与退出码 |
|
||||||
|
| `urls.ts` | 用户面控制台 URL(非 API endpoint) |
|
||||||
|
| `output/` | 颜色、表格、进度条、banner |
|
||||||
|
| `pipeline/` | 多步 pipeline 编排(`bl pipeline run`) |
|
||||||
|
|
||||||
|
**Middleware 流水线**(洋葱模型):
|
||||||
|
|
||||||
|
```
|
||||||
|
argv 解析
|
||||||
|
→ authStage 按 command.auth 注入 apiKey / console 凭证到 ctx.client
|
||||||
|
→ telemetryStage 记录命令执行
|
||||||
|
→ versionCheckStage 检查 npm 更新
|
||||||
|
→ runCommandStage 调用 command.run(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 `packages/commands` — `bailian-cli-commands`
|
||||||
|
|
||||||
|
命令实现库,按**能力域**组织目录(≠ 最终 CLI 路径):
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/commands/src/commands/
|
||||||
|
├── text/ # 文本对话
|
||||||
|
├── omni/ # 全模态对话
|
||||||
|
├── image/ # 图像生成/编辑
|
||||||
|
├── video/ # 视频生成/编辑/下载
|
||||||
|
├── speech/ # 语音合成/识别
|
||||||
|
├── vision/ # 图像/视频理解
|
||||||
|
├── knowledge/ # 知识库检索/搜索/对话
|
||||||
|
├── memory/ # 记忆管理
|
||||||
|
├── app/ # 应用调用
|
||||||
|
├── mcp/ # MCP 服务
|
||||||
|
├── auth/ # 登录/登出/状态
|
||||||
|
├── config/ # 配置读写
|
||||||
|
├── console/ # 通用 Console Gateway 调用
|
||||||
|
├── dataset/ # 数据集上传/校验
|
||||||
|
├── finetune/ # 微调任务
|
||||||
|
├── deploy/ # 模型部署
|
||||||
|
├── quota/ # 限流与提额
|
||||||
|
├── workspace/ # 业务空间
|
||||||
|
├── usage/ # 用量统计
|
||||||
|
├── advisor/ # 模型推荐
|
||||||
|
├── asset-center/ # 资产中心(新)
|
||||||
|
├── pipeline/ # Pipeline 编排
|
||||||
|
├── search/ # 联网搜索
|
||||||
|
├── file/ # 文件上传
|
||||||
|
├── token-plan/ # Token 计划
|
||||||
|
└── update.ts # 自更新
|
||||||
|
```
|
||||||
|
|
||||||
|
每个命令文件 `export default defineCommand(…)`,并在 `packages/commands/src/index.ts` 具名 re-export。
|
||||||
|
|
||||||
|
### 4.4 `packages/cli` — `bailian-cli`(`bl`)
|
||||||
|
|
||||||
|
产品入口,极薄:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/cli/src/main.ts
|
||||||
|
createCli(commands, {
|
||||||
|
binName: "bl",
|
||||||
|
version: pkg.version,
|
||||||
|
clientName: "bailian-cli",
|
||||||
|
npmPackage: "bailian-cli",
|
||||||
|
}).run();
|
||||||
|
```
|
||||||
|
|
||||||
|
**命令路径由 `packages/cli/src/commands.ts` 决定**,例如:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const commands: Record<string, AnyCommand> = {
|
||||||
|
"text chat": textChat,
|
||||||
|
"asset-center list": assetList,
|
||||||
|
"finetune create": finetuneCreate,
|
||||||
|
update, // 单级命令 key 即路径
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
此文件还被 `tools/generate-reference.ts` 读取,生成 Agent Skill 参考文档。
|
||||||
|
|
||||||
|
### 4.5 `packages/kscli` — `knowledge-studio-cli`(`kscli`)
|
||||||
|
|
||||||
|
轻量 RAG 产品,**复用同一套 commands**,但路径拍平:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const commands = {
|
||||||
|
retrieve: knowledgeRetrieve, // ↔ bl knowledge retrieve
|
||||||
|
search: knowledgeSearch, // ↔ bl knowledge search
|
||||||
|
chat: knowledgeChat, // ↔ bl knowledge chat
|
||||||
|
"config show": configShow,
|
||||||
|
update,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
同一个 `knowledgeRetrieve` 实现,在 `bl` 显示 `bl knowledge retrieve`,在 `kscli` 显示 `kscli retrieve`——路径完全由产品入口 map 的 key 决定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 一次命令执行的完整链路
|
||||||
|
|
||||||
|
以 `bl text chat --message "hi"` 为例:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant main as cli/main.ts
|
||||||
|
participant createCli as runtime/create-cli.ts
|
||||||
|
participant registry as runtime/registry.ts
|
||||||
|
participant mw as middleware
|
||||||
|
participant cmd as commands/text/chat.ts
|
||||||
|
participant client as core/client
|
||||||
|
|
||||||
|
User->>main: bl text chat --message "hi"
|
||||||
|
main->>createCli: createCli(commands, identity).run(argv)
|
||||||
|
createCli->>registry: 解析路径 ["text","chat"]
|
||||||
|
registry-->>createCli: 匹配 textChat command
|
||||||
|
createCli->>mw: authStage → 注入 apiKey 到 client
|
||||||
|
mw->>cmd: run(ctx)
|
||||||
|
cmd->>client: requestJson / parseSSE
|
||||||
|
client-->>User: stdout 输出
|
||||||
|
```
|
||||||
|
|
||||||
|
**配置与凭证解析优先级**(core 统一处理,命令不介入):
|
||||||
|
|
||||||
|
| 来源 | API Key | Console Token |
|
||||||
|
| ---- | ----------------------- | ------------------------------------- |
|
||||||
|
| 1 | `--api-key` flag | `~/.bailian/config.json` access_token |
|
||||||
|
| 2 | `DASHSCOPE_API_KEY` env | — |
|
||||||
|
| 3 | config.json `api_key` | — |
|
||||||
|
|
||||||
|
Console 命令额外有 `--console-region`、`--workspace-id` 等 flag(由 runtime 按 `auth: "console"` 自动展示)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 鉴权域
|
||||||
|
|
||||||
|
每个命令声明 `auth` 字段,runtime 自动处理:
|
||||||
|
|
||||||
|
| auth 值 | 适用场景 | 凭证来源 | 网络方法 |
|
||||||
|
| ----------- | ------------------------------ | -------------------- | -------------------------------- |
|
||||||
|
| `"apiKey"` | DashScope API(模型推理等) | API Key | `client.request` / `requestJson` |
|
||||||
|
| `"console"` | Console Gateway(控制台能力) | Console access token | `client.console` |
|
||||||
|
| `"none"` | 纯本地(config、update、help) | 无 | 可选 credential-less client |
|
||||||
|
|
||||||
|
**规则**:调用 Console Gateway 的命令必须 `auth: "console"`,且**不要**重复声明 console 凭证域 flags。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 错误处理约定
|
||||||
|
|
||||||
|
CLI **只翻译自己能权威解释的错误**,服务端错误原样透传:
|
||||||
|
|
||||||
|
| 错误来源 | 处理 |
|
||||||
|
| ---------------------- | -------------------------------- |
|
||||||
|
| 缺参、flag 校验 | `UsageError` → 退出码 2 |
|
||||||
|
| 本地无凭证 | `BailianError(AUTH)` |
|
||||||
|
| 网络/DNS/TLS | `BailianError(NETWORK)` |
|
||||||
|
| HTTP 4xx/5xx、业务错码 | message **原样透传**,不二次包装 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 开发工作流
|
||||||
|
|
||||||
|
### 8.1 环境准备
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 要求 Node >= 22.12, pnpm >= 10
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 格式化 + lint + 类型检查
|
||||||
|
pnpm run check # 或 vp check
|
||||||
|
|
||||||
|
# 本地跑 bl(tsx 直跑,无需 build)
|
||||||
|
pnpm run bl -- text chat --help
|
||||||
|
pnpm run kscli -- search --help
|
||||||
|
|
||||||
|
# 全量测试
|
||||||
|
pnpm test # 或 vp test
|
||||||
|
|
||||||
|
# 构建所有包
|
||||||
|
pnpm run ready # check + test + build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 新增一个 `bl` 命令(最小路径)
|
||||||
|
|
||||||
|
假设新增 `bl widget do`:
|
||||||
|
|
||||||
|
**Step 1** — 实现命令(`packages/commands`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 新建
|
||||||
|
packages/commands/src/commands/widget/do.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineCommand, type FlagsDef } from "bailian-cli-core";
|
||||||
|
import { emitResult } from "bailian-cli-runtime";
|
||||||
|
|
||||||
|
const FLAGS = {
|
||||||
|
name: { type: "string", valueHint: "<name>", description: "Widget name", required: true },
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Do something with a widget",
|
||||||
|
auth: "apiKey", // 或 "console" / "none"
|
||||||
|
flags: FLAGS,
|
||||||
|
usageArgs: "--name <name>",
|
||||||
|
exampleArgs: ['--name "demo"'],
|
||||||
|
async run(ctx) {
|
||||||
|
const data = await ctx.client.requestJson({ path: "/…", method: "POST", body: { … } });
|
||||||
|
emitResult(ctx, data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2** — 导出(`packages/commands/src/index.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export { default as widgetDo } from "./commands/widget/do.ts";
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3** — 注册产品路径(`packages/cli/src/commands.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { widgetDo } from "bailian-cli-commands";
|
||||||
|
// …
|
||||||
|
"widget do": widgetDo,
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4** — E2E 测试(`packages/cli/tests/e2e/widget.e2e.test.ts`)
|
||||||
|
|
||||||
|
见 [docs/agents/cli-e2e-tests.md](agents/cli-e2e-tests.md):至少覆盖分组 help、`--help`、缺参用例。
|
||||||
|
|
||||||
|
**Step 5** — 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vp check
|
||||||
|
vp test
|
||||||
|
pnpm run bl -- widget do --help
|
||||||
|
```
|
||||||
|
|
||||||
|
> 若 `kscli` 也需要暴露:在 `packages/kscli/src/main.ts` 的 map 里加 key。
|
||||||
|
> 技能 reference 会在 pre-commit 时由 `generate-reference.ts` 自动从 `commands.ts` 生成。
|
||||||
|
|
||||||
|
详细清单 → [docs/agents/command-add-remove.md](agents/command-add-remove.md)
|
||||||
|
|
||||||
|
### 8.3 给已有命令加 flag
|
||||||
|
|
||||||
|
→ [docs/agents/command-flag-change.md](agents/command-flag-change.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 测试体系
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/cli/tests/
|
||||||
|
├── e2e/ # 33 个 e2e 测试文件
|
||||||
|
│ ├── helpers.ts # runCli、环境变量 readiness 判断
|
||||||
|
│ ├── global-setup.ts
|
||||||
|
│ └── <topic>.e2e.test.ts
|
||||||
|
└── stress/ # 多能力并发压测
|
||||||
|
├── run.mjs
|
||||||
|
└── targets/
|
||||||
|
```
|
||||||
|
|
||||||
|
**E2E 双层结构**(固定模式):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 层 1:永远跑 — help / 分组,无需 API Key
|
||||||
|
describe("e2e: asset-center", () => {
|
||||||
|
test("asset-center 分组展示子命令帮助且成功退出", …);
|
||||||
|
test("asset-center list --help 正常退出", …);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 层 2:skipIf 缺凭证 — dry-run / 真实集成
|
||||||
|
describe.skipIf(!isConsoleE2EReady())("e2e: asset-center(Console …)", () => {
|
||||||
|
test("缺少 --asset-id 时退出为用法错误 (2)", …);
|
||||||
|
test("真实 list 流程", …);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
环境变量(常用):
|
||||||
|
|
||||||
|
| 变量 | 用途 |
|
||||||
|
| --------------------------------------------- | ------------------------- |
|
||||||
|
| `DASHSCOPE_API_KEY` | 模型 API 集成测试 |
|
||||||
|
| Console token(经 `bl auth login --console`) | 控制台命令测试 |
|
||||||
|
| `BAILIAN_E2E_*` | 各能力开关(视频/媒体等) |
|
||||||
|
|
||||||
|
压测:`pnpm run test:stress`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 命令能力地图(`bl` 全量)
|
||||||
|
|
||||||
|
当前 `packages/cli/src/commands.ts` 注册的命令组:
|
||||||
|
|
||||||
|
| 命令组 | 子命令示例 | auth 域 |
|
||||||
|
| -------------- | ------------------------------------------------------------------------------- | ---------------- |
|
||||||
|
| `auth` | login, status, logout | none / console |
|
||||||
|
| `text` | chat | apiKey |
|
||||||
|
| `omni` | (全模态对话) | apiKey |
|
||||||
|
| `image` | generate, edit | apiKey |
|
||||||
|
| `video` | generate, edit, ref, task get, download | apiKey |
|
||||||
|
| `vision` | describe | apiKey |
|
||||||
|
| `speech` | synthesize, recognize | apiKey |
|
||||||
|
| `knowledge` | retrieve, search, chat | apiKey |
|
||||||
|
| `memory` | add, search, list, update, delete, profile create/get | apiKey |
|
||||||
|
| `app` | call, list | apiKey / console |
|
||||||
|
| `mcp` | call, list, tools | apiKey |
|
||||||
|
| `search` | web | apiKey |
|
||||||
|
| `file` | upload | apiKey |
|
||||||
|
| `config` | show, set | none |
|
||||||
|
| `console` | call | console |
|
||||||
|
| `usage` | free, freetier, stats | console |
|
||||||
|
| `workspace` | list | console |
|
||||||
|
| `quota` | list, request, history, check | console |
|
||||||
|
| `dataset` | upload, list, get, delete, validate | console |
|
||||||
|
| `finetune` | create, list, get, cancel, delete, logs, checkpoints, export, watch, capability | console |
|
||||||
|
| `deploy` | create, list, get, models, scale, update, delete | console |
|
||||||
|
| `token-plan` | list-seats, create-key, assign-seats, add-member | console |
|
||||||
|
| `asset-center` | list, get, favorite, unfavorite, delete, download, stats, storage | console |
|
||||||
|
| `pipeline` | run, validate | apiKey |
|
||||||
|
| `advisor` | recommend | apiKey |
|
||||||
|
| `update` | (自更新) | none |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 非代码资产
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/
|
||||||
|
├── generate-reference.ts # 从 cli/commands.ts → skills/bailian-cli/reference/
|
||||||
|
├── sync-skill-metadata.ts # 同步 SKILL.md 版本号
|
||||||
|
└── release/ # CI 发版自动化
|
||||||
|
|
||||||
|
skills/bailian-cli/ # Agent Skill(npx skills add modelstudioai/cli)
|
||||||
|
.github/workflows/ # CI/CD(publish.yml 等)
|
||||||
|
docs/agents/ # 各维护场景的 AI 清单
|
||||||
|
```
|
||||||
|
|
||||||
|
根脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run sync:skill-assets # build + 生成 reference + 同步版本
|
||||||
|
pnpm run release:check # 发版前校验
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 发布
|
||||||
|
|
||||||
|
- 版本号:`packages/core`、`runtime`、`commands`、`cli`、`kscli` **保持同步**
|
||||||
|
- 发布范围:`tools/release/lib/packages.mjs` 定义
|
||||||
|
- `bailian-cli` 走常规定义发布;`knowledge-studio-cli` 走 `--knowledge` 通道
|
||||||
|
- 详见 [docs/agents/publish.md](agents/publish.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 关键文件速查
|
||||||
|
|
||||||
|
| 我想… | 看这里 |
|
||||||
|
| ------------------- | --------------------------------------- |
|
||||||
|
| 了解项目契约 | `AGENTS.md` |
|
||||||
|
| 改 `bl` 命令路径 | `packages/cli/src/commands.ts` |
|
||||||
|
| 写/改命令逻辑 | `packages/commands/src/commands/<域>/` |
|
||||||
|
| 导出命令 | `packages/commands/src/index.ts` |
|
||||||
|
| 改 CLI 框架行为 | `packages/runtime/src/` |
|
||||||
|
| 改 HTTP/鉴权/配置 | `packages/core/src/` |
|
||||||
|
| 改 kscli 路径 | `packages/kscli/src/main.ts` |
|
||||||
|
| 加 E2E 测试 | `packages/cli/tests/e2e/` |
|
||||||
|
| 改控制台 URL | `packages/runtime/src/urls.ts` |
|
||||||
|
| 改 API endpoint | `packages/core/src/client/endpoints.ts` |
|
||||||
|
| 改配置 schema | `packages/core/src/config/schema.ts` |
|
||||||
|
| 生成 Agent 参考文档 | `tools/generate-reference.ts` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 场景导航(维护清单)
|
||||||
|
|
||||||
|
| 场景 | 文档 |
|
||||||
|
| ----------- | ------------------------------------------------------- |
|
||||||
|
| 命令增删改 | [command-add-remove.md](agents/command-add-remove.md) |
|
||||||
|
| E2E 测试 | [cli-e2e-tests.md](agents/cli-e2e-tests.md) |
|
||||||
|
| 加/改 flag | [command-flag-change.md](agents/command-flag-change.md) |
|
||||||
|
| 模型上下架 | [model-add-remove.md](agents/model-add-remove.md) |
|
||||||
|
| 错误文案 | [error-hint-change.md](agents/error-hint-change.md) |
|
||||||
|
| 鉴权扩展 | [auth-change.md](agents/auth-change.md) |
|
||||||
|
| 配置项扩展 | [config-add.md](agents/config-add.md) |
|
||||||
|
| 发布 | [publish.md](agents/publish.md) |
|
||||||
|
| 工具链/lint | [lint-toolchain.md](agents/lint-toolchain.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 架构设计要点(读懂代码的钥匙)
|
||||||
|
|
||||||
|
1. **命令实现 ≠ 产品路径** — 同一 `knowledgeRetrieve` 可以是 `bl knowledge retrieve` 或 `kscli retrieve`。
|
||||||
|
2. **defineCommand 是契约** — `auth` + `flags` + `run(ctx)` 是命令的全部接口;凭证和网络细节下沉到 core/runtime。
|
||||||
|
3. **registry 从 map 建树** — `"asset-center list"` 等 path 自动变成命令组,help 动态生成。
|
||||||
|
4. **flags 用 camelCase 定义** — runtime 渲染为 `--kebab-case`;`ParsedFlags<typeof FLAGS>` 提供类型安全。
|
||||||
|
5. **dry-run 是全局 flag** — `--dry-run` 在 auth stage 有例外处理,命令在 `run` 开头判断 `ctx.settings.dryRun`。
|
||||||
|
6. **本地路径即 URL** — 所有接受 URL 的参数同时支持本地文件路径,core `files/upload` 自动上传。
|
||||||
|
7. **Console Gateway 统一入口** — 控制台 API 走 `client.console({ product, action, params })`,不散落 raw fetch。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 本地配置速览
|
||||||
|
|
||||||
|
配置文件:`~/.bailian/config.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"api_key": "sk-…",
|
||||||
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"access_token": "…",
|
||||||
|
"console_region": "cn-beijing",
|
||||||
|
"console_site": "domestic"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
常用环境变量:
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
| ---------------------------- | -------------------------- |
|
||||||
|
| `DASHSCOPE_API_KEY` | 模型 API Key |
|
||||||
|
| `DASHSCOPE_BASE_URL` | API Base URL |
|
||||||
|
| `BAILIAN_WORKSPACE_ID` | 业务空间 ID |
|
||||||
|
| `HTTP_PROXY` / `HTTPS_PROXY` | 代理(runtime 启动时读取) |
|
||||||
|
|
||||||
|
登录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl auth login # API Key
|
||||||
|
bl auth login --console # Console token(扫码)
|
||||||
|
bl auth status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_文档版本:基于仓库当前结构(含 `asset-center`、`kscli`);`packages/rag` 已演进为 `packages/kscli`。_
|
||||||
@@ -123,7 +123,7 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
|||||||
|
|
||||||
### Console Login (OAuth)
|
### Console Login (OAuth)
|
||||||
|
|
||||||
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.
|
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, asset center, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bl auth login --console
|
bl auth login --console
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
|||||||
|
|
||||||
### 控制台登录(OAuth)
|
### 控制台登录(OAuth)
|
||||||
|
|
||||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、资产中心、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bl auth login --console
|
bl auth login --console
|
||||||
|
|||||||
@@ -84,6 +84,14 @@ import {
|
|||||||
tokenPlanCreateKey,
|
tokenPlanCreateKey,
|
||||||
tokenPlanAssignSeats,
|
tokenPlanAssignSeats,
|
||||||
tokenPlanAddMember,
|
tokenPlanAddMember,
|
||||||
|
assetList,
|
||||||
|
assetGet,
|
||||||
|
assetFavorite,
|
||||||
|
assetUnfavorite,
|
||||||
|
assetDelete,
|
||||||
|
assetDownload,
|
||||||
|
assetStats,
|
||||||
|
assetStorage,
|
||||||
workspaceInit,
|
workspaceInit,
|
||||||
pluginInstall,
|
pluginInstall,
|
||||||
pluginLink,
|
pluginLink,
|
||||||
@@ -203,6 +211,14 @@ export const commands: Record<string, AnyCommand> = {
|
|||||||
"token-plan create-key": tokenPlanCreateKey,
|
"token-plan create-key": tokenPlanCreateKey,
|
||||||
"token-plan assign-seats": tokenPlanAssignSeats,
|
"token-plan assign-seats": tokenPlanAssignSeats,
|
||||||
"token-plan add-member": tokenPlanAddMember,
|
"token-plan add-member": tokenPlanAddMember,
|
||||||
|
"asset-center list": assetList,
|
||||||
|
"asset-center get": assetGet,
|
||||||
|
"asset-center favorite": assetFavorite,
|
||||||
|
"asset-center unfavorite": assetUnfavorite,
|
||||||
|
"asset-center delete": assetDelete,
|
||||||
|
"asset-center download": assetDownload,
|
||||||
|
"asset-center stats": assetStats,
|
||||||
|
"asset-center storage": assetStorage,
|
||||||
"workspace init": workspaceInit,
|
"workspace init": workspaceInit,
|
||||||
"plugin install": pluginInstall,
|
"plugin install": pluginInstall,
|
||||||
"plugin link": pluginLink,
|
"plugin link": pluginLink,
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { describe, expect, test } from "vite-plus/test";
|
||||||
|
import { isConsoleE2EReady, isConsoleAuthFailure, parseStdoutJson, runCli } from "./helpers.ts";
|
||||||
|
|
||||||
|
describe("e2e: asset-center", () => {
|
||||||
|
test("asset-center 分组展示子命令帮助且成功退出", async () => {
|
||||||
|
const { stdout, stderr, exitCode } = await runCli(["asset-center"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
const output = `${stdout}\n${stderr}`;
|
||||||
|
expect(output).toContain("list");
|
||||||
|
expect(output).toContain("storage");
|
||||||
|
expect(output).not.toContain("oss");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center list --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "list", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--type");
|
||||||
|
expect(stderr).toContain("--recycle-bin");
|
||||||
|
expect(stderr).toContain("bl asset-center list");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center get --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "get", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--asset-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center favorite --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "favorite", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--id");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center delete --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "delete", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--permanent");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center download --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "download", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--id");
|
||||||
|
expect(stderr).not.toMatch(/(^|\s)--out(\s|$)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center stats --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "stats", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("--sync-failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center storage --help 正常退出", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "storage", "--help"]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
expect(stderr).toContain("bl asset-center storage");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skipIf(!isConsoleE2EReady())("e2e: asset-center(Console)", () => {
|
||||||
|
test("asset-center get 缺少 --asset-id 时退出为用法错误 (2)", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "get", "--quiet"]);
|
||||||
|
expect(exitCode).toBe(2);
|
||||||
|
expect(stderr).toMatch(/--asset-id|Missing required argument/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center favorite 缺少 --id 时退出为用法错误 (2)", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "favorite", "--quiet"]);
|
||||||
|
expect(exitCode).toBe(2);
|
||||||
|
expect(stderr).toMatch(/--id|Missing required argument/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center download 缺少 --id 时退出为用法错误 (2)", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(["asset-center", "download", "--quiet"]);
|
||||||
|
expect(exitCode).toBe(2);
|
||||||
|
expect(stderr).toMatch(/--id|Missing required argument/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center list --dry-run 输出请求参数", async () => {
|
||||||
|
const { stdout, stderr, exitCode } = await runCli([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
const data = parseStdoutJson<{
|
||||||
|
api?: string;
|
||||||
|
data?: { deleteStatus?: string };
|
||||||
|
}>(stdout);
|
||||||
|
expect(data.api).toContain("listModelGeneratedAsset");
|
||||||
|
expect(data.data?.deleteStatus).toBe("NORMAL");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center stats --dry-run 输出请求参数", async () => {
|
||||||
|
const { stdout, stderr, exitCode } = await runCli([
|
||||||
|
"asset-center",
|
||||||
|
"stats",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
const data = parseStdoutJson<{ api?: string }>(stdout);
|
||||||
|
expect(data.api).toContain("countModelGeneratedAsset");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("asset-center storage --dry-run 输出请求参数", async () => {
|
||||||
|
const { stdout, stderr, exitCode } = await runCli([
|
||||||
|
"asset-center",
|
||||||
|
"storage",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
]);
|
||||||
|
expect(exitCode, stderr).toBe(0);
|
||||||
|
const data = parseStdoutJson<{ api?: string }>(stdout);
|
||||||
|
expect(data.api).toContain("getStorageQuota");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("【console】asset-center list 真实调用或鉴权失败优雅退出", async () => {
|
||||||
|
const workspaceId = process.env.BAILIAN_WORKSPACE_ID;
|
||||||
|
const args = ["asset-center", "list", "--output", "json", "--page-size", "1"];
|
||||||
|
if (workspaceId) args.push("--workspace-id", workspaceId);
|
||||||
|
|
||||||
|
const result = await runCli(args);
|
||||||
|
if (isConsoleAuthFailure(result)) return;
|
||||||
|
expect(result.exitCode, result.stderr).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
# 资产中心 CLI 命令树设计
|
||||||
|
|
||||||
|
> 本文档定义 `bl asset` 命令族的路径结构、help 层级、flags 概览与示例。
|
||||||
|
> 技术实现细节见 [DESIGN.md](./DESIGN.md);API 字段见 [api-doc.md](./api-doc.md)。
|
||||||
|
|
||||||
|
## 1. 命名原则
|
||||||
|
|
||||||
|
| 原则 | 说明 |
|
||||||
|
| ------------ | ------------------------------------------------------------------------- |
|
||||||
|
| 产品路径前缀 | `asset`(不用 `asset-center`,与 `deploy` / `dataset` 等产品域一致) |
|
||||||
|
| 层级深度 | 最多三级:`asset <group> <action>` |
|
||||||
|
| 子组条件 | 仅当子组下 ≥ 2 个 action 时使用子组(见 AGENTS.md) |
|
||||||
|
| bin 前缀 | `usageArgs` / `exampleArgs` 不写 `bl`;help 由 runtime 按路径补全 |
|
||||||
|
| 鉴权 | 全部 `auth: "console"`;自动可见 `--console-region` 等 CONSOLE_AUTH_FLAGS |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 命令树总览
|
||||||
|
|
||||||
|
```
|
||||||
|
bl asset
|
||||||
|
│
|
||||||
|
├── list # 分页查询资产列表
|
||||||
|
├── get <asset-id> # 查询单个资产详情
|
||||||
|
├── favorite # 收藏资产
|
||||||
|
├── unfavorite # 取消收藏
|
||||||
|
├── delete # 删除资产(默认软删到回收站)
|
||||||
|
├── restore # 从回收站恢复
|
||||||
|
├── download # 获取下载链接 / 可选落盘
|
||||||
|
├── stats # 资产数量统计
|
||||||
|
├── storage # 存储容量与配额
|
||||||
|
│
|
||||||
|
├── models # [P1] 模型列表(辅助筛选)
|
||||||
|
│ └── list
|
||||||
|
│
|
||||||
|
├── service # [P1/P2] 服务开通状态
|
||||||
|
│ ├── status
|
||||||
|
│ ├── enable # [P2]
|
||||||
|
│ └── disable # [P2]
|
||||||
|
│
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 产品入口注册 Map
|
||||||
|
|
||||||
|
`packages/cli/src/commands.ts` 中预期注册(camelCase export → kebab path):
|
||||||
|
|
||||||
|
| Map Key | Export 名(建议) | Phase |
|
||||||
|
| ------------------------- | --------------------- | ----- |
|
||||||
|
| `"asset list"` | `assetList` | 1 |
|
||||||
|
| `"asset get"` | `assetGet` | 1 |
|
||||||
|
| `"asset favorite"` | `assetFavorite` | 1 |
|
||||||
|
| `"asset unfavorite"` | `assetUnfavorite` | 1 |
|
||||||
|
| `"asset delete"` | `assetDelete` | 1 |
|
||||||
|
| `"asset restore"` | `assetRestore` | 1 |
|
||||||
|
| `"asset download"` | `assetDownload` | 1 |
|
||||||
|
| `"asset stats"` | `assetStats` | 1 |
|
||||||
|
| `"asset storage"` | `assetStorage` | 1 |
|
||||||
|
| `"asset models list"` | `assetModelsList` | 2 |
|
||||||
|
| `"asset service status"` | `assetServiceStatus` | 2 |
|
||||||
|
| `"asset service enable"` | `assetServiceEnable` | 3 |
|
||||||
|
| `"asset service disable"` | `assetServiceDisable` | 3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Help 层级预览
|
||||||
|
|
||||||
|
### 4.1 顶层分组
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bl asset
|
||||||
|
Asset management commands for Bailian Asset Center.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
list List model-generated assets
|
||||||
|
get Get asset details by ID
|
||||||
|
favorite Mark assets as favorites
|
||||||
|
unfavorite Remove assets from favorites
|
||||||
|
delete Delete assets (soft delete by default)
|
||||||
|
restore Restore soft-deleted assets
|
||||||
|
download Get asset download URLs
|
||||||
|
stats Count assets by type
|
||||||
|
storage View storage quota and usage
|
||||||
|
models Model configuration helpers
|
||||||
|
service Asset center service subscription
|
||||||
|
|
||||||
|
Run `bl asset <command> --help` for details.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 各命令规格
|
||||||
|
|
||||||
|
以下 `usageArgs` 为命令 metadata 中的值(不含 global flags)。Global flags(`--output`、`--dry-run`、`--quiet` 等)与 console flags(`--workspace-id` 等)由 runtime 自动追加到 help。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.1 Phase 1 命令
|
||||||
|
|
||||||
|
#### `bl asset list`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: List model-generated assets with filters and cursor pagination
|
||||||
|
|
||||||
|
Usage: bl asset list [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--type <type> Asset type: IMAGE, VIDEO, AUDIO
|
||||||
|
--model <name> Filter by model name
|
||||||
|
--keyword <text> Filter by asset name (substring)
|
||||||
|
--favorited Show only favorited assets
|
||||||
|
--recycle-bin Show soft-deleted assets (recycle bin)
|
||||||
|
--sync-status <status> OSS sync status filter
|
||||||
|
--begin-time <datetime> Filter by generate time start (ISO_LOCAL_DATE_TIME)
|
||||||
|
--end-time <datetime> Filter by generate time end
|
||||||
|
--include-download-url Include signed download URLs
|
||||||
|
--include-thumbnail Include thumbnail URLs
|
||||||
|
--thumbnail-width <px> Thumbnail width
|
||||||
|
--thumbnail-height <px> Thumbnail height
|
||||||
|
--page-size <n> Page size (default: 10, max: 100)
|
||||||
|
--next-token <token> Cursor for next page
|
||||||
|
--pre-token <token> Cursor for previous page
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset list
|
||||||
|
bl asset list --type IMAGE --model qwen-image-3.0
|
||||||
|
bl asset list --favorited --page-size 20
|
||||||
|
bl asset list --recycle-bin
|
||||||
|
bl asset list --keyword landscape --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #1 查看资产列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset get <asset-id>`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Get full details of a model-generated asset
|
||||||
|
|
||||||
|
Usage: bl asset get <asset-id> [flags]
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<asset-id> Asset ID to query
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--asset-id <id> Asset ID (alternative to positional)
|
||||||
|
--include-download-url Include signed download URL
|
||||||
|
--include-thumbnail Include thumbnail URL
|
||||||
|
--thumbnail-width <px> Thumbnail width
|
||||||
|
--thumbnail-height <px> Thumbnail height
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset get asset-001
|
||||||
|
bl asset get asset-001 --include-download-url --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #2 查看资产详情
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset favorite`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Add assets to favorites
|
||||||
|
|
||||||
|
Usage: bl asset favorite --id <asset-id> [--id <asset-id>...]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--id <asset-id> Asset ID (repeatable, max 100, required)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset favorite --id asset-001
|
||||||
|
bl asset favorite --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #3 收藏
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset unfavorite`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Remove assets from favorites
|
||||||
|
|
||||||
|
Usage: bl asset unfavorite --id <asset-id> [--id <asset-id>...]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--id <asset-id> Asset ID (repeatable, max 100, required)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset unfavorite --id asset-001
|
||||||
|
bl asset unfavorite --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #3 取消收藏
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset delete`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Delete assets (soft delete to recycle bin by default)
|
||||||
|
|
||||||
|
Usage: bl asset delete --id <asset-id> [--id <asset-id>...] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--id <asset-id> Asset ID (repeatable, max 100, required)
|
||||||
|
--permanent Permanently delete (cannot be restored)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset delete --id asset-001
|
||||||
|
bl asset delete --id asset-001 --id asset-002
|
||||||
|
bl asset delete --id asset-001 --permanent
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #4 删除资产、#5 批量删除
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset restore`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Restore soft-deleted assets from recycle bin
|
||||||
|
|
||||||
|
Usage: bl asset restore --id <asset-id> [--id <asset-id>...]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--id <asset-id> Asset ID (repeatable, max 100, required)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset restore --id asset-001
|
||||||
|
bl asset restore --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** 补充能力(配合回收站)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset download`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Get signed download URLs for assets
|
||||||
|
|
||||||
|
Usage: bl asset download --id <asset-id> [--id <asset-id>...] [--out <path>]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--id <asset-id> Asset ID (repeatable, max 100, required)
|
||||||
|
--out <path> Save file to path (only when exactly one --id)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset download --id asset-001
|
||||||
|
bl asset download --id asset-001 --out ./image.png
|
||||||
|
bl asset download --id asset-001 --id asset-002 --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #6 下载资产
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset stats`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Count model-generated assets by type
|
||||||
|
|
||||||
|
Usage: bl asset stats [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--type <type> Filter by asset type
|
||||||
|
--model <name> Filter by model name
|
||||||
|
--keyword <text> Filter by asset name
|
||||||
|
--favorited Count only favorited assets
|
||||||
|
--recycle-bin Count soft-deleted assets
|
||||||
|
--sync-failed Also count assets with failed OSS sync
|
||||||
|
--begin-time <datetime> Filter by generate time start
|
||||||
|
--end-time <datetime> Filter by generate time end
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset stats
|
||||||
|
bl asset stats --sync-failed
|
||||||
|
bl asset stats --type IMAGE --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #7 查看资产统计
|
||||||
|
|
||||||
|
**text 输出示例:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Total: 200
|
||||||
|
Image: 150
|
||||||
|
Video: 30
|
||||||
|
Audio: 20
|
||||||
|
Sync failed: 5 # 仅 --sync-failed 时出现
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset storage`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: View storage quota, usage, and overage pricing
|
||||||
|
|
||||||
|
Usage: bl asset storage [flags]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset storage
|
||||||
|
bl asset storage --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
**PRD 映射:** #14 查看容量信息
|
||||||
|
|
||||||
|
**text 输出示例:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Used: 1.2 GB
|
||||||
|
Free quota: 5.0 GB
|
||||||
|
Overage: ¥0.12/GB/month
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3 Phase 2/3 可选命令
|
||||||
|
|
||||||
|
#### `bl asset models list`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: List managed models grouped by asset type
|
||||||
|
|
||||||
|
Usage: bl asset models list
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset models list --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
用途:配合 `bl asset list --model` 时查阅可用 modelId。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `bl asset service status`
|
||||||
|
|
||||||
|
```
|
||||||
|
Description: Check whether asset center service is enabled
|
||||||
|
|
||||||
|
Usage: bl asset service status
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bl asset service status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. PRD 覆盖矩阵
|
||||||
|
|
||||||
|
| PRD # | 功能 | CLI 命令 | Phase | 状态 |
|
||||||
|
| ----- | ------------- | ------------------------------- | ----- | ---------------------------------- |
|
||||||
|
| 1 | 查看资产列表 | `asset list` | 1 | ✅ 可开发 |
|
||||||
|
| 2 | 查看资产详情 | `asset get` | 1 | ✅ 可开发 |
|
||||||
|
| 3 | 收藏/取消收藏 | `asset favorite` / `unfavorite` | 1 | ✅ 可开发 |
|
||||||
|
| 4 | 删除资产 | `asset delete` | 1 | ✅ 可开发 |
|
||||||
|
| 5 | 批量删除 | `asset delete`(多 `--id`) | 1 | ✅ 可开发 |
|
||||||
|
| 6 | 下载资产 | `asset download` | 1 | ✅ 可开发 |
|
||||||
|
| 7 | 查看资产统计 | `asset stats` | 1 | ✅ 可开发(转存失败用 workaround) |
|
||||||
|
| 14 | 查看容量信息 | `asset storage` | 1 | ✅ 可开发 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 典型工作流
|
||||||
|
|
||||||
|
### 7.1 首次使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl auth login --console
|
||||||
|
bl config set workspace_id ws-xxxxx
|
||||||
|
bl asset service status # 可选:确认已开通
|
||||||
|
bl asset storage # 查看容量
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 浏览与筛选
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset list
|
||||||
|
bl asset list --type IMAGE --model qwen-image-3.0 --keyword landscape
|
||||||
|
bl asset list --favorited
|
||||||
|
bl asset list --recycle-bin
|
||||||
|
bl asset get asset-001 --include-download-url
|
||||||
|
bl asset stats
|
||||||
|
bl asset stats --sync-failed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 资产管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset favorite --id asset-001
|
||||||
|
bl asset unfavorite --id asset-001
|
||||||
|
bl asset delete --id asset-001
|
||||||
|
bl asset delete --id asset-001 --id asset-002
|
||||||
|
bl asset restore --id asset-001
|
||||||
|
bl asset download --id asset-001 --out ./image.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.5 脚本翻页(JSON)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 第一页
|
||||||
|
bl asset list --page-size 50 --output json
|
||||||
|
# 后续页(使用响应中的 nextToken)
|
||||||
|
bl asset list --page-size 50 --next-token 1000 --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 与现有命令的风格对齐
|
||||||
|
|
||||||
|
| 参考命令 | 对齐点 |
|
||||||
|
| ------------------------------ | -------------------------------------------------- |
|
||||||
|
| `bl app list` | console gateway 调用、dry-run 输出 `{ api, data }` |
|
||||||
|
| `bl dataset list` | text 表格 + json items 结构 |
|
||||||
|
| `bl deploy list/get/create` | 产品域子命令命名、多级 path |
|
||||||
|
| `bl memory profile get/create` | 三级 path 子组 |
|
||||||
|
| `bl quota list` | `zeldaHttp.*` API 名、响应 extract |
|
||||||
|
| `bl video download` | `--out` 落盘 |
|
||||||
|
| `bl usage stats` | `requireWorkspaceId`、console E2E 模式 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 变更记录
|
||||||
|
|
||||||
|
| 日期 | 版本 | 说明 |
|
||||||
|
| ---------- | ---- | ---------------------------------------------- |
|
||||||
|
| 2026-07-09 | 0.1 | 初稿:命令树、PRD 映射、分 Phase 规格 |
|
||||||
|
| 2026-08-07 | 0.2 | 取消 OSS 转存命令(`oss *` / `transfer list`) |
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
# 资产中心 CLI 设计文档
|
||||||
|
|
||||||
|
> 本文档描述 `bl asset` 命令族的技术设计方案,供开发、评审与联调使用。
|
||||||
|
> 接口字段细节见同目录 [api-doc.md](./api-doc.md);命令路径与 help 结构见 [COMMAND-TREE.md](./COMMAND-TREE.md)。
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
|
||||||
|
百炼资产中心(Asset Center)提供模型生成资产的存储、检索、收藏、删除与容量管理能力。产品 PRD 要求 CLI 覆盖以下模块:
|
||||||
|
|
||||||
|
| 模块 | PRD 能力 |
|
||||||
|
| -------- | ----------------------------------------------- |
|
||||||
|
| 资产管理 | 列表、详情、收藏/取消收藏、删除、批量删除、下载 |
|
||||||
|
| 资产统计 | 总量、按类型统计 |
|
||||||
|
| 容量 | 已用容量、免费额度、超额单价 |
|
||||||
|
|
||||||
|
后端接口通过 **Zelda HTTP 网关** 暴露,Base Path 为 `/zelda/api/v1/bailian/asset`,详见 [api-doc.md](./api-doc.md)。
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
|
||||||
|
- 在 `packages/commands` 实现可复用命令库,由 `packages/cli/src/commands.ts` 注册为 `bl asset ...` 产品路径
|
||||||
|
- 遵循 monorepo 分层约定:`commands` 不写产品 bin 前缀;Console Gateway 命令统一 `auth: "console"`
|
||||||
|
- 服务端错误原样透传;CLI 仅对参数校验、缺凭证、网络失败等内部错误发出语义化 `BailianError`
|
||||||
|
- 支持 `--dry-run`、`--output json`、text 表格输出等现有 CLI 惯例
|
||||||
|
|
||||||
|
### 1.3 非目标
|
||||||
|
|
||||||
|
- 不在 `rag` 入口暴露(首期与 `deploy` / `finetune` 一致,仅 `bl`)
|
||||||
|
- 不暴露 `sendMqMessage` 等内部 MQ 接口
|
||||||
|
- 不在 `core` / `runtime` 层硬编码 `bl` 命令名或控制台 URL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. PRD → API → CLI 映射
|
||||||
|
|
||||||
|
### 2.1 资产管理
|
||||||
|
|
||||||
|
| PRD # | 能力 | CLI 命令 | API Action | 备注 |
|
||||||
|
| ----- | ------------- | ------------------------------------------- | --------------------------------------------- | ------------------------------------------ |
|
||||||
|
| 1 | 查看资产列表 | `bl asset list` | `listModelGeneratedAsset` | 游标分页;支持类型/模型/关键词/收藏/回收站 |
|
||||||
|
| 2 | 查看资产详情 | `bl asset get <asset-id>` | `getModelGeneratedAsset` | positional 或 `--asset-id` |
|
||||||
|
| 3 | 收藏/取消收藏 | `bl asset favorite` / `bl asset unfavorite` | `batchFavoriteAsset` / `batchUnfavoriteAsset` | 单 ID 也走 batch(长度 1) |
|
||||||
|
| 4 | 删除资产 | `bl asset delete` | `batchDeleteAsset` | 默认 `SOFT_DELETE`(移入回收站) |
|
||||||
|
| 5 | 批量删除 | `bl asset delete` | `batchDeleteAsset` | `--id` 可重复,最多 100 |
|
||||||
|
| 6 | 下载资产 | `bl asset download` | `batchGetAssetDownloadUrl` | 默认输出 URL;单资产可选 `--out` 落盘 |
|
||||||
|
|
||||||
|
**建议补充(API 已有、PRD 未写):**
|
||||||
|
|
||||||
|
| 能力 | CLI 命令 | API Action |
|
||||||
|
| ------------ | ------------------ | ------------------- |
|
||||||
|
| 从回收站恢复 | `bl asset restore` | `batchRestoreAsset` |
|
||||||
|
|
||||||
|
### 2.2 资产统计
|
||||||
|
|
||||||
|
| PRD # | 能力 | CLI 命令 | API Action | 备注 |
|
||||||
|
| ----- | ------------ | ---------------- | -------------------------- | --------------------------------------- |
|
||||||
|
| 7 | 查看资产统计 | `bl asset stats` | `countModelGeneratedAsset` | 输出 total / image / video / audio 计数 |
|
||||||
|
|
||||||
|
**转存失败数(PRD 子项):**
|
||||||
|
|
||||||
|
- API 支持 `syncOssDataStatus=SYNC_FAILED` 筛选,但无独立 `failureCount` 字段
|
||||||
|
- **Phase 1 方案**:`bl asset stats --sync-failed` 额外发起一次 count 查询,输出 `sync_failed_count`
|
||||||
|
- **Phase 3 备选**:等后端在 stats 响应中增加专用字段后收敛
|
||||||
|
|
||||||
|
### 2.4 容量
|
||||||
|
|
||||||
|
| PRD # | 能力 | CLI 命令 | API Action |
|
||||||
|
| ----- | ------------ | ------------------ | ----------------- |
|
||||||
|
| 14 | 查看容量信息 | `bl asset storage` | `getStorageQuota` |
|
||||||
|
|
||||||
|
### 2.5 可选扩展(API 有、PRD 未列)
|
||||||
|
|
||||||
|
| CLI 命令 | API Action | 优先级 |
|
||||||
|
| ------------------------------------- | ------------------------------- | -------------------- |
|
||||||
|
| `bl asset service status` | `checkAssetServiceSubscription` | P1 |
|
||||||
|
| `bl asset service enable` / `disable` | `subscribeAssetService` | P2 |
|
||||||
|
| `bl asset models list` | `listModels` | P1(配合 list 筛选) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 架构与分层
|
||||||
|
|
||||||
|
### 3.1 在 monorepo 中的位置
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/commands/src/commands/asset-center/*.ts ← 命令实现(本目录)
|
||||||
|
↓ export
|
||||||
|
packages/commands/src/index.ts
|
||||||
|
↓ import + map key
|
||||||
|
packages/cli/src/commands.ts ← "asset list": assetList, ...
|
||||||
|
↓
|
||||||
|
packages/runtime (createCli / authStage / registry)
|
||||||
|
```
|
||||||
|
|
||||||
|
约定:
|
||||||
|
|
||||||
|
- 实现文件按能力组织在本目录
|
||||||
|
- `usageArgs` / `exampleArgs` 不含 `bl` 前缀
|
||||||
|
- 所有 asset 命令 `auth: "console"`;不重复声明 `CONSOLE_AUTH_FLAGS`(runtime 自动注入)
|
||||||
|
|
||||||
|
### 3.2 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
asset-center/
|
||||||
|
├── api-doc.md # 后端 API 文档(已有)
|
||||||
|
├── DESIGN.md # 本文档
|
||||||
|
├── COMMAND-TREE.md # 命令树与 help 结构
|
||||||
|
├── types.ts # TypeScript 类型(ModelGeneratedAssetItem 等)
|
||||||
|
├── utils.ts # 公共请求构建、API 调用、响应解析
|
||||||
|
├── list.ts
|
||||||
|
├── get.ts
|
||||||
|
├── favorite.ts
|
||||||
|
├── unfavorite.ts
|
||||||
|
├── delete.ts
|
||||||
|
├── download.ts
|
||||||
|
├── stats.ts
|
||||||
|
└── storage.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 共享层 `utils.ts`
|
||||||
|
|
||||||
|
参考 `token-plan/utils.ts`、`usage/stats.ts` 的 `requireWorkspaceId` 模式。
|
||||||
|
|
||||||
|
#### 3.3.1 API 名称约定
|
||||||
|
|
||||||
|
与 `quota/list.ts` 中 `zeldaHttp.dashscopeModel./zelda/api/v1/...` 类似,资产中心预期为:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const ASSET_SERVICE = "bailianAsset"; // ⚠️ 编码前需 spike 确认
|
||||||
|
const ASSET_BASE = "/zelda/api/v1/bailian/asset";
|
||||||
|
|
||||||
|
function assetApi(action: string): string {
|
||||||
|
return `zeldaHttp.${ASSET_SERVICE}.${ASSET_BASE}/${action}`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
编码第一步用 `bl console call --api <name> --data '{...}'` 验证实际注册名。
|
||||||
|
|
||||||
|
#### 3.3.2 公共请求体
|
||||||
|
|
||||||
|
所有接口继承 `AssetHttpBaseRequest`(见 api-doc §公共请求参数):
|
||||||
|
|
||||||
|
| 字段 | CLI 来源 | 状态 |
|
||||||
|
| ---------------- | --------------------------------------------------------- | ---------- |
|
||||||
|
| `workspace` | `settings.workspaceId`(`--workspace-id` / env / config) | ✅ 已有 |
|
||||||
|
| `tenantId` | 待定 | ⚠️ 需确认 |
|
||||||
|
| `mainAccountUid` | 待定 | ⚠️ 需确认 |
|
||||||
|
| `apiSource` | 固定 `"CLI"` | 实现时写入 |
|
||||||
|
| `aliYunUid` 等 | 网关 session 注入或省略 | 待确认 |
|
||||||
|
|
||||||
|
`requireWorkspaceId(settings, binName)` 在缺少 workspace 时抛出 `BailianError(GENERAL)`,hint 指向 `bl workspace list`。
|
||||||
|
|
||||||
|
#### 3.3.3 调用封装
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function callAssetApi<T>(
|
||||||
|
ctx: CommandRunContext,
|
||||||
|
action: string,
|
||||||
|
body: Record<string, unknown>,
|
||||||
|
): Promise<T> {
|
||||||
|
const payload = { ...buildBaseRequest(ctx), ...body };
|
||||||
|
const raw = await ctx.client.console(assetApi(action), payload);
|
||||||
|
return extractAssetResponse<T>(raw);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3.4 响应解析
|
||||||
|
|
||||||
|
Console Gateway 响应可能存在多层嵌套(参考 `quota/list.ts` 的 `extractResponseData`):
|
||||||
|
|
||||||
|
1. 剥 gateway 外层:`data` → `DataV2` → `data` → ...
|
||||||
|
2. 到达业务 `Result<T>`:`{ success, code, message, data }`
|
||||||
|
3. 若 `success === false`:抛 `BailianError(GENERAL, message)`,**不翻译、不替换** message
|
||||||
|
4. 成功时返回 `data` 字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 命令实现规范
|
||||||
|
|
||||||
|
### 4.1 通用模式
|
||||||
|
|
||||||
|
每个命令文件遵循:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineCommand({
|
||||||
|
description: "...",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "...",
|
||||||
|
flags: { ... },
|
||||||
|
exampleArgs: ["...", "--output json"],
|
||||||
|
validate(ctx) { /* 跨 flag 条件校验 */ },
|
||||||
|
async run(ctx) {
|
||||||
|
const format = detectOutputFormat(ctx.settings.output);
|
||||||
|
if (ctx.settings.dryRun) {
|
||||||
|
emitResult({ api: assetApi("..."), data: { ... } }, format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await callAssetApi(ctx, "actionName", { ... });
|
||||||
|
// text 表格 或 emitResult(json)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
参考实现:`app/list.ts`(console + dry-run)、`dataset/list.ts`(表格输出)、`video/download.ts`(落盘)。
|
||||||
|
|
||||||
|
### 4.2 分页模型(`asset list`)
|
||||||
|
|
||||||
|
**与 `app list` 不同**:资产列表使用 **id 游标分页**,不是 page/pageSize 页码模式。
|
||||||
|
|
||||||
|
| Flag | API 字段 | 说明 |
|
||||||
|
| -------------- | ----------- | -------------------------- |
|
||||||
|
| `--page-size` | `pageSize` | 默认 10,最大 100 |
|
||||||
|
| `--next-token` | `nextToken` | 下一页游标(来自上次响应) |
|
||||||
|
| `--pre-token` | `preToken` | 上一页游标 |
|
||||||
|
|
||||||
|
JSON 输出保留 `nextToken` / `preToken` / `hasNext` / `hasPre`,便于脚本翻页。
|
||||||
|
|
||||||
|
### 4.3 批量 ID 传参
|
||||||
|
|
||||||
|
批量操作(favorite / unfavorite / delete / restore / download)统一:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
id: {
|
||||||
|
type: "array",
|
||||||
|
valueHint: "<asset-id>",
|
||||||
|
description: "Asset ID(s) to operate on (repeatable, max 100)",
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CLI 用法:`--id asset-001 --id asset-002` 或多次重复。实现时在 `validate` 中校验 `ids.length <= 100`。
|
||||||
|
|
||||||
|
### 4.4 输出格式
|
||||||
|
|
||||||
|
| 命令 | text 默认 | json |
|
||||||
|
| ---------- | -------------------------------------------------------------- | ----------------------------------------- |
|
||||||
|
| `list` | 表格:assetId / type / name / model / favorited / generateTime | items + pagination |
|
||||||
|
| `get` | 关键字段摘要 | 完整 item |
|
||||||
|
| `stats` | 数字摘要 | `{ total_count, image_count, ... }` |
|
||||||
|
| `storage` | 人类可读字节 + 单价 | 原始 quota 字段 |
|
||||||
|
| 写操作 | 一行确认(affectedCount) | `{ success, affected_count }` |
|
||||||
|
| `download` | URL 列表或 saved 路径 | `{ items: [{ asset_id, download_url }] }` |
|
||||||
|
|
||||||
|
使用 `formatTable`(`dataset/list.ts`)、`formatBytes`(`video/download.ts`)、`emitResult` / `emitBare`。
|
||||||
|
|
||||||
|
### 4.5 条件校验(`validate`)
|
||||||
|
|
||||||
|
| 命令 | 规则 |
|
||||||
|
| ---------- | --------------------------------------------------------- |
|
||||||
|
| `delete` | `--permanent` 映射 `PERMANENT_DELETE`;默认 `SOFT_DELETE` |
|
||||||
|
| 所有 batch | `assetIdList.length <= 100` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 关键命令 Flag 详设
|
||||||
|
|
||||||
|
### 5.1 `bl asset list`
|
||||||
|
|
||||||
|
| Flag | 类型 | API 映射 | 说明 |
|
||||||
|
| ------------------------ | ---------------- | ---------------------------- | ------------------------------------------------------------ |
|
||||||
|
| `--type` | string (choices) | `assetType` | `IMAGE` / `VIDEO` / `AUDIO` |
|
||||||
|
| `--model` | string | `modelName` | PRD「按模型筛选」 |
|
||||||
|
| `--keyword` | string | `assetName` | PRD「关键词」;是否同时搜 description 待产品确认 |
|
||||||
|
| `--favorited` | switch | `favorited: true` | 仅看收藏 |
|
||||||
|
| `--recycle-bin` | switch | `deleteStatus: SOFT_DELETED` | 仅看回收站 |
|
||||||
|
| `--sync-status` | string (choices) | `syncOssDataStatus` | `NOT_SYNCED` / `IN_SYNCING` / `SYNC_SUCCESS` / `SYNC_FAILED` |
|
||||||
|
| `--begin-time` | string | `beginTime` | ISO_LOCAL_DATE_TIME |
|
||||||
|
| `--end-time` | string | `endTime` | ISO_LOCAL_DATE_TIME |
|
||||||
|
| `--include-download-url` | switch | `includeDownloadUrl` | |
|
||||||
|
| `--include-thumbnail` | switch | `includeThumbnail` | |
|
||||||
|
| `--thumbnail-width` | number | `thumbnailWidth` | 配合 thumbnail |
|
||||||
|
| `--thumbnail-height` | number | `thumbnailHeight` | 配合 thumbnail |
|
||||||
|
| `--page-size` | number | `pageSize` | |
|
||||||
|
| `--next-token` | number | `nextToken` | |
|
||||||
|
| `--pre-token` | number | `preToken` | |
|
||||||
|
|
||||||
|
### 5.2 `bl asset get`
|
||||||
|
|
||||||
|
| 参数/Flag | 说明 |
|
||||||
|
| ------------------------------------------ | --------------------------------------- |
|
||||||
|
| `<asset-id>` | positional,primary |
|
||||||
|
| `--asset-id` | 与 positional 二选一(positional 优先) |
|
||||||
|
| `--include-download-url` | |
|
||||||
|
| `--include-thumbnail` | |
|
||||||
|
| `--thumbnail-width` / `--thumbnail-height` | |
|
||||||
|
|
||||||
|
### 5.3 `bl asset delete`
|
||||||
|
|
||||||
|
| Flag | 说明 |
|
||||||
|
| ------------- | --------------------------------------------------------- |
|
||||||
|
| `--id` | array, required, max 100 |
|
||||||
|
| `--permanent` | switch → `deleteType: PERMANENT_DELETE`;默认 SOFT_DELETE |
|
||||||
|
|
||||||
|
### 5.4 `bl asset download`
|
||||||
|
|
||||||
|
| Flag | 说明 |
|
||||||
|
| ------- | ----------------------------------------------------- |
|
||||||
|
| `--id` | array, required |
|
||||||
|
| `--out` | 仅当 `--id` 恰好 1 个时有效;调用 `downloadFile` 落盘 |
|
||||||
|
|
||||||
|
### 5.5 `bl asset stats`
|
||||||
|
|
||||||
|
| Flag | 说明 |
|
||||||
|
| ------------------------------------- | ---------------------------------------------- |
|
||||||
|
| (无 filter) | 默认 `deleteStatus: NORMAL` |
|
||||||
|
| `--recycle-bin` | `deleteStatus: SOFT_DELETED` |
|
||||||
|
| `--sync-failed` | 额外查询 `syncOssDataStatus: SYNC_FAILED` 计数 |
|
||||||
|
| `--type` / `--model` / `--keyword` 等 | 与 list 相同筛选维度(可选) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 风险与待确认项
|
||||||
|
|
||||||
|
### 6.1 P0 — 编码前必须对齐
|
||||||
|
|
||||||
|
| # | 问题 | 影响 | 建议动作 |
|
||||||
|
| --- | ------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------- |
|
||||||
|
| 1 | Console API 注册名(`zeldaHttp.{service}./zelda/api/v1/bailian/asset/*`) | 无法调用 | `bl console call` spike;与后端确认 service 名 |
|
||||||
|
| 2 | `tenantId` / `mainAccountUid` 由谁填充 | 所有接口必填 | 确认网关是否从 session 自动注入;否则扩展 config 或新增解析 API |
|
||||||
|
|
||||||
|
### 6.2 P1 — 产品设计
|
||||||
|
|
||||||
|
| # | 问题 | 建议默认 |
|
||||||
|
| --- | -------------------- | ----------------------------------------------- |
|
||||||
|
| 4 | 关键词搜索范围 | 仅 `assetName`;后续可加 `--search-description` |
|
||||||
|
| 5 | PRD 只提 image/video | CLI 暴露 IMAGE/VIDEO/AUDIO(与 API 一致) |
|
||||||
|
| 6 | 下载行为 | 默认输出 URL;单 ID + `--out` 落盘 |
|
||||||
|
| 7 | 永久删除 | 提供 `--permanent`,help 注明不可恢复 |
|
||||||
|
| 8 | 服务未开通 | 不预检查;失败时透传服务端 message |
|
||||||
|
| 9 | 收藏命令形态 | 两个命令 `favorite` / `unfavorite`(语义清晰) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 错误处理
|
||||||
|
|
||||||
|
遵循 [AGENTS.md](../../../../../../AGENTS.md) 错误边界:
|
||||||
|
|
||||||
|
| 场景 | 处理 |
|
||||||
|
| --------------------------------- | --------------------------------------------- |
|
||||||
|
| 缺 `--workspace-id` | `BailianError(GENERAL)` + hint |
|
||||||
|
| 缺 console token | authStage 抛 `BailianError(AUTH)` |
|
||||||
|
| flag 校验失败 | `UsageError` (exit 2) |
|
||||||
|
| HTTP 4xx/5xx / 业务 success=false | `BailianError(GENERAL)`,message **原样透传** |
|
||||||
|
| batch ID > 100 | `UsageError` |
|
||||||
|
|
||||||
|
Console 未登录参考 `mcp/list.ts`:检测 `BailianGateway.Login.NotLogined` 时 hint 指向 `bl auth login --console`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 测试策略
|
||||||
|
|
||||||
|
新建 `packages/cli/tests/e2e/asset.e2e.test.ts`,遵循 [cli-e2e-tests.md](../../../../../../docs/agents/cli-e2e-tests.md)。
|
||||||
|
|
||||||
|
### 8.1 不 skip 层
|
||||||
|
|
||||||
|
- `bl asset` 分组 help
|
||||||
|
- 各子命令 `--help`
|
||||||
|
- 缺参 → exit 2
|
||||||
|
|
||||||
|
### 8.2 Console skip 层(`isConsoleE2EReady()`)
|
||||||
|
|
||||||
|
- 各命令 `--dry-run` 输出 api + data
|
||||||
|
- 真实 `asset list` / `asset storage` 集成(需已开通资产中心的工作空间)
|
||||||
|
|
||||||
|
环境:`BAILIAN_E2E=1` + console `access_token` + `BAILIAN_WORKSPACE_ID`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 注册与文档变更清单
|
||||||
|
|
||||||
|
| 文件 | 变更 |
|
||||||
|
| -------------------------------------------------- | ------------------- |
|
||||||
|
| `packages/commands/src/commands/asset-center/*.ts` | 新建 |
|
||||||
|
| `packages/commands/src/index.ts` | export |
|
||||||
|
| `packages/cli/src/commands.ts` | 注册 map |
|
||||||
|
| `packages/cli/tests/e2e/asset.e2e.test.ts` | 新建 |
|
||||||
|
| `skills/bailian-cli/reference/` | pre-commit 自动生成 |
|
||||||
|
| `README.md` / `README.zh.md` | 发版前补充命令一览 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 分期实施
|
||||||
|
|
||||||
|
### Phase 1 — 核心资产(P0)
|
||||||
|
|
||||||
|
```
|
||||||
|
asset list | get | favorite | unfavorite | delete | restore | download | stats | storage
|
||||||
|
```
|
||||||
|
|
||||||
|
**前置:** §6.1 #1 #2 确认。
|
||||||
|
|
||||||
|
### Phase 2+ — 可选扩展
|
||||||
|
|
||||||
|
```
|
||||||
|
asset models list | service status | service enable/disable
|
||||||
|
```
|
||||||
|
|
||||||
|
> OSS 转存相关命令(`asset-center oss *` / `transfer list`)已取消,不再排期。
|
||||||
|
|
||||||
|
## 11. 参考
|
||||||
|
|
||||||
|
- 命令注册:[docs/agents/command-add-remove.md](../../../../../../docs/agents/command-add-remove.md)
|
||||||
|
- E2E 规范:[docs/agents/cli-e2e-tests.md](../../../../../../docs/agents/cli-e2e-tests.md)
|
||||||
|
- Console 命令样例:`packages/commands/src/commands/app/list.ts`
|
||||||
|
- 游标/表格:`packages/commands/src/commands/quota/list.ts`
|
||||||
|
- workspace 必填:`packages/commands/src/commands/usage/stats.ts`
|
||||||
|
- 文件落盘:`packages/commands/src/commands/video/download.ts`
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Asset Center 命令测试报告 — Phase 2
|
||||||
|
|
||||||
|
- **测试时间**: 2026-07-10 09:07:09 (UTC)
|
||||||
|
- **Workspace**: `llm-0xvms4kqhbqjlg8s`
|
||||||
|
- **测试 IMAGE**: `asset_98175cbf83294f7b8ada86657623dcf3`
|
||||||
|
- **测试 VIDEO**: `asset_df026105d2274ff9b8c824058fa23d60`
|
||||||
|
- **策略**: 可逆写操作(favorite/unfavorite 往返);download 到 /tmp 后删除;其余只读
|
||||||
|
- **汇总**: 16 通过 / 0 失败 / 16 总计
|
||||||
|
|
||||||
|
> Phase 1 报告见同目录 [TEST-REPORT.md](./TEST-REPORT.md)(24 项 dry-run + 只读基础验证)
|
||||||
|
|
||||||
|
## Phase 2 测试结果
|
||||||
|
|
||||||
|
| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |
|
||||||
|
| --- | ---- | ------------------------------------------ | -------- | ------- | ---- | ------- | -------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 1 | 下载 | `asset-center download (IMAGE)` | 真实调用 | ✅ PASS | 0 | 20045ms | saved /tmp/asset-center-test-asset_98175cbf83294f7b8ada86657623dcf3.png (1449847 bytes, reported 1.4 MB) |
|
||||||
|
| 2 | 查询 | `asset-center get --include-download-url` | 真实调用 | ✅ PASS | 0 | 21226ms | download_url present |
|
||||||
|
| 3 | 查询 | `asset-center list --include-download-url` | 真实调用 | ✅ PASS | 0 | 19057ms | items contain download_url |
|
||||||
|
| 4 | 查询 | `asset-center list --next-token` | 真实调用 | ✅ PASS | 0 | 19584ms | page2=3 items, overlap=0, has_pre=true |
|
||||||
|
| 5 | 统计 | `asset-center stats --type IMAGE` | 真实调用 | ✅ PASS | 0 | 18830ms | image=7, total=7 |
|
||||||
|
| 6 | 统计 | `asset-center stats --sync-failed` | 真实调用 | ✅ PASS | 0 | 19084ms | total=27, sync_failed=0 |
|
||||||
|
| 7 | 查询 | `asset-center list --recycle-bin` | 真实调用 | ✅ PASS | 0 | 19314ms | 0 soft-deleted item(s) |
|
||||||
|
| 8 | 输出 | `asset-center list (text)` | 真实调用 | ✅ PASS | 0 | 17840ms | 4 lines table output |
|
||||||
|
| 9 | 收藏 | `asset-center favorite (真实)` | 真实调用 | ✅ PASS | 0 | 22082ms | affected=1 |
|
||||||
|
| 10 | 收藏 | `get 验证 favorited=true` | 真实调用 | ✅ PASS | 0 | 18865ms | favorited=true ✓ |
|
||||||
|
| 11 | 查询 | `list --favorited 含测试资产` | 真实调用 | ✅ PASS | 0 | 22120ms | found in favorited list |
|
||||||
|
| 12 | 收藏 | `asset-center unfavorite (真实)` | 真实调用 | ✅ PASS | 0 | 22133ms | affected=1 |
|
||||||
|
| 13 | 收藏 | `get 验证 favorited=false (恢复)` | 真实调用 | ✅ PASS | 0 | 27797ms | favorited=false ✓ |
|
||||||
|
| 14 | 收藏 | `favorite 批量 (--id x2)` | 真实调用 | ✅ PASS | 0 | 22947ms | affected=2 |
|
||||||
|
| 15 | 收藏 | `unfavorite 批量 (--id x2)` | 真实调用 | ✅ PASS | 0 | 15385ms | affected=2 |
|
||||||
|
| 16 | 边界 | `get 不存在的 asset-id` | 真实调用 | ✅ PASS | 1 | 12207ms | exit 1, 服务端错误原样透传: "资产不存在" |
|
||||||
|
|
||||||
|
## 边界行为说明
|
||||||
|
|
||||||
|
查询不存在的 `asset-id` 时,服务端返回业务错误 **「资产不存在」**,CLI 按约定 **原样透传**(exit code 1),不会替换为本地文案。这与 AGENTS.md 错误处理边界一致。
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Asset Center 命令测试报告
|
||||||
|
|
||||||
|
- **测试时间**: 2026-07-10 08:41:06 (UTC)
|
||||||
|
- **Workspace**: `llm-0xvms4kqhbqjlg8s`
|
||||||
|
- **样本 Asset ID**: `asset_df026105d2274ff9b8c824058fa23d60`
|
||||||
|
- **策略**: 只读命令真实调用;写操作/下载一律 `--dry-run`
|
||||||
|
- **汇总**: 24 通过 / 0 失败 / 24 总计
|
||||||
|
|
||||||
|
## 测试结果
|
||||||
|
|
||||||
|
| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |
|
||||||
|
| --- | ---- | ---------------------------------- | -------- | ------- | ---- | ------- | -------------------------------------------------------------- |
|
||||||
|
| 1 | 查询 | `asset-center list` | 真实调用 | ✅ PASS | 0 | 18962ms | 3 item(s), next=94 |
|
||||||
|
| 2 | 查询 | `asset-center list --type IMAGE` | 真实调用 | ✅ PASS | 0 | 16411ms | 2 item(s), next=90 |
|
||||||
|
| 3 | 查询 | `asset-center get` | 真实调用 | ✅ PASS | 0 | 16541ms | {gmtModified, aliyunUid, generateTime, aliyunMainId} |
|
||||||
|
| 4 | 统计 | `asset-center stats` | 真实调用 | ✅ PASS | 0 | 14226ms | total=27 |
|
||||||
|
| 5 | 统计 | `asset-center storage` | 真实调用 | ✅ PASS | 0 | 18622ms | {used_storage_size, free_storage_quota, extra_storage_price} |
|
||||||
|
| 9 | 查询 | `asset-center list --dry-run` | dry-run | ✅ PASS | 0 | 23855ms | dry-run → /zelda/api/v1/bailian/asset/listModelGeneratedAsset |
|
||||||
|
| 10 | 查询 | `asset-center get --dry-run` | dry-run | ✅ PASS | 0 | 17398ms | dry-run → /zelda/api/v1/bailian/asset/getModelGeneratedAsset |
|
||||||
|
| 11 | 收藏 | `asset-center favorite` | dry-run | ✅ PASS | 0 | 16471ms | dry-run → /zelda/api/v1/bailian/asset/batchFavoriteAsset |
|
||||||
|
| 12 | 收藏 | `asset-center unfavorite` | dry-run | ✅ PASS | 0 | 19724ms | dry-run → /zelda/api/v1/bailian/asset/batchUnfavoriteAsset |
|
||||||
|
| 13 | 删除 | `asset-center delete` | dry-run | ✅ PASS | 0 | 20612ms | dry-run → /zelda/api/v1/bailian/asset/batchDeleteAsset |
|
||||||
|
| 14 | 下载 | `asset-center download` | dry-run | ✅ PASS | 0 | 20348ms | dry-run → /zelda/api/v1/bailian/asset/batchGetAssetDownloadUrl |
|
||||||
|
| 15 | 统计 | `asset-center stats --dry-run` | dry-run | ✅ PASS | 0 | 14898ms | dry-run → /zelda/api/v1/bailian/asset/countModelGeneratedAsset |
|
||||||
|
| 16 | 统计 | `asset-center storage --dry-run` | dry-run | ✅ PASS | 0 | 14305ms | dry-run → /zelda/api/v1/bailian/asset/getStorageQuota |
|
||||||
|
| 21 | 校验 | `asset-center get (缺 asset-id)` | 参数校验 | ✅ PASS | 2 | 15202ms | Error: Missing required flag: --asset-id |
|
||||||
|
| 22 | 校验 | `asset-center favorite (缺 --id)` | 参数校验 | ✅ PASS | 2 | 16697ms | Error: Missing required flag: --id |
|
||||||
|
| 23 | 校验 | `asset-center download (缺 --out)` | 参数校验 | ✅ PASS | 2 | 18803ms | Error: Missing required flag: --out |
|
||||||
|
|
||||||
|
## 模式说明
|
||||||
|
|
||||||
|
| 模式 | 说明 |
|
||||||
|
| -------- | -------------------------------------------------- |
|
||||||
|
| 真实调用 | 只读 API,不修改数据 |
|
||||||
|
| dry-run | 输出 `{ api, data, gateway }` 请求体,不发起写操作 |
|
||||||
|
| 参数校验 | 预期 exit code 2(用法错误) |
|
||||||
@@ -0,0 +1,624 @@
|
|||||||
|
# BailianAssetZeldaHttpService API 文档
|
||||||
|
|
||||||
|
通过 Zelda 网关调用 bailian-asset HTTP 接口文档。
|
||||||
|
|
||||||
|
## 基础信息
|
||||||
|
|
||||||
|
- **Base Path**: `/zelda/api/v1/bailian/asset`
|
||||||
|
- **Method**: POST
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
- **Accept**: `application/json`
|
||||||
|
|
||||||
|
## 统一响应格式
|
||||||
|
|
||||||
|
所有接口返回 `Result<T>` 结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "string",
|
||||||
|
"success": true,
|
||||||
|
"code": "string",
|
||||||
|
"message": "string",
|
||||||
|
"data": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --------- | ------- | ---------------------- |
|
||||||
|
| requestId | String | 请求唯一ID |
|
||||||
|
| success | Boolean | 是否成功 |
|
||||||
|
| code | String | 错误码(失败时返回) |
|
||||||
|
| message | String | 错误信息(失败时返回) |
|
||||||
|
| data | Object | 业务数据(成功时返回) |
|
||||||
|
|
||||||
|
## 公共请求参数(基类字段)
|
||||||
|
|
||||||
|
所有接口请求体均继承自 `AssetHttpBaseRequest`,包含以下公共字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| -------------- | ------ | ---- | ---------------------------------------------- |
|
||||||
|
| requestId | String | 否 | 请求唯一ID |
|
||||||
|
| apiSource | String | 否 | 调用入口渠道,如 OpenAPI、CloudSDK |
|
||||||
|
| tenantId | String | 是 | 内部租户ID |
|
||||||
|
| workspace | String | 是 | 业务空间ID |
|
||||||
|
| aliYunUid | String | 否 | 阿里云子账号ID |
|
||||||
|
| mainAccountUid | String | 是 | 阿里云主账号ID |
|
||||||
|
| callerType | String | 否 | 账号类型:partner/customer/sub/AssumedRoleUser |
|
||||||
|
| callerParentId | Long | 否 | 调用者所属主账号ID |
|
||||||
|
| accessKeyId | String | 否 | STS认证:用户AccessKeyId |
|
||||||
|
| securityToken | String | 否 | STS认证:扮演者的STS Token |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 开通/关闭资产中心服务
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/subscribeAssetService`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ------ | ------------ | ---- | --------------------------------------- |
|
||||||
|
| action | String(Enum) | 是 | 操作类型:`ENABLE`-开通,`DISABLE`-关闭 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------ | ------------ | ------------ |
|
||||||
|
| status | String(Enum) | 当前服务状态 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"action": "ENABLE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 批量收藏资产
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/batchFavoriteAsset`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ----------- | ------------ | ---- | ---------------------------------- |
|
||||||
|
| assetIdList | List<String> | 是 | 待收藏的资产ID列表,长度不超过 100 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------- | ------- | -------------------- |
|
||||||
|
| success | Boolean | 是否收藏成功 |
|
||||||
|
| affectedCount | Integer | 实际被收藏的资产数量 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetIdList": ["asset-001", "asset-002", "asset-003"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 批量取消收藏资产
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/batchUnfavoriteAsset`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ----------- | ------------ | ---- | -------------------------------------- |
|
||||||
|
| assetIdList | List<String> | 是 | 待取消收藏的资产ID列表,长度不超过 100 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------- | ------- | ------------------------ |
|
||||||
|
| success | Boolean | 是否取消收藏成功 |
|
||||||
|
| affectedCount | Integer | 实际被取消收藏的资产数量 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetIdList": ["asset-001", "asset-002"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 批量删除资产
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/batchDeleteAsset`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ----------- | ------------ | ---- | ----------------------------------------------------------- |
|
||||||
|
| assetIdList | List<String> | 是 | 待删除的资产ID列表,长度不超过 100 |
|
||||||
|
| deleteType | String(Enum) | 是 | 删除类型:`SOFT_DELETE`-软删除,`PERMANENT_DELETE`-彻底删除 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------- | ------- | -------------------- |
|
||||||
|
| success | Boolean | 是否删除成功 |
|
||||||
|
| affectedCount | Integer | 实际被删除的资产数量 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetIdList": ["asset-001", "asset-002"],
|
||||||
|
"deleteType": "SOFT_DELETE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 批量恢复软删除资产
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/batchRestoreAsset`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ----------- | ------------ | ---- | ---------------------------------- |
|
||||||
|
| assetIdList | List<String> | 是 | 待恢复的资产ID列表,长度不超过 100 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------- | ------- | -------------------- |
|
||||||
|
| success | Boolean | 是否恢复成功 |
|
||||||
|
| affectedCount | Integer | 实际被恢复的资产数量 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetIdList": ["asset-001", "asset-002"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 分页查询模型生成资产
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/listModelGeneratedAsset`
|
||||||
|
|
||||||
|
采用 id 游标分页,默认 pageSize=10,最大 100。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ------------------- | ------------ | ---- | ---------------------------------------------------------------------------------- |
|
||||||
|
| preToken | Long | 否 | 向前翻页游标 |
|
||||||
|
| nextToken | Long | 否 | 向后翻页游标(查询下一页时传入上一次响应的 nextToken) |
|
||||||
|
| pageSize | Integer | 否 | 每页大小,默认 10,最大 100 |
|
||||||
|
| includeDownloadUrl | Boolean | 否 | 是否返回文件下载链接,默认 false |
|
||||||
|
| includeThumbnail | Boolean | 否 | 是否返回资产缩放图 URL,默认 false |
|
||||||
|
| thumbnailWidth | Integer | 否 | 缩放图宽度(像素),includeThumbnail=true 时生效 |
|
||||||
|
| thumbnailHeight | Integer | 否 | 缩放图高度(像素),includeThumbnail=true 时生效 |
|
||||||
|
| softDeleteTimeOrder | String(Enum) | 否 | 软删除时间排序方式:`ASC`-正序,`DESC`-倒序;仅在 deleteStatus=SOFT_DELETED 时有效 |
|
||||||
|
| assetType | String(Enum) | 否 | 资产类型:`IMAGE`-图片,`VIDEO`-视频,`AUDIO`-音频 |
|
||||||
|
| favorited | Boolean | 否 | 是否被收藏 |
|
||||||
|
| assetName | String | 否 | 资产名称(子串模糊匹配) |
|
||||||
|
| assetDescription | String | 否 | 资产描述(子串模糊匹配) |
|
||||||
|
| trusted | Boolean | 否 | 是否可信 |
|
||||||
|
| modelType | String | 否 | 生成资产的模型类型 |
|
||||||
|
| modelName | String | 否 | 生成资产的模型型号 |
|
||||||
|
| syncWhiteListStatus | String(Enum) | 否 | 同步白名单状态:`NOT_SYNCED` / `SYNC_SUCCESS` / `SYNC_FAILED` |
|
||||||
|
| syncOssDataStatus | String(Enum) | 否 | 同步OSS数据状态:`NOT_SYNCED` / `IN_SYNCING` / `SYNC_SUCCESS` / `SYNC_FAILED` |
|
||||||
|
| deleteStatus | String(Enum) | 否 | 删除状态:`NORMAL` / `SOFT_DELETED` / `PERMANENTLY_DELETED` |
|
||||||
|
| beginTime | String | 否 | 资产生成时间起始(含),格式 ISO_LOCAL_DATE_TIME,如 `2023-10-25T14:30:00` |
|
||||||
|
| endTime | String | 否 | 资产生成时间截止(含),格式 ISO_LOCAL_DATE_TIME,如 `2023-10-25T14:30:00` |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --------- | ----------------------------- | ------------ |
|
||||||
|
| dataList | List<ModelGeneratedAssetItem> | 资产列表 |
|
||||||
|
| preToken | Long | 前一页游标 |
|
||||||
|
| nextToken | Long | 下一页游标 |
|
||||||
|
| hasNext | Boolean | 是否有下一页 |
|
||||||
|
| hasPre | Boolean | 是否有前一页 |
|
||||||
|
|
||||||
|
**ModelGeneratedAssetItem 结构:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
|
| id | Long | 主键 ID(分页游标 token) |
|
||||||
|
| gmtCreate | Date | 创建时间 |
|
||||||
|
| gmtModified | Date | 修改时间 |
|
||||||
|
| workspaceId | String | 工作空间ID |
|
||||||
|
| tenantId | String | 租户ID |
|
||||||
|
| aliyunUid | String | 阿里云子账号ID |
|
||||||
|
| aliyunMainId | String | 阿里云主账号ID |
|
||||||
|
| assetId | String | 资产 ID |
|
||||||
|
| assetType | String | 资产类型:IMAGE/VIDEO/AUDIO |
|
||||||
|
| assetSource | String | 资产来源:MODEL_GENERATED/OFFICIAL/USER_UPLOADED |
|
||||||
|
| favorited | Boolean | 是否被收藏 |
|
||||||
|
| assetName | String | 资产名称 |
|
||||||
|
| assetDescription | String | 资产描述 |
|
||||||
|
| assetSize | Long | 资产大小(字节) |
|
||||||
|
| md5 | String | 资产 MD5 |
|
||||||
|
| ossBucket | String | 资产所在 OSS Bucket |
|
||||||
|
| ossKey | String | 资产在 OSS bucket 中的 key |
|
||||||
|
| region | String | 工作空间地域 |
|
||||||
|
| ossRegion | String | 资产所在 OSS bucket 的地域 |
|
||||||
|
| trusted | Boolean | 是否可信 |
|
||||||
|
| modelType | String | 模型类型 |
|
||||||
|
| modelName | String | 模型型号 |
|
||||||
|
| syncWhiteListStatus | String | 同步白名单状态 |
|
||||||
|
| syncOssDataStatus | String | 同步 OSS 数据状态 |
|
||||||
|
| deleteStatus | String | 删除状态:NORMAL/SOFT_DELETED/PERMANENTLY_DELETED |
|
||||||
|
| generateTime | Long | 资产生成时间戳(毫秒) |
|
||||||
|
| softDeleteDays | Integer | 已被软删除的天数(仅当 deleteStatus=SOFT_DELETED 且请求 softDeleteTimeOrder 时返回) |
|
||||||
|
| originalOssUrl | String | 原始 OSS URL |
|
||||||
|
| downloadUrl | String | 文件下载链接(仅当请求 includeDownloadUrl=true 时返回) |
|
||||||
|
| thumbnailUrl | String | 资产缩放图 URL(仅当请求 includeThumbnail=true 时返回;视频返回首帧缩放图,图片返回缩放图,音频返回 null) |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"pageSize": 20,
|
||||||
|
"includeDownloadUrl": true,
|
||||||
|
"includeThumbnail": true,
|
||||||
|
"thumbnailWidth": 200,
|
||||||
|
"thumbnailHeight": 200,
|
||||||
|
"assetType": "IMAGE",
|
||||||
|
"favorited": true,
|
||||||
|
"beginTime": "2024-01-01T00:00:00",
|
||||||
|
"endTime": "2024-12-31T23:59:59"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"dataList": [
|
||||||
|
{
|
||||||
|
"id": 1001,
|
||||||
|
"assetId": "asset-001",
|
||||||
|
"assetType": "IMAGE",
|
||||||
|
"assetName": "generated_image_01.png",
|
||||||
|
"assetDescription": "A landscape painting",
|
||||||
|
"favorited": true,
|
||||||
|
"generateTime": 1700000000000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nextToken": 1000,
|
||||||
|
"hasNext": true,
|
||||||
|
"hasPre": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 统计模型生成资产数量
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/countModelGeneratedAsset`
|
||||||
|
|
||||||
|
查询条件与 `listModelGeneratedAsset` 一致(不需要分页参数),按资产类型分组返回数量。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ------------------- | ------------ | ---- | ------------------------------------------------ |
|
||||||
|
| assetType | String(Enum) | 否 | 资产类型:`IMAGE` / `VIDEO` / `AUDIO` |
|
||||||
|
| favorited | Boolean | 否 | 是否被收藏 |
|
||||||
|
| assetName | String | 否 | 资产名称(子串模糊匹配) |
|
||||||
|
| assetDescription | String | 否 | 资产描述(子串模糊匹配) |
|
||||||
|
| trusted | Boolean | 否 | 是否可信 |
|
||||||
|
| modelType | String | 否 | 模型类型 |
|
||||||
|
| modelName | String | 否 | 模型型号 |
|
||||||
|
| syncWhiteListStatus | String(Enum) | 否 | 同步白名单状态 |
|
||||||
|
| syncOssDataStatus | String(Enum) | 否 | 同步OSS数据状态 |
|
||||||
|
| deleteStatus | String(Enum) | 否 | 删除状态 |
|
||||||
|
| beginTime | String | 否 | 资产生成时间起始(含),格式 ISO_LOCAL_DATE_TIME |
|
||||||
|
| endTime | String | 否 | 资产生成时间截止(含),格式 ISO_LOCAL_DATE_TIME |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ---------- | ---- | ---------------- |
|
||||||
|
| imageCount | Long | 图片类型资产数量 |
|
||||||
|
| videoCount | Long | 视频类型资产数量 |
|
||||||
|
| audioCount | Long | 音频类型资产数量 |
|
||||||
|
| totalCount | Long | 总资产数量 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"deleteStatus": "NORMAL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"imageCount": 150,
|
||||||
|
"videoCount": 30,
|
||||||
|
"audioCount": 20,
|
||||||
|
"totalCount": 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 批量获取资产下载链接
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/batchGetAssetDownloadUrl`
|
||||||
|
|
||||||
|
一次最多获取 100 个资产的下载链接。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ----------- | ------------ | ---- | ------------------------------------------ |
|
||||||
|
| assetIdList | List<String> | 是 | 待获取下载链接的资产ID列表,长度不超过 100 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ----- | -------------------------- | -------------------------------- |
|
||||||
|
| items | List<AssetDownloadUrlItem> | 资产下载链接列表,按请求顺序返回 |
|
||||||
|
|
||||||
|
**AssetDownloadUrlItem 结构:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ----------- | ------ | ---------------------------------------------------------- |
|
||||||
|
| assetId | String | 资产 ID |
|
||||||
|
| downloadUrl | String | 资产下载链接(带签名);资产不存在或缺少 OSS 信息时为 null |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetIdList": ["asset-001", "asset-002", "asset-003"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 查询模型生成资产详情
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/getModelGeneratedAsset`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ------------------ | ------- | ---- | ------------------------------------------------ |
|
||||||
|
| assetId | String | 是 | 待查询的资产 ID |
|
||||||
|
| includeDownloadUrl | Boolean | 否 | 是否返回文件下载链接,默认 false |
|
||||||
|
| includeThumbnail | Boolean | 否 | 是否返回资产缩放图 URL,默认 false |
|
||||||
|
| thumbnailWidth | Integer | 否 | 缩放图宽度(像素),includeThumbnail=true 时生效 |
|
||||||
|
| thumbnailHeight | Integer | 否 | 缩放图高度(像素),includeThumbnail=true 时生效 |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ---- | ----------------------- | ------------------------ |
|
||||||
|
| item | ModelGeneratedAssetItem | 资产详情(结构同第12节) |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"assetId": "asset-001",
|
||||||
|
"includeDownloadUrl": true,
|
||||||
|
"includeThumbnail": true,
|
||||||
|
"thumbnailWidth": 200,
|
||||||
|
"thumbnailHeight": 200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 获取存储额度与用量
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/getStorageQuota`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
仅需公共参数(`workspace`、`tenantId` 必填)。
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ----------------- | ------ | --------------------------------------------- |
|
||||||
|
| freeStorageQuota | Long | 平台免费存储额度(单位:字节) |
|
||||||
|
| usedStorageSize | Long | 当前用户已使用的存储量(单位:字节) |
|
||||||
|
| extraStoragePrice | String | 超出免费额度的费用说明(如 "¥0.12元/GB/月") |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. 通用 MQ 消息发送
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/sendMqMessage`
|
||||||
|
|
||||||
|
向指定的 RocketMQ Producer 发送 JSON 格式的消息。producerType 对应 `EnumRocketMqProducerType` 枚举的 code 值,mainAccountUid 作为消息路由 key。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| ------------ | ------ | ---- | --------------------------------------------------------------------------------------------------- |
|
||||||
|
| producerType | String | 是 | 生产者类型:`WHITE_LIST_ASSET_PRODUCER` / `OSS_DATA_HANDEL_PRODUCER` / `ORIGIN_ASSET_INFO_PRODUCER` |
|
||||||
|
| messageBody | String | 是 | JSON 格式的消息体字符串 |
|
||||||
|
| messageKey | String | 否 | 消息 key(可选,为空时默认使用 mainAccountUid) |
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------- | ------- | ------------ |
|
||||||
|
| success | Boolean | 是否发送成功 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890",
|
||||||
|
"producerType": "ORIGIN_ASSET_INFO_PRODUCER",
|
||||||
|
"messageBody": "{\"time\":1700000000000,\"modelId\":\"model-abc\",\"type\":\"IMAGE\",\"workspace\":\"ws-xxxxx\",\"ossUrl\":\"oss://my-bucket/path/to/asset.png\"}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. 查询模型列表
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/listModels`
|
||||||
|
|
||||||
|
返回当前服务管理的模型配置列表,按资产类型分组,包含每个模型的ID及是否可信标识。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
仅需公共参数。
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ----------- | ---------------- | ---------------------------- |
|
||||||
|
| modelGroups | List<ModelGroup> | 按资产类型分组的模型配置列表 |
|
||||||
|
|
||||||
|
**ModelGroup 结构:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --------- | --------------- | ------------------------------------- |
|
||||||
|
| assetType | String(Enum) | 资产类型:`IMAGE` / `VIDEO` / `AUDIO` |
|
||||||
|
| models | List<ModelItem> | 该类型下管理的模型列表 |
|
||||||
|
|
||||||
|
**ModelItem 结构:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------- | ------- | -------------- |
|
||||||
|
| modelId | String | 模型ID |
|
||||||
|
| trusted | Boolean | 该模型是否可信 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"modelGroups": [
|
||||||
|
{
|
||||||
|
"assetType": "IMAGE",
|
||||||
|
"models": [
|
||||||
|
{ "modelId": "qwen-image-3.0", "trusted": true },
|
||||||
|
{ "modelId": "qwen-image-3.0-pro", "trusted": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assetType": "VIDEO",
|
||||||
|
"models": [
|
||||||
|
{ "modelId": "wan2.7-t2v", "trusted": true },
|
||||||
|
{ "modelId": "wan2.7-i2v", "trusted": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. 查询用户是否已开通资产中心服务
|
||||||
|
|
||||||
|
**POST** `/zelda/api/v1/bailian/asset/checkAssetServiceSubscription`
|
||||||
|
|
||||||
|
查询当前用户是否已开通资产中心服务。
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
仅需公共参数(`mainAccountUid` 必填)。
|
||||||
|
|
||||||
|
### 响应 data
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| ------- | ------- | ------------------------------------------------- |
|
||||||
|
| enabled | Boolean | 是否已开通资产中心服务:true-已开通,false-未开通 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace": "ws-xxxxx",
|
||||||
|
"tenantId": "123456",
|
||||||
|
"mainAccountUid": "1234567890"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||||
|
import type { AssetBatchResponse } from "./types.ts";
|
||||||
|
import {
|
||||||
|
ASSET_API,
|
||||||
|
ASSET_ID_FLAG,
|
||||||
|
callAssetApi,
|
||||||
|
dryRunPayload,
|
||||||
|
validateAssetIds,
|
||||||
|
} from "./utils.ts";
|
||||||
|
|
||||||
|
const DELETE_FLAGS = {
|
||||||
|
...ASSET_ID_FLAG,
|
||||||
|
permanent: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Permanently delete assets (cannot be restored)",
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center delete` — 删除资产(默认软删到回收站)。
|
||||||
|
*
|
||||||
|
* 软删可恢复;--permanent 为永久删除。支持重复 --id(单次最多 100 个)。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Delete assets (soft delete to recycle bin by default)",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "--id <asset-id> [--id <asset-id>...] [flags]",
|
||||||
|
flags: DELETE_FLAGS,
|
||||||
|
exampleArgs: ["--id asset-001", "--id asset-001 --id asset-002", "--id asset-001 --permanent"],
|
||||||
|
validate(flags) {
|
||||||
|
return validateAssetIds(flags.id);
|
||||||
|
},
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const assetIdList = flags.id;
|
||||||
|
const deleteType = flags.permanent ? "PERMANENT_DELETE" : "SOFT_DELETE";
|
||||||
|
const body = { assetIdList, deleteType };
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.batchDeleteAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetBatchResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.batchDeleteAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const verb = flags.permanent ? "Permanently deleted" : "Deleted";
|
||||||
|
if (settings.quiet || format === "text") {
|
||||||
|
emitBare(`${verb} ${data.affectedCount ?? assetIdList.length} asset(s).`);
|
||||||
|
} else {
|
||||||
|
emitResult(
|
||||||
|
{ affected_count: data.affectedCount ?? assetIdList.length, delete_type: deleteType },
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
defineCommand,
|
||||||
|
detectOutputFormat,
|
||||||
|
BailianError,
|
||||||
|
ExitCode,
|
||||||
|
type FlagsDef,
|
||||||
|
} from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare, padEnd } from "bailian-cli-runtime";
|
||||||
|
import type { AssetDownloadResponse } from "./types.ts";
|
||||||
|
import { ASSET_API, callAssetApi, dryRunPayload } from "./utils.ts";
|
||||||
|
|
||||||
|
const DOWNLOAD_FLAGS = {
|
||||||
|
id: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<asset-id>",
|
||||||
|
description: "Asset ID to get download URL for",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center download` — 通过资产 ID 获取签名下载链接。
|
||||||
|
*
|
||||||
|
* 调用 batchGetAssetDownloadUrl,输出 download URL,不落盘。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Get a signed download URL for an asset by ID",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "--id <asset-id>",
|
||||||
|
flags: DOWNLOAD_FLAGS,
|
||||||
|
exampleArgs: ["--id asset-001", "--id asset-001 --output json", "--id asset-001 --quiet"],
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const assetId = flags.id;
|
||||||
|
const body = { assetIdList: [assetId] };
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
{
|
||||||
|
asset_id: assetId,
|
||||||
|
action: "download",
|
||||||
|
...dryRunPayload(settings, identity.binName, ASSET_API.batchGetAssetDownloadUrl, body),
|
||||||
|
},
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetDownloadResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.batchGetAssetDownloadUrl,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = data.items?.[0]?.downloadUrl;
|
||||||
|
if (!url) {
|
||||||
|
throw new BailianError(`No download URL available for ${assetId}.`, ExitCode.GENERAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.quiet) {
|
||||||
|
emitBare(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === "json") {
|
||||||
|
emitResult({ asset_id: assetId, download_url: url }, format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBare(`${padEnd("AssetId", 16)} ${assetId}`);
|
||||||
|
emitBare(`${padEnd("DownloadUrl", 16)} ${url}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||||
|
import type { AssetBatchResponse } from "./types.ts";
|
||||||
|
import {
|
||||||
|
ASSET_API,
|
||||||
|
ASSET_ID_FLAG,
|
||||||
|
callAssetApi,
|
||||||
|
dryRunPayload,
|
||||||
|
validateAssetIds,
|
||||||
|
} from "./utils.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center favorite` — 收藏一个或多个资产。
|
||||||
|
*
|
||||||
|
* 支持重复 --id(单次最多 100 个),调用 batchFavoriteAsset。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Add assets to favorites",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "--id <asset-id> [--id <asset-id>...]",
|
||||||
|
flags: ASSET_ID_FLAG,
|
||||||
|
exampleArgs: ["--id asset-001", "--id asset-001 --id asset-002"],
|
||||||
|
validate(flags) {
|
||||||
|
return validateAssetIds(flags.id);
|
||||||
|
},
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const assetIdList = flags.id;
|
||||||
|
const body = { assetIdList };
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.batchFavoriteAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetBatchResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.batchFavoriteAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.quiet || format === "text") {
|
||||||
|
emitBare(`Favorited ${data.affectedCount ?? assetIdList.length} asset(s).`);
|
||||||
|
} else {
|
||||||
|
emitResult({ affected_count: data.affectedCount ?? assetIdList.length }, format);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare, padEnd } from "bailian-cli-runtime";
|
||||||
|
import type { AssetGetResponse } from "./types.ts";
|
||||||
|
import { ASSET_API, callAssetApi, dryRunPayload, formatGenerateTime } from "./utils.ts";
|
||||||
|
|
||||||
|
const GET_FLAGS = {
|
||||||
|
assetId: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<id>",
|
||||||
|
description: "Asset ID to query",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
includeDownloadUrl: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Include signed download URL",
|
||||||
|
},
|
||||||
|
includeThumbnail: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Include thumbnail URL",
|
||||||
|
},
|
||||||
|
thumbnailWidth: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<px>",
|
||||||
|
description: "Thumbnail width in pixels",
|
||||||
|
},
|
||||||
|
thumbnailHeight: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<px>",
|
||||||
|
description: "Thumbnail height in pixels",
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center get` — 按 ID 查询单个资产详情。
|
||||||
|
*
|
||||||
|
* 可选 --include-download-url / --include-thumbnail 获取签名 URL。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Get full details of a model-generated asset",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "--asset-id <id> [flags]",
|
||||||
|
flags: GET_FLAGS,
|
||||||
|
exampleArgs: [
|
||||||
|
"--asset-id asset-001",
|
||||||
|
"--asset-id asset-001 --include-download-url --output json",
|
||||||
|
],
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const assetId = flags.assetId;
|
||||||
|
|
||||||
|
const body: Record<string, unknown> = { assetId };
|
||||||
|
if (flags.includeDownloadUrl) body.includeDownloadUrl = true;
|
||||||
|
if (flags.includeThumbnail) body.includeThumbnail = true;
|
||||||
|
if (flags.thumbnailWidth !== undefined) body.thumbnailWidth = flags.thumbnailWidth;
|
||||||
|
if (flags.thumbnailHeight !== undefined) body.thumbnailHeight = flags.thumbnailHeight;
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.getModelGeneratedAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetGetResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.getModelGeneratedAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const item = data.item;
|
||||||
|
if (!item) {
|
||||||
|
emitBare("Asset not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === "json") {
|
||||||
|
emitResult(item, format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBare(`${padEnd("AssetId", 16)} ${item.assetId ?? "-"}`);
|
||||||
|
emitBare(`${padEnd("Type", 16)} ${item.assetType ?? "-"}`);
|
||||||
|
emitBare(`${padEnd("Name", 16)} ${item.assetName ?? "-"}`);
|
||||||
|
emitBare(`${padEnd("Description", 16)} ${item.assetDescription ?? "-"}`);
|
||||||
|
emitBare(`${padEnd("Model", 16)} ${item.modelName ?? "-"}`);
|
||||||
|
emitBare(`${padEnd("Favorited", 16)} ${item.favorited ? "yes" : "no"}`);
|
||||||
|
emitBare(`${padEnd("Generated", 16)} ${formatGenerateTime(item.generateTime)}`);
|
||||||
|
if (item.downloadUrl) emitBare(`${padEnd("DownloadUrl", 16)} ${item.downloadUrl}`);
|
||||||
|
if (item.thumbnailUrl) emitBare(`${padEnd("ThumbnailUrl", 16)} ${item.thumbnailUrl}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare, formatTable } from "bailian-cli-runtime";
|
||||||
|
import type { AssetListResponse, ModelGeneratedAssetItem } from "./types.ts";
|
||||||
|
import {
|
||||||
|
ASSET_API,
|
||||||
|
ASSET_LIST_FILTER_FLAGS,
|
||||||
|
buildListFilterBody,
|
||||||
|
callAssetApi,
|
||||||
|
dryRunPayload,
|
||||||
|
formatGenerateTime,
|
||||||
|
} from "./utils.ts";
|
||||||
|
|
||||||
|
const LIST_FLAGS = {
|
||||||
|
...ASSET_LIST_FILTER_FLAGS,
|
||||||
|
includeDownloadUrl: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Include signed download URLs in the response",
|
||||||
|
},
|
||||||
|
includeThumbnail: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Include thumbnail URLs in the response",
|
||||||
|
},
|
||||||
|
thumbnailWidth: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<px>",
|
||||||
|
description: "Thumbnail width in pixels",
|
||||||
|
},
|
||||||
|
thumbnailHeight: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<px>",
|
||||||
|
description: "Thumbnail height in pixels",
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<n>",
|
||||||
|
description: "Results per page (default: 10, max: 100)",
|
||||||
|
},
|
||||||
|
nextToken: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<token>",
|
||||||
|
description: "Cursor for the next page",
|
||||||
|
},
|
||||||
|
preToken: {
|
||||||
|
type: "number",
|
||||||
|
valueHint: "<token>",
|
||||||
|
description: "Cursor for the previous page",
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
function normalizeItem(item: ModelGeneratedAssetItem) {
|
||||||
|
return {
|
||||||
|
asset_id: item.assetId ?? "",
|
||||||
|
asset_type: item.assetType ?? "",
|
||||||
|
asset_name: item.assetName ?? "",
|
||||||
|
model_name: item.modelName ?? "",
|
||||||
|
favorited: item.favorited ?? false,
|
||||||
|
generate_time: item.generateTime,
|
||||||
|
download_url: item.downloadUrl,
|
||||||
|
thumbnail_url: item.thumbnailUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center list` — 分页查询模型生成资产列表。
|
||||||
|
*
|
||||||
|
* 支持类型/模型/关键词/收藏/回收站/OSS 同步状态/时间范围筛选,以及
|
||||||
|
* --next-token / --pre-token 游标翻页;可选返回签名下载链接与缩略图 URL。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "List model-generated assets with filters and cursor pagination",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "[flags]",
|
||||||
|
flags: LIST_FLAGS,
|
||||||
|
exampleArgs: [
|
||||||
|
"",
|
||||||
|
"--type IMAGE --model qwen-image-3.0",
|
||||||
|
"--favorited --page-size 20",
|
||||||
|
"--recycle-bin",
|
||||||
|
"--keyword landscape --output json",
|
||||||
|
],
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const pageSize = flags.pageSize ?? 10;
|
||||||
|
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
...buildListFilterBody(flags),
|
||||||
|
pageSize,
|
||||||
|
};
|
||||||
|
if (flags.includeDownloadUrl) body.includeDownloadUrl = true;
|
||||||
|
if (flags.includeThumbnail) body.includeThumbnail = true;
|
||||||
|
if (flags.thumbnailWidth !== undefined) body.thumbnailWidth = flags.thumbnailWidth;
|
||||||
|
if (flags.thumbnailHeight !== undefined) body.thumbnailHeight = flags.thumbnailHeight;
|
||||||
|
if (flags.nextToken !== undefined) body.nextToken = flags.nextToken;
|
||||||
|
if (flags.preToken !== undefined) body.preToken = flags.preToken;
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.listModelGeneratedAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetListResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.listModelGeneratedAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = (data.dataList ?? []).map(normalizeItem);
|
||||||
|
|
||||||
|
if (format === "json") {
|
||||||
|
emitResult(
|
||||||
|
{
|
||||||
|
items,
|
||||||
|
pre_token: data.preToken,
|
||||||
|
next_token: data.nextToken,
|
||||||
|
has_next: data.hasNext,
|
||||||
|
has_pre: data.hasPre,
|
||||||
|
},
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
emitBare("No assets found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = ["ASSET_ID", "TYPE", "NAME", "MODEL", "FAVORITED", "GENERATED"];
|
||||||
|
const rows = items.map((item) => [
|
||||||
|
item.asset_id,
|
||||||
|
item.asset_type,
|
||||||
|
item.asset_name,
|
||||||
|
item.model_name,
|
||||||
|
item.favorited ? "yes" : "-",
|
||||||
|
formatGenerateTime(item.generate_time),
|
||||||
|
]);
|
||||||
|
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (data.hasPre) parts.push("has previous page");
|
||||||
|
if (data.hasNext) parts.push(`next token: ${data.nextToken}`);
|
||||||
|
if (parts.length > 0) emitBare(`\n${parts.join("; ")}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare, padEnd } from "bailian-cli-runtime";
|
||||||
|
import type { AssetCountResponse } from "./types.ts";
|
||||||
|
import {
|
||||||
|
ASSET_API,
|
||||||
|
ASSET_LIST_FILTER_FLAGS,
|
||||||
|
buildListFilterBody,
|
||||||
|
callAssetApi,
|
||||||
|
dryRunPayload,
|
||||||
|
} from "./utils.ts";
|
||||||
|
|
||||||
|
const STATS_FLAGS = {
|
||||||
|
...ASSET_LIST_FILTER_FLAGS,
|
||||||
|
syncFailed: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Also count assets with failed OSS sync",
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center stats` — 按类型统计模型生成资产数量。
|
||||||
|
*
|
||||||
|
* 复用 list 的筛选条件;--sync-failed 额外统计 OSS 同步失败的资产数。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Count model-generated assets by type",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "[flags]",
|
||||||
|
flags: STATS_FLAGS,
|
||||||
|
exampleArgs: ["", "--sync-failed", "--type IMAGE --output json"],
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const body = buildListFilterBody(flags);
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.countModelGeneratedAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetCountResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.countModelGeneratedAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
let syncFailedCount: number | undefined;
|
||||||
|
if (flags.syncFailed) {
|
||||||
|
const failed = await callAssetApi<AssetCountResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.countModelGeneratedAsset,
|
||||||
|
{ ...body, syncOssDataStatus: "SYNC_FAILED" },
|
||||||
|
);
|
||||||
|
syncFailedCount = failed.totalCount ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === "json") {
|
||||||
|
emitResult(
|
||||||
|
{
|
||||||
|
total_count: data.totalCount ?? 0,
|
||||||
|
image_count: data.imageCount ?? 0,
|
||||||
|
video_count: data.videoCount ?? 0,
|
||||||
|
audio_count: data.audioCount ?? 0,
|
||||||
|
...(syncFailedCount !== undefined ? { sync_failed_count: syncFailedCount } : {}),
|
||||||
|
},
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBare(`${padEnd("Total", 14)} ${data.totalCount ?? 0}`);
|
||||||
|
emitBare(`${padEnd("Image", 14)} ${data.imageCount ?? 0}`);
|
||||||
|
emitBare(`${padEnd("Video", 14)} ${data.videoCount ?? 0}`);
|
||||||
|
emitBare(`${padEnd("Audio", 14)} ${data.audioCount ?? 0}`);
|
||||||
|
if (syncFailedCount !== undefined) {
|
||||||
|
emitBare(`${padEnd("Sync failed", 14)} ${syncFailedCount}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare, padEnd } from "bailian-cli-runtime";
|
||||||
|
import type { AssetStorageQuotaResponse } from "./types.ts";
|
||||||
|
import { ASSET_API, callAssetApi, dryRunPayload, formatStorageBytes } from "./utils.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center storage` — 查看存储配额、已用容量与超额计费说明。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "View storage quota, usage, and overage pricing",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "[flags]",
|
||||||
|
exampleArgs: ["", "--output json"],
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(dryRunPayload(settings, identity.binName, ASSET_API.getStorageQuota, {}), format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetStorageQuotaResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.getStorageQuota,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (format === "json") {
|
||||||
|
emitResult(
|
||||||
|
{
|
||||||
|
used_storage_size: data.usedStorageSize,
|
||||||
|
free_storage_quota: data.freeStorageQuota,
|
||||||
|
extra_storage_price: data.extraStoragePrice,
|
||||||
|
},
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBare(`${padEnd("Used", 14)} ${formatStorageBytes(data.usedStorageSize)}`);
|
||||||
|
emitBare(`${padEnd("Free quota", 14)} ${formatStorageBytes(data.freeStorageQuota)}`);
|
||||||
|
emitBare(`${padEnd("Overage", 14)} ${data.extraStoragePrice ?? "-"}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
export type AssetType = "IMAGE" | "VIDEO" | "AUDIO";
|
||||||
|
|
||||||
|
export type AssetDeleteStatus = "NORMAL" | "SOFT_DELETED" | "PERMANENTLY_DELETED";
|
||||||
|
|
||||||
|
export type AssetSyncOssStatus = "NOT_SYNCED" | "IN_SYNCING" | "SYNC_SUCCESS" | "SYNC_FAILED";
|
||||||
|
|
||||||
|
export type AssetDeleteType = "SOFT_DELETE" | "PERMANENT_DELETE";
|
||||||
|
|
||||||
|
export interface AssetHttpBaseRequest {
|
||||||
|
workspace: string;
|
||||||
|
tenantId?: string;
|
||||||
|
mainAccountUid?: string;
|
||||||
|
apiSource?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelGeneratedAssetItem {
|
||||||
|
id?: number;
|
||||||
|
assetId?: string;
|
||||||
|
assetType?: string;
|
||||||
|
assetName?: string;
|
||||||
|
assetDescription?: string;
|
||||||
|
favorited?: boolean;
|
||||||
|
assetSize?: number;
|
||||||
|
modelType?: string;
|
||||||
|
modelName?: string;
|
||||||
|
deleteStatus?: string;
|
||||||
|
syncOssDataStatus?: string;
|
||||||
|
generateTime?: number;
|
||||||
|
downloadUrl?: string;
|
||||||
|
thumbnailUrl?: string;
|
||||||
|
gmtCreate?: string;
|
||||||
|
gmtModified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetListResponse {
|
||||||
|
dataList?: ModelGeneratedAssetItem[];
|
||||||
|
preToken?: number;
|
||||||
|
nextToken?: number;
|
||||||
|
hasNext?: boolean;
|
||||||
|
hasPre?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetGetResponse {
|
||||||
|
item?: ModelGeneratedAssetItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetBatchResponse {
|
||||||
|
success?: boolean;
|
||||||
|
affectedCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetDownloadUrlItem {
|
||||||
|
assetId?: string;
|
||||||
|
downloadUrl?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetDownloadResponse {
|
||||||
|
items?: AssetDownloadUrlItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetCountResponse {
|
||||||
|
imageCount?: number;
|
||||||
|
videoCount?: number;
|
||||||
|
audioCount?: number;
|
||||||
|
totalCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetStorageQuotaResponse {
|
||||||
|
freeStorageQuota?: number;
|
||||||
|
usedStorageSize?: number;
|
||||||
|
extraStoragePrice?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
|
||||||
|
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||||
|
import type { AssetBatchResponse } from "./types.ts";
|
||||||
|
import {
|
||||||
|
ASSET_API,
|
||||||
|
ASSET_ID_FLAG,
|
||||||
|
callAssetApi,
|
||||||
|
dryRunPayload,
|
||||||
|
validateAssetIds,
|
||||||
|
} from "./utils.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `bl asset-center unfavorite` — 取消收藏一个或多个资产。
|
||||||
|
*
|
||||||
|
* 支持重复 --id(单次最多 100 个),调用 batchUnfavoriteAsset。
|
||||||
|
*/
|
||||||
|
export default defineCommand({
|
||||||
|
description: "Remove assets from favorites",
|
||||||
|
auth: "console",
|
||||||
|
usageArgs: "--id <asset-id> [--id <asset-id>...]",
|
||||||
|
flags: ASSET_ID_FLAG,
|
||||||
|
exampleArgs: ["--id asset-001", "--id asset-001 --id asset-002"],
|
||||||
|
validate(flags) {
|
||||||
|
return validateAssetIds(flags.id);
|
||||||
|
},
|
||||||
|
async run(ctx) {
|
||||||
|
const { settings, identity, flags } = ctx;
|
||||||
|
const format = detectOutputFormat(settings.output);
|
||||||
|
const assetIdList = flags.id;
|
||||||
|
const body = { assetIdList };
|
||||||
|
|
||||||
|
if (settings.dryRun) {
|
||||||
|
emitResult(
|
||||||
|
dryRunPayload(settings, identity.binName, ASSET_API.batchUnfavoriteAsset, body),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await callAssetApi<AssetBatchResponse>(
|
||||||
|
ctx.client,
|
||||||
|
settings,
|
||||||
|
identity.binName,
|
||||||
|
ASSET_API.batchUnfavoriteAsset,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.quiet || format === "text") {
|
||||||
|
emitBare(`Unfavorited ${data.affectedCount ?? assetIdList.length} asset(s).`);
|
||||||
|
} else {
|
||||||
|
emitResult({ affected_count: data.affectedCount ?? assetIdList.length }, format);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import {
|
||||||
|
BailianError,
|
||||||
|
ExitCode,
|
||||||
|
effectiveConsoleGatewayConfig,
|
||||||
|
type Client,
|
||||||
|
type FlagsDef,
|
||||||
|
type ParsedFlags,
|
||||||
|
type Settings,
|
||||||
|
} from "bailian-cli-core";
|
||||||
|
import type { AssetHttpBaseRequest, AssetSyncOssStatus, AssetType } from "./types.ts";
|
||||||
|
|
||||||
|
const ASSET_SERVICE = "dashscopeModel";
|
||||||
|
const ASSET_BASE = "/zelda/api/v1/bailian/asset";
|
||||||
|
export const MAX_ASSET_BATCH_SIZE = 100;
|
||||||
|
|
||||||
|
export const ASSET_API = {
|
||||||
|
listModelGeneratedAsset: assetApi("listModelGeneratedAsset"),
|
||||||
|
getModelGeneratedAsset: assetApi("getModelGeneratedAsset"),
|
||||||
|
batchFavoriteAsset: assetApi("batchFavoriteAsset"),
|
||||||
|
batchUnfavoriteAsset: assetApi("batchUnfavoriteAsset"),
|
||||||
|
batchDeleteAsset: assetApi("batchDeleteAsset"),
|
||||||
|
batchGetAssetDownloadUrl: assetApi("batchGetAssetDownloadUrl"),
|
||||||
|
countModelGeneratedAsset: assetApi("countModelGeneratedAsset"),
|
||||||
|
getStorageQuota: assetApi("getStorageQuota"),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ASSET_ID_FLAG = {
|
||||||
|
id: {
|
||||||
|
type: "array",
|
||||||
|
valueHint: "<asset-id>",
|
||||||
|
description: "Asset ID(s) to operate on (repeatable, max 100)",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
export const ASSET_LIST_FILTER_FLAGS = {
|
||||||
|
type: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<type>",
|
||||||
|
description: "Asset type: IMAGE, VIDEO, or AUDIO",
|
||||||
|
choices: ["IMAGE", "VIDEO", "AUDIO"] as const,
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<name>",
|
||||||
|
description: "Filter by model name",
|
||||||
|
},
|
||||||
|
keyword: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<text>",
|
||||||
|
description: "Filter by asset name (substring match)",
|
||||||
|
},
|
||||||
|
favorited: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Show or count only favorited assets",
|
||||||
|
},
|
||||||
|
recycleBin: {
|
||||||
|
type: "switch",
|
||||||
|
description: "Show or count soft-deleted assets (recycle bin)",
|
||||||
|
},
|
||||||
|
syncStatus: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<status>",
|
||||||
|
description: "OSS sync status filter",
|
||||||
|
choices: ["NOT_SYNCED", "IN_SYNCING", "SYNC_SUCCESS", "SYNC_FAILED"] as const,
|
||||||
|
},
|
||||||
|
beginTime: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<datetime>",
|
||||||
|
description: "Filter by generate time start (ISO_LOCAL_DATE_TIME)",
|
||||||
|
},
|
||||||
|
endTime: {
|
||||||
|
type: "string",
|
||||||
|
valueHint: "<datetime>",
|
||||||
|
description: "Filter by generate time end (ISO_LOCAL_DATE_TIME)",
|
||||||
|
},
|
||||||
|
} satisfies FlagsDef;
|
||||||
|
|
||||||
|
type AssetListFilterFlags = ParsedFlags<typeof ASSET_LIST_FILTER_FLAGS>;
|
||||||
|
|
||||||
|
function assetApi(action: string): string {
|
||||||
|
return `zeldaHttp.${ASSET_SERVICE}.${ASSET_BASE}/${action}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNestedRecord(
|
||||||
|
obj: Record<string, unknown>,
|
||||||
|
key: string,
|
||||||
|
): Record<string, unknown> | undefined {
|
||||||
|
const val = obj[key];
|
||||||
|
if (val && typeof val === "object" && !Array.isArray(val)) return val as Record<string, unknown>;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractAssetResponse<T>(result: unknown): T {
|
||||||
|
const raw = result as Record<string, unknown>;
|
||||||
|
const data = getNestedRecord(raw, "data");
|
||||||
|
if (!data) {
|
||||||
|
throw new BailianError("Unexpected empty response from asset API.", ExitCode.GENERAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataV2 = getNestedRecord(data, "DataV2");
|
||||||
|
const payload = dataV2
|
||||||
|
? (getNestedRecord(getNestedRecord(dataV2, "data") ?? dataV2, "data") ??
|
||||||
|
getNestedRecord(dataV2, "data") ??
|
||||||
|
dataV2)
|
||||||
|
: (getNestedRecord(data, "data") ?? data);
|
||||||
|
|
||||||
|
if (payload.success === false) {
|
||||||
|
const message =
|
||||||
|
typeof payload.message === "string" && payload.message.length > 0
|
||||||
|
? payload.message
|
||||||
|
: typeof payload.code === "string"
|
||||||
|
? payload.code
|
||||||
|
: "Asset API request failed.";
|
||||||
|
throw new BailianError(message, ExitCode.GENERAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.data !== undefined) {
|
||||||
|
return payload.data as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requireWorkspaceId(settings: Settings, binName: string): string {
|
||||||
|
if (settings.workspaceId) return settings.workspaceId;
|
||||||
|
|
||||||
|
throw new BailianError(
|
||||||
|
`workspace-id is required. Set via --workspace-id, BAILIAN_WORKSPACE_ID, or \`${binName} config set workspace_id <id>\`.`,
|
||||||
|
ExitCode.GENERAL,
|
||||||
|
`Run \`${binName} workspace list\` to view available workspaces.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildBaseRequest(settings: Settings, binName: string): AssetHttpBaseRequest {
|
||||||
|
// workspace 由 CLI 注入;tenantId / mainAccountUid 由 Console 网关从登录 session 自动填充,
|
||||||
|
// CLI 侧无需也不应手动解析阿里云账号 ID。
|
||||||
|
return {
|
||||||
|
workspace: requireWorkspaceId(settings, binName),
|
||||||
|
apiSource: "CLI",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildListFilterBody(flags: AssetListFilterFlags): Record<string, unknown> {
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if (flags.type) body.assetType = flags.type as AssetType;
|
||||||
|
if (flags.model) body.modelName = flags.model;
|
||||||
|
if (flags.keyword) body.assetName = flags.keyword;
|
||||||
|
if (flags.favorited) body.favorited = true;
|
||||||
|
if (flags.recycleBin) {
|
||||||
|
body.deleteStatus = "SOFT_DELETED";
|
||||||
|
} else {
|
||||||
|
body.deleteStatus = "NORMAL";
|
||||||
|
}
|
||||||
|
if (flags.syncStatus) body.syncOssDataStatus = flags.syncStatus as AssetSyncOssStatus;
|
||||||
|
if (flags.beginTime) body.beginTime = flags.beginTime;
|
||||||
|
if (flags.endTime) body.endTime = flags.endTime;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateAssetIds(ids: string[] | undefined): string | undefined {
|
||||||
|
if (!ids || ids.length === 0) {
|
||||||
|
return "At least one --id is required.";
|
||||||
|
}
|
||||||
|
if (ids.length > MAX_ASSET_BATCH_SIZE) {
|
||||||
|
return `At most ${MAX_ASSET_BATCH_SIZE} asset IDs are allowed per request.`;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function callAssetApi<T>(
|
||||||
|
client: Client,
|
||||||
|
settings: Settings,
|
||||||
|
binName: string,
|
||||||
|
api: string,
|
||||||
|
body: Record<string, unknown>,
|
||||||
|
): Promise<T> {
|
||||||
|
const payload = { ...buildBaseRequest(settings, binName), ...body };
|
||||||
|
const raw = await client.console(api, payload);
|
||||||
|
return extractAssetResponse<T>(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dryRunPayload(
|
||||||
|
settings: Settings,
|
||||||
|
binName: string,
|
||||||
|
api: string,
|
||||||
|
body: Record<string, unknown>,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
api,
|
||||||
|
data: { ...buildBaseRequest(settings, binName), ...body },
|
||||||
|
...effectiveConsoleGatewayConfig(settings),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatGenerateTime(ts?: number): string {
|
||||||
|
if (ts == null) return "-";
|
||||||
|
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatStorageBytes(bytes?: number): string {
|
||||||
|
if (bytes == null) return "-";
|
||||||
|
if (bytes < 1024) return `${bytes} B`;
|
||||||
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||||
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||||
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||||
|
}
|
||||||
@@ -91,6 +91,14 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.
|
|||||||
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
|
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
|
||||||
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
|
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
|
||||||
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
|
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
|
||||||
|
export { default as assetList } from "./commands/asset-center/list.ts";
|
||||||
|
export { default as assetGet } from "./commands/asset-center/get.ts";
|
||||||
|
export { default as assetFavorite } from "./commands/asset-center/favorite.ts";
|
||||||
|
export { default as assetUnfavorite } from "./commands/asset-center/unfavorite.ts";
|
||||||
|
export { default as assetDelete } from "./commands/asset-center/delete.ts";
|
||||||
|
export { default as assetDownload } from "./commands/asset-center/download.ts";
|
||||||
|
export { default as assetStats } from "./commands/asset-center/stats.ts";
|
||||||
|
export { default as assetStorage } from "./commands/asset-center/storage.ts";
|
||||||
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
|
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
|
||||||
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
|
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
|
||||||
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
|
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
# `bl asset-center` commands
|
||||||
|
|
||||||
|
> Auto-generated from `packages/cli/src/commands.ts`. Do not edit by hand.
|
||||||
|
> Regenerate: `pnpm --filter bailian-cli run generate:reference`.
|
||||||
|
|
||||||
|
Index: [index.md](index.md)
|
||||||
|
|
||||||
|
## Commands in this group
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ---------------------------- | -------------------------------------------------------------- |
|
||||||
|
| `bl asset-center delete` | Delete assets (soft delete to recycle bin by default) |
|
||||||
|
| `bl asset-center download` | Get a signed download URL for an asset by ID |
|
||||||
|
| `bl asset-center favorite` | Add assets to favorites |
|
||||||
|
| `bl asset-center get` | Get full details of a model-generated asset |
|
||||||
|
| `bl asset-center list` | List model-generated assets with filters and cursor pagination |
|
||||||
|
| `bl asset-center stats` | Count model-generated assets by type |
|
||||||
|
| `bl asset-center storage` | View storage quota, usage, and overage pricing |
|
||||||
|
| `bl asset-center unfavorite` | Remove assets from favorites |
|
||||||
|
|
||||||
|
## Command details
|
||||||
|
|
||||||
|
### `bl asset-center delete`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | --------------------------------------------------------------------- |
|
||||||
|
| **Name** | `asset-center delete` |
|
||||||
|
| **Description** | Delete assets (soft delete to recycle bin by default) |
|
||||||
|
| **Usage** | `bl asset-center delete --id <asset-id> [--id <asset-id>...] [flags]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--id <asset-id>` | array | yes | Asset ID(s) to operate on (repeatable, max 100) |
|
||||||
|
| `--permanent` | switch | no | Permanently delete assets (cannot be restored) |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center delete --id asset-001
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center delete --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center delete --id asset-001 --permanent
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center download`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | -------------------------------------------- |
|
||||||
|
| **Name** | `asset-center download` |
|
||||||
|
| **Description** | Get a signed download URL for an asset by ID |
|
||||||
|
| **Usage** | `bl asset-center download --id <asset-id>` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--id <asset-id>` | string | yes | Asset ID to get download URL for |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center download --id asset-001
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center download --id asset-001 --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center download --id asset-001 --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center favorite`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | --------------------------------------------------------------- |
|
||||||
|
| **Name** | `asset-center favorite` |
|
||||||
|
| **Description** | Add assets to favorites |
|
||||||
|
| **Usage** | `bl asset-center favorite --id <asset-id> [--id <asset-id>...]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--id <asset-id>` | array | yes | Asset ID(s) to operate on (repeatable, max 100) |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center favorite --id asset-001
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center favorite --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center get`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | --------------------------------------------- |
|
||||||
|
| **Name** | `asset-center get` |
|
||||||
|
| **Description** | Get full details of a model-generated asset |
|
||||||
|
| **Usage** | `bl asset-center get --asset-id <id> [flags]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--asset-id <id>` | string | yes | Asset ID to query |
|
||||||
|
| `--include-download-url` | switch | no | Include signed download URL |
|
||||||
|
| `--include-thumbnail` | switch | no | Include thumbnail URL |
|
||||||
|
| `--thumbnail-width <px>` | number | no | Thumbnail width in pixels |
|
||||||
|
| `--thumbnail-height <px>` | number | no | Thumbnail height in pixels |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center get --asset-id asset-001
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center get --asset-id asset-001 --include-download-url --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center list`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | -------------------------------------------------------------- |
|
||||||
|
| **Name** | `asset-center list` |
|
||||||
|
| **Description** | List model-generated assets with filters and cursor pagination |
|
||||||
|
| **Usage** | `bl asset-center list [flags]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------------------------------------------- | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--type <IMAGE\|VIDEO\|AUDIO>` | string | no | Asset type: IMAGE, VIDEO, or AUDIO |
|
||||||
|
| `--model <name>` | string | no | Filter by model name |
|
||||||
|
| `--keyword <text>` | string | no | Filter by asset name (substring match) |
|
||||||
|
| `--favorited` | switch | no | Show or count only favorited assets |
|
||||||
|
| `--recycle-bin` | switch | no | Show or count soft-deleted assets (recycle bin) |
|
||||||
|
| `--sync-status <NOT_SYNCED\|IN_SYNCING\|SYNC_SUCCESS\|SYNC_FAILED>` | string | no | OSS sync status filter |
|
||||||
|
| `--begin-time <datetime>` | string | no | Filter by generate time start (ISO_LOCAL_DATE_TIME) |
|
||||||
|
| `--end-time <datetime>` | string | no | Filter by generate time end (ISO_LOCAL_DATE_TIME) |
|
||||||
|
| `--include-download-url` | switch | no | Include signed download URLs in the response |
|
||||||
|
| `--include-thumbnail` | switch | no | Include thumbnail URLs in the response |
|
||||||
|
| `--thumbnail-width <px>` | number | no | Thumbnail width in pixels |
|
||||||
|
| `--thumbnail-height <px>` | number | no | Thumbnail height in pixels |
|
||||||
|
| `--page-size <n>` | number | no | Results per page (default: 10, max: 100) |
|
||||||
|
| `--next-token <token>` | number | no | Cursor for the next page |
|
||||||
|
| `--pre-token <token>` | number | no | Cursor for the previous page |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center list
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center list --type IMAGE --model qwen-image-3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center list --favorited --page-size 20
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center list --recycle-bin
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center list --keyword landscape --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center stats`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | ------------------------------------ |
|
||||||
|
| **Name** | `asset-center stats` |
|
||||||
|
| **Description** | Count model-generated assets by type |
|
||||||
|
| **Usage** | `bl asset-center stats [flags]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------------------------------------------- | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--type <IMAGE\|VIDEO\|AUDIO>` | string | no | Asset type: IMAGE, VIDEO, or AUDIO |
|
||||||
|
| `--model <name>` | string | no | Filter by model name |
|
||||||
|
| `--keyword <text>` | string | no | Filter by asset name (substring match) |
|
||||||
|
| `--favorited` | switch | no | Show or count only favorited assets |
|
||||||
|
| `--recycle-bin` | switch | no | Show or count soft-deleted assets (recycle bin) |
|
||||||
|
| `--sync-status <NOT_SYNCED\|IN_SYNCING\|SYNC_SUCCESS\|SYNC_FAILED>` | string | no | OSS sync status filter |
|
||||||
|
| `--begin-time <datetime>` | string | no | Filter by generate time start (ISO_LOCAL_DATE_TIME) |
|
||||||
|
| `--end-time <datetime>` | string | no | Filter by generate time end (ISO_LOCAL_DATE_TIME) |
|
||||||
|
| `--sync-failed` | switch | no | Also count assets with failed OSS sync |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center stats
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center stats --sync-failed
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center stats --type IMAGE --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center storage`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | ---------------------------------------------- |
|
||||||
|
| **Name** | `asset-center storage` |
|
||||||
|
| **Description** | View storage quota, usage, and overage pricing |
|
||||||
|
| **Usage** | `bl asset-center storage [flags]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center storage
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center storage --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bl asset-center unfavorite`
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------------- |
|
||||||
|
| **Name** | `asset-center unfavorite` |
|
||||||
|
| **Description** | Remove assets from favorites |
|
||||||
|
| **Usage** | `bl asset-center unfavorite --id <asset-id> [--id <asset-id>...]` |
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
| Flag | Type | Required | Description |
|
||||||
|
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||||
|
| `--id <asset-id>` | array | yes | Asset ID(s) to operate on (repeatable, max 100) |
|
||||||
|
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||||
|
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||||
|
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||||
|
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center unfavorite --id asset-001
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bl asset-center unfavorite --id asset-001 --id asset-002
|
||||||
|
```
|
||||||
@@ -9,90 +9,99 @@ Use this index for the skill-scoped quick index and global flags.
|
|||||||
|
|
||||||
## Quick index
|
## Quick index
|
||||||
|
|
||||||
| Command | Description | Detail |
|
| Command | Description | Detail |
|
||||||
| ------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------ |
|
| ------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------- |
|
||||||
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
||||||
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
||||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||||
| `bl auth generate-access-token` | Generate a CLI access token using OpenAPI AK/SK | [auth.md](auth.md) |
|
| `bl asset-center delete` | Delete assets (soft delete to recycle bin by default) | [asset-center.md](asset-center.md) |
|
||||||
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
|
| `bl asset-center download` | Get a signed download URL for an asset by ID | [asset-center.md](asset-center.md) |
|
||||||
| `bl auth logout` | Clear stored credentials; full logout also clears the model Base URL | [auth.md](auth.md) |
|
| `bl asset-center favorite` | Add assets to favorites | [asset-center.md](asset-center.md) |
|
||||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
| `bl asset-center get` | Get full details of a model-generated asset | [asset-center.md](asset-center.md) |
|
||||||
| `bl config agent` | Configure a coding agent to use DashScope API | [config.md](config.md) |
|
| `bl asset-center list` | List model-generated assets with filters and cursor pagination | [asset-center.md](asset-center.md) |
|
||||||
| `bl config list` | List config profiles and show the active profile | [config.md](config.md) |
|
| `bl asset-center stats` | Count model-generated assets by type | [asset-center.md](asset-center.md) |
|
||||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
| `bl asset-center storage` | View storage quota, usage, and overage pricing | [asset-center.md](asset-center.md) |
|
||||||
| `bl config show` | Display current configuration | [config.md](config.md) |
|
| `bl asset-center unfavorite` | Remove assets from favorites | [asset-center.md](asset-center.md) |
|
||||||
| `bl config ui` | Open a local web UI to manage config profiles | [config.md](config.md) |
|
| `bl auth generate-access-token` | Generate a CLI access token using OpenAPI AK/SK | [auth.md](auth.md) |
|
||||||
| `bl config use` | Set the active config profile | [config.md](config.md) |
|
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
|
||||||
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
| `bl auth logout` | Clear stored credentials; full logout also clears the model Base URL | [auth.md](auth.md) |
|
||||||
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||||
| `bl knowledge chat` | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) |
|
| `bl config agent` | Configure a coding agent to use DashScope API | [config.md](config.md) |
|
||||||
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) |
|
| `bl config list` | List config profiles and show the active profile | [config.md](config.md) |
|
||||||
| `bl knowledge search` | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) |
|
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||||
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
| `bl config show` | Display current configuration | [config.md](config.md) |
|
||||||
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
| `bl config ui` | Open a local web UI to manage config profiles | [config.md](config.md) |
|
||||||
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
| `bl config use` | Set the active config profile | [config.md](config.md) |
|
||||||
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
||||||
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
||||||
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
| `bl knowledge chat` | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) |
|
||||||
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) |
|
||||||
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
| `bl knowledge search` | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) |
|
||||||
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
||||||
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
||||||
| `bl model list` | Browse model families or show detailed model info in the Bailian model marketplace | [model.md](model.md) |
|
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
||||||
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
||||||
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
||||||
| `bl plugin install` | Install or upgrade an allowlisted Command Pack | [plugin.md](plugin.md) |
|
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
||||||
| `bl plugin link` | Link an allowlisted local Command Pack for development | [plugin.md](plugin.md) |
|
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
||||||
| `bl plugin list` | List installed Command Packs and their load status | [plugin.md](plugin.md) |
|
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
||||||
| `bl plugin remove` | Remove an installed Command Pack | [plugin.md](plugin.md) |
|
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
||||||
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
||||||
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
| `bl model list` | Browse model families or show detailed model info in the Bailian model marketplace | [model.md](model.md) |
|
||||||
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
||||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
||||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
| `bl plugin install` | Install or upgrade an allowlisted Command Pack | [plugin.md](plugin.md) |
|
||||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
|
| `bl plugin link` | Link an allowlisted local Command Pack for development | [plugin.md](plugin.md) |
|
||||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
|
| `bl plugin list` | List installed Command Packs and their load status | [plugin.md](plugin.md) |
|
||||||
| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) |
|
| `bl plugin remove` | Remove an installed Command Pack | [plugin.md](plugin.md) |
|
||||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) |
|
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
||||||
| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) |
|
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
||||||
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
||||||
| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||||
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||||
| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
|
||||||
| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
|
||||||
| `bl update` | Update the CLI to the latest or a specified version | [update.md](update.md) |
|
| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) |
|
||||||
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) |
|
||||||
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) |
|
||||||
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
||||||
| `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | [usage.md](usage.md) |
|
| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
||||||
| `bl workspace init` | Initialize Bailian workspace and activate postpaid services | [workspace.md](workspace.md) |
|
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
||||||
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
||||||
|
| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
||||||
|
| `bl update` | Update the CLI to the latest or a specified version | [update.md](update.md) |
|
||||||
|
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
||||||
|
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
||||||
|
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
||||||
|
| `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | [usage.md](usage.md) |
|
||||||
|
| `bl workspace init` | Initialize Bailian workspace and activate postpaid services | [workspace.md](workspace.md) |
|
||||||
|
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
||||||
|
|
||||||
## By group
|
## By group
|
||||||
|
|
||||||
| Group | Commands | Reference |
|
| Group | Commands | Reference |
|
||||||
| ------------ | ---------------------------------------------------------------------------- | ------------------------------ |
|
| -------------- | --------------------------------------------------------------------------------- | ---------------------------------- |
|
||||||
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
||||||
| `app` | `call`, `list` | [app.md](app.md) |
|
| `app` | `call`, `list` | [app.md](app.md) |
|
||||||
| `auth` | `generate-access-token`, `login`, `logout`, `status` | [auth.md](auth.md) |
|
| `asset-center` | `delete`, `download`, `favorite`, `get`, `list`, `stats`, `storage`, `unfavorite` | [asset-center.md](asset-center.md) |
|
||||||
| `config` | `agent`, `list`, `set`, `show`, `ui`, `use` | [config.md](config.md) |
|
| `auth` | `generate-access-token`, `login`, `logout`, `status` | [auth.md](auth.md) |
|
||||||
| `console` | `call` | [console.md](console.md) |
|
| `config` | `agent`, `list`, `set`, `show`, `ui`, `use` | [config.md](config.md) |
|
||||||
| `file` | `upload` | [file.md](file.md) |
|
| `console` | `call` | [console.md](console.md) |
|
||||||
| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) |
|
| `file` | `upload` | [file.md](file.md) |
|
||||||
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) |
|
||||||
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
||||||
| `model` | `list` | [model.md](model.md) |
|
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
||||||
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
| `model` | `list` | [model.md](model.md) |
|
||||||
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
|
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
||||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
|
||||||
| `search` | `web` | [search.md](search.md) |
|
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||||
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
| `search` | `web` | [search.md](search.md) |
|
||||||
| `text` | `chat` | [text.md](text.md) |
|
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
| `text` | `chat` | [text.md](text.md) |
|
||||||
| `update` | `(root)` | [update.md](update.md) |
|
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||||
| `usage` | `free`, `freetier`, `stats`, `summary` | [usage.md](usage.md) |
|
| `update` | `(root)` | [update.md](update.md) |
|
||||||
| `workspace` | `init`, `list` | [workspace.md](workspace.md) |
|
| `usage` | `free`, `freetier`, `stats`, `summary` | [usage.md](usage.md) |
|
||||||
|
| `workspace` | `init`, `list` | [workspace.md](workspace.md) |
|
||||||
|
|
||||||
## Global flags
|
## Global flags
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,523 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Asset-center Phase 2: reversible write ops + advanced read scenarios.
|
||||||
|
*/
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { existsSync, unlinkSync, statSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { writeFileSync } from "node:fs";
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const cliDir = join(root, "packages/cli");
|
||||||
|
const mainTs = join(cliDir, "src/main.ts");
|
||||||
|
const workspaceId = process.env.BAILIAN_WORKSPACE_ID ?? "llm-0xvms4kqhbqjlg8s";
|
||||||
|
|
||||||
|
function runBl(args, { timeout = 180_000 } = {}) {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const stdout = execFileSync("pnpm", ["exec", "tsx", mainTs, ...args], {
|
||||||
|
cwd: cliDir,
|
||||||
|
encoding: "utf8",
|
||||||
|
timeout,
|
||||||
|
env: { ...process.env, BAILIAN_WORKSPACE_ID: workspaceId },
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
return { exitCode: 0, stdout: stdout.trim(), stderr: "", durationMs: Date.now() - start };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
exitCode: error.status ?? 1,
|
||||||
|
stdout: (error.stdout ?? "").trim(),
|
||||||
|
stderr: (error.stderr ?? "").trim(),
|
||||||
|
durationMs: Date.now() - start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJson(text) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(exitCode, expected = 0) {
|
||||||
|
return exitCode === expected ? "✅ PASS" : "❌ FAIL";
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickUnfavoritedImage(listJson) {
|
||||||
|
return listJson?.items?.find((item) => item.asset_type === "IMAGE" && item.favorited === false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
function record(category, command, mode, result, expect, summary) {
|
||||||
|
const pass = result.exitCode === expect;
|
||||||
|
results.push({ category, command, mode, ...result, expect, pass, summary });
|
||||||
|
console.log(` ${pass ? "✅" : "❌"} ${command} — ${summary}`);
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n🔍 Asset Center Phase 2 — workspace: ${workspaceId}\n`);
|
||||||
|
|
||||||
|
// --- Setup: find test assets ---
|
||||||
|
const imageList = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--type",
|
||||||
|
"IMAGE",
|
||||||
|
"--page-size",
|
||||||
|
"10",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const imageJson = parseJson(imageList.stdout);
|
||||||
|
const testImage = pickUnfavoritedImage(imageJson);
|
||||||
|
const testImageId = testImage?.asset_id ?? imageJson?.items?.[0]?.asset_id;
|
||||||
|
|
||||||
|
const videoList = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--type",
|
||||||
|
"VIDEO",
|
||||||
|
"--page-size",
|
||||||
|
"1",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const testVideoId = parseJson(videoList.stdout)?.items?.[0]?.asset_id;
|
||||||
|
|
||||||
|
console.log(`📦 测试 IMAGE: ${testImageId}`);
|
||||||
|
console.log(`📦 测试 VIDEO: ${testVideoId}\n`);
|
||||||
|
|
||||||
|
// 1. Download IMAGE to /tmp
|
||||||
|
const downloadPath = `/tmp/asset-center-test-${testImageId}.png`;
|
||||||
|
if (existsSync(downloadPath)) unlinkSync(downloadPath);
|
||||||
|
|
||||||
|
const dlResult = runBl(
|
||||||
|
[
|
||||||
|
"asset-center",
|
||||||
|
"download",
|
||||||
|
"--id",
|
||||||
|
testImageId,
|
||||||
|
"--out",
|
||||||
|
downloadPath,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
{ timeout: 300_000 },
|
||||||
|
);
|
||||||
|
|
||||||
|
let dlSummary = "download failed";
|
||||||
|
if (dlResult.exitCode === 0) {
|
||||||
|
const dlJson = parseJson(dlResult.stdout);
|
||||||
|
const fileExists = existsSync(downloadPath);
|
||||||
|
const fileSize = fileExists ? statSync(downloadPath).size : 0;
|
||||||
|
dlSummary = fileExists
|
||||||
|
? `saved ${downloadPath} (${fileSize} bytes, reported ${dlJson?.size ?? "?"})`
|
||||||
|
: `exit 0 but file missing`;
|
||||||
|
if (fileExists) unlinkSync(downloadPath);
|
||||||
|
}
|
||||||
|
record("下载", "asset-center download (IMAGE)", "真实调用", dlResult, 0, dlSummary);
|
||||||
|
|
||||||
|
// 2. Get with --include-download-url
|
||||||
|
const getUrlResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
testImageId,
|
||||||
|
"--include-download-url",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const getUrlJson = parseJson(getUrlResult.stdout);
|
||||||
|
const hasDownloadUrl = Boolean(getUrlJson?.download_url ?? getUrlJson?.downloadUrl);
|
||||||
|
record(
|
||||||
|
"查询",
|
||||||
|
"asset-center get --include-download-url",
|
||||||
|
"真实调用",
|
||||||
|
getUrlResult,
|
||||||
|
0,
|
||||||
|
hasDownloadUrl ? "download_url present" : `no download_url in response`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. List with --include-download-url
|
||||||
|
const listUrlResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--type",
|
||||||
|
"IMAGE",
|
||||||
|
"--page-size",
|
||||||
|
"2",
|
||||||
|
"--include-download-url",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const listUrlJson = parseJson(listUrlResult.stdout);
|
||||||
|
const listHasUrl = listUrlJson?.items?.some((item) => item.download_url);
|
||||||
|
record(
|
||||||
|
"查询",
|
||||||
|
"asset-center list --include-download-url",
|
||||||
|
"真实调用",
|
||||||
|
listUrlResult,
|
||||||
|
0,
|
||||||
|
listHasUrl ? "items contain download_url" : "no download_url in items",
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Pagination: page 1 → next-token → page 2
|
||||||
|
const page1 = parseJson(
|
||||||
|
runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"3",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]).stdout,
|
||||||
|
);
|
||||||
|
const nextToken = page1?.next_token;
|
||||||
|
let pageSummary = "no next_token (single page)";
|
||||||
|
if (nextToken != null) {
|
||||||
|
const page2Result = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"3",
|
||||||
|
"--next-token",
|
||||||
|
String(nextToken),
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const page2 = parseJson(page2Result.stdout);
|
||||||
|
const ids1 = new Set(page1.items.map((item) => item.asset_id));
|
||||||
|
const overlap = page2?.items?.filter((item) => ids1.has(item.asset_id)).length ?? 0;
|
||||||
|
pageSummary =
|
||||||
|
page2Result.exitCode === 0
|
||||||
|
? `page2=${page2?.items?.length ?? 0} items, overlap=${overlap}, has_pre=${page2?.has_pre}`
|
||||||
|
: `page2 failed exit ${page2Result.exitCode}`;
|
||||||
|
record("查询", "asset-center list --next-token", "真实调用", page2Result, 0, pageSummary);
|
||||||
|
} else {
|
||||||
|
record(
|
||||||
|
"查询",
|
||||||
|
"asset-center list --next-token",
|
||||||
|
"跳过",
|
||||||
|
{ exitCode: 0, durationMs: 0, stdout: "", stderr: "" },
|
||||||
|
0,
|
||||||
|
pageSummary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Stats filters
|
||||||
|
const statsImage = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"stats",
|
||||||
|
"--type",
|
||||||
|
"IMAGE",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const statsImageJson = parseJson(statsImage.stdout);
|
||||||
|
record(
|
||||||
|
"统计",
|
||||||
|
"asset-center stats --type IMAGE",
|
||||||
|
"真实调用",
|
||||||
|
statsImage,
|
||||||
|
0,
|
||||||
|
`image=${statsImageJson?.image_count ?? "?"}, total=${statsImageJson?.total_count ?? "?"}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const statsSyncFailed = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"stats",
|
||||||
|
"--sync-failed",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const statsSyncJson = parseJson(statsSyncFailed.stdout);
|
||||||
|
record(
|
||||||
|
"统计",
|
||||||
|
"asset-center stats --sync-failed",
|
||||||
|
"真实调用",
|
||||||
|
statsSyncFailed,
|
||||||
|
0,
|
||||||
|
`total=${statsSyncJson?.total_count ?? "?"}, sync_failed=${statsSyncJson?.sync_failed_count ?? "?"}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Recycle bin (read-only)
|
||||||
|
const recycleResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--recycle-bin",
|
||||||
|
"--page-size",
|
||||||
|
"5",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const recycleJson = parseJson(recycleResult.stdout);
|
||||||
|
record(
|
||||||
|
"查询",
|
||||||
|
"asset-center list --recycle-bin",
|
||||||
|
"真实调用",
|
||||||
|
recycleResult,
|
||||||
|
0,
|
||||||
|
`${recycleJson?.items?.length ?? 0} soft-deleted item(s)`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Text output format
|
||||||
|
const textResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"2",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const textHasTable = textResult.stdout.includes("ASSET_ID") || textResult.stdout.includes("asset_");
|
||||||
|
record(
|
||||||
|
"输出",
|
||||||
|
"asset-center list (text)",
|
||||||
|
"真实调用",
|
||||||
|
textResult,
|
||||||
|
0,
|
||||||
|
textHasTable
|
||||||
|
? `${textResult.stdout.split("\n").filter(Boolean).length} lines table output`
|
||||||
|
: "unexpected text format",
|
||||||
|
);
|
||||||
|
|
||||||
|
// 8. Favorite → verify → unfavorite → verify (round-trip)
|
||||||
|
if (testImageId) {
|
||||||
|
const favResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"favorite",
|
||||||
|
"--id",
|
||||||
|
testImageId,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const favJson = parseJson(favResult.stdout);
|
||||||
|
record(
|
||||||
|
"收藏",
|
||||||
|
"asset-center favorite (真实)",
|
||||||
|
"真实调用",
|
||||||
|
favResult,
|
||||||
|
0,
|
||||||
|
`affected=${favJson?.affected_count ?? "?"}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const verifyFav = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
testImageId,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const verifyFavJson = parseJson(verifyFav.stdout);
|
||||||
|
const isFavorited = verifyFavJson?.favorited === true;
|
||||||
|
record(
|
||||||
|
"收藏",
|
||||||
|
"get 验证 favorited=true",
|
||||||
|
"真实调用",
|
||||||
|
verifyFav,
|
||||||
|
0,
|
||||||
|
isFavorited ? "favorited=true ✓" : `favorited=${verifyFavJson?.favorited}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const favListResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--favorited",
|
||||||
|
"--page-size",
|
||||||
|
"20",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const favListJson = parseJson(favListResult.stdout);
|
||||||
|
const inFavList = favListJson?.items?.some((item) => item.asset_id === testImageId);
|
||||||
|
record(
|
||||||
|
"查询",
|
||||||
|
"list --favorited 含测试资产",
|
||||||
|
"真实调用",
|
||||||
|
favListResult,
|
||||||
|
0,
|
||||||
|
inFavList ? "found in favorited list" : "NOT in favorited list",
|
||||||
|
);
|
||||||
|
|
||||||
|
const unfavResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"unfavorite",
|
||||||
|
"--id",
|
||||||
|
testImageId,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const unfavJson = parseJson(unfavResult.stdout);
|
||||||
|
record(
|
||||||
|
"收藏",
|
||||||
|
"asset-center unfavorite (真实)",
|
||||||
|
"真实调用",
|
||||||
|
unfavResult,
|
||||||
|
0,
|
||||||
|
`affected=${unfavJson?.affected_count ?? "?"}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const verifyUnfav = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
testImageId,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const verifyUnfavJson = parseJson(verifyUnfav.stdout);
|
||||||
|
const isUnfavorited = verifyUnfavJson?.favorited === false;
|
||||||
|
record(
|
||||||
|
"收藏",
|
||||||
|
"get 验证 favorited=false (恢复)",
|
||||||
|
"真实调用",
|
||||||
|
verifyUnfav,
|
||||||
|
0,
|
||||||
|
isUnfavorited ? "favorited=false ✓" : `favorited=${verifyUnfavJson?.favorited}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Batch favorite (2 ids) round-trip
|
||||||
|
const twoImages = imageJson?.items?.filter((item) => !item.favorited).slice(0, 2) ?? [];
|
||||||
|
if (twoImages.length === 2) {
|
||||||
|
const [first, second] = twoImages;
|
||||||
|
const batchFav = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"favorite",
|
||||||
|
"--id",
|
||||||
|
first.asset_id,
|
||||||
|
"--id",
|
||||||
|
second.asset_id,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
record("收藏", "favorite 批量 (--id x2)", "真实调用", batchFav, 0, summarizeBatch(batchFav));
|
||||||
|
const batchUnfav = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"unfavorite",
|
||||||
|
"--id",
|
||||||
|
first.asset_id,
|
||||||
|
"--id",
|
||||||
|
second.asset_id,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
record(
|
||||||
|
"收藏",
|
||||||
|
"unfavorite 批量 (--id x2)",
|
||||||
|
"真实调用",
|
||||||
|
batchUnfav,
|
||||||
|
0,
|
||||||
|
summarizeBatch(batchUnfav),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Get non-existent asset — server error passthrough (exit 1)
|
||||||
|
const badGet = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
"asset_nonexistent_00000000",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const badGetErr = parseJson(badGet.stderr);
|
||||||
|
record(
|
||||||
|
"边界",
|
||||||
|
"get 不存在的 asset-id",
|
||||||
|
"真实调用",
|
||||||
|
badGet,
|
||||||
|
1,
|
||||||
|
badGetErr?.error?.message === "资产不存在"
|
||||||
|
? 'exit 1, 服务端错误原样透传: "资产不存在"'
|
||||||
|
: `exit ${badGet.exitCode}, msg=${badGetErr?.error?.message ?? badGet.stderr.slice(0, 60)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
function summarizeBatch(result) {
|
||||||
|
const json = parseJson(result.stdout);
|
||||||
|
return json ? `affected=${json.affected_count ?? "?"}` : result.stdout.slice(0, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Report ---
|
||||||
|
const passed = results.filter((row) => row.pass).length;
|
||||||
|
const failed = results.filter((row) => !row.pass).length;
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
`# Asset Center 命令测试报告 — Phase 2`,
|
||||||
|
``,
|
||||||
|
`- **测试时间**: ${new Date().toISOString().replace("T", " ").slice(0, 19)} (UTC)`,
|
||||||
|
`- **Workspace**: \`${workspaceId}\``,
|
||||||
|
`- **测试 IMAGE**: \`${testImageId ?? "n/a"}\``,
|
||||||
|
`- **测试 VIDEO**: \`${testVideoId ?? "n/a"}\``,
|
||||||
|
`- **策略**: 可逆写操作(favorite/unfavorite 往返);download 到 /tmp 后删除;其余只读`,
|
||||||
|
`- **汇总**: ${passed} 通过 / ${failed} 失败 / ${results.length} 总计`,
|
||||||
|
``,
|
||||||
|
`> Phase 1 报告见同目录 [TEST-REPORT.md](./TEST-REPORT.md)(24 项 dry-run + 只读基础验证)`,
|
||||||
|
``,
|
||||||
|
`## Phase 2 测试结果`,
|
||||||
|
``,
|
||||||
|
`| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |`,
|
||||||
|
`|---|------|------|------|------|------|------|----------|`,
|
||||||
|
];
|
||||||
|
|
||||||
|
results.forEach((row, index) => {
|
||||||
|
lines.push(
|
||||||
|
`| ${index + 1} | ${row.category} | \`${row.command}\` | ${row.mode} | ${status(row.exitCode, row.expect)} | ${row.exitCode} | ${row.durationMs}ms | ${(row.summary ?? "").replace(/\|/g, "\\|")} |`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
lines.push(``, `## 失败详情`, ``);
|
||||||
|
for (const row of results.filter((row) => !row.pass)) {
|
||||||
|
lines.push(`### ${row.command}`, ``);
|
||||||
|
lines.push(`**stderr:**`, `\`\`\``, row.stderr || "(empty)", `\`\`\``, ``);
|
||||||
|
lines.push(`**stdout:**`, `\`\`\``, row.stdout?.slice(0, 800) || "(empty)", `\`\`\``, ``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportPath = join(root, "packages/commands/src/commands/asset-center/TEST-REPORT-PHASE2.md");
|
||||||
|
writeFileSync(reportPath, lines.join("\n"));
|
||||||
|
console.log(`\n📄 Report: ${reportPath}`);
|
||||||
|
console.log(`✅ ${passed} passed, ❌ ${failed} failed\n`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Asset-center command integration test runner.
|
||||||
|
* Read-only commands: real API. Write commands: --dry-run only.
|
||||||
|
*/
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const cliDir = join(root, "packages/cli");
|
||||||
|
const mainTs = join(cliDir, "src/main.ts");
|
||||||
|
const workspaceId = process.env.BAILIAN_WORKSPACE_ID ?? "llm-0xvms4kqhbqjlg8s";
|
||||||
|
|
||||||
|
function runBl(args, { timeout = 120_000 } = {}) {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const stdout = execFileSync("pnpm", ["exec", "tsx", mainTs, ...args], {
|
||||||
|
cwd: cliDir,
|
||||||
|
encoding: "utf8",
|
||||||
|
timeout,
|
||||||
|
env: { ...process.env, BAILIAN_WORKSPACE_ID: workspaceId },
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: stdout.trim(),
|
||||||
|
stderr: "",
|
||||||
|
durationMs: Date.now() - start,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
exitCode: error.status ?? 1,
|
||||||
|
stdout: (error.stdout ?? "").trim(),
|
||||||
|
stderr: (error.stderr ?? "").trim(),
|
||||||
|
durationMs: Date.now() - start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJson(text) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeOutput(result) {
|
||||||
|
const json = parseJson(result.stdout);
|
||||||
|
if (json) {
|
||||||
|
if (json.api) return `dry-run → ${json.api.split(".").pop()}`;
|
||||||
|
if (json.items?.length !== undefined)
|
||||||
|
return `${json.items.length} item(s)${json.next_token != null ? `, next=${json.next_token}` : ""}`;
|
||||||
|
if (json.item) return `asset=${json.item.asset_id ?? json.item.assetId ?? "?"}`;
|
||||||
|
if (json.total_count != null) return `total=${json.total_count}`;
|
||||||
|
if (json.used_bytes != null)
|
||||||
|
return `used=${json.used_bytes} / quota=${json.quota_bytes ?? "?"}`;
|
||||||
|
if (json.policy_id ?? json.policyId) return `policy=${json.policy_id ?? json.policyId}`;
|
||||||
|
if (json.authorized != null) return `authorized=${json.authorized}`;
|
||||||
|
if (json.saved) return `saved=${json.saved}`;
|
||||||
|
const keys = Object.keys(json).slice(0, 4).join(", ");
|
||||||
|
return keys ? `{${keys}}` : "json ok";
|
||||||
|
}
|
||||||
|
const lines = result.stdout.split("\n").filter(Boolean);
|
||||||
|
if (lines.length === 0 && result.stderr) {
|
||||||
|
const errLine = result.stderr
|
||||||
|
.split("\n")
|
||||||
|
.find((line) => line.includes("Error") || line.includes("error"));
|
||||||
|
return errLine?.slice(0, 80) ?? result.stderr.slice(0, 80);
|
||||||
|
}
|
||||||
|
if (lines.length <= 3) return lines.join(" | ").slice(0, 100);
|
||||||
|
return `${lines.length} lines, first: ${lines[0].slice(0, 60)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(exitCode, expected = 0) {
|
||||||
|
if (exitCode === expected) return "✅ PASS";
|
||||||
|
return "❌ FAIL";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch sample asset ID
|
||||||
|
const listResult = runBl([
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"1",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
]);
|
||||||
|
const listJson = parseJson(listResult.stdout);
|
||||||
|
const sampleAssetId = listJson?.items?.[0]?.asset_id ?? "asset_df026105d2274ff9b8c824058fa23d60";
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
// --- Read-only (real API) ---
|
||||||
|
{
|
||||||
|
category: "查询",
|
||||||
|
command: "asset-center list",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"3",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "查询",
|
||||||
|
command: "asset-center list --type IMAGE",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"list",
|
||||||
|
"--type",
|
||||||
|
"IMAGE",
|
||||||
|
"--page-size",
|
||||||
|
"2",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "查询",
|
||||||
|
command: "asset-center get",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "统计",
|
||||||
|
command: "asset-center stats",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: ["asset-center", "stats", "--output", "json", "--workspace-id", workspaceId],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "统计",
|
||||||
|
command: "asset-center storage",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: ["asset-center", "storage", "--output", "json", "--workspace-id", workspaceId],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "转存",
|
||||||
|
command: "asset-center transfer list",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"transfer",
|
||||||
|
"list",
|
||||||
|
"--page-size",
|
||||||
|
"5",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "OSS",
|
||||||
|
command: "asset-center oss show",
|
||||||
|
mode: "真实调用",
|
||||||
|
args: ["asset-center", "oss", "show", "--output", "json", "--workspace-id", workspaceId],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- Dry-run (write / side-effect) ---
|
||||||
|
{
|
||||||
|
category: "查询",
|
||||||
|
command: "asset-center list --dry-run",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: ["asset-center", "list", "--dry-run", "--output", "json", "--workspace-id", workspaceId],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "查询",
|
||||||
|
command: "asset-center get --dry-run",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"get",
|
||||||
|
"--asset-id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "收藏",
|
||||||
|
command: "asset-center favorite",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"favorite",
|
||||||
|
"--id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "收藏",
|
||||||
|
command: "asset-center unfavorite",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"unfavorite",
|
||||||
|
"--id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "删除",
|
||||||
|
command: "asset-center delete",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"delete",
|
||||||
|
"--id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "下载",
|
||||||
|
command: "asset-center download",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"download",
|
||||||
|
"--id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--out",
|
||||||
|
"/tmp/asset-test-download.mp4",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "统计",
|
||||||
|
command: "asset-center stats --dry-run",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: ["asset-center", "stats", "--dry-run", "--output", "json", "--workspace-id", workspaceId],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "统计",
|
||||||
|
command: "asset-center storage --dry-run",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"storage",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "OSS",
|
||||||
|
command: "asset-center oss slr authorize",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"oss",
|
||||||
|
"slr",
|
||||||
|
"authorize",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "OSS",
|
||||||
|
command: "asset-center oss bind",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"oss",
|
||||||
|
"bind",
|
||||||
|
"--bucket",
|
||||||
|
"test-bucket",
|
||||||
|
"--region",
|
||||||
|
"cn-hangzhou",
|
||||||
|
"--path-prefix",
|
||||||
|
"assets/",
|
||||||
|
"--policy",
|
||||||
|
"ALL",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "OSS",
|
||||||
|
command: "asset-center oss update",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"oss",
|
||||||
|
"update",
|
||||||
|
"--policy-id",
|
||||||
|
"policy-test-001",
|
||||||
|
"--bucket",
|
||||||
|
"test-bucket",
|
||||||
|
"--region",
|
||||||
|
"cn-hangzhou",
|
||||||
|
"--path-prefix",
|
||||||
|
"assets/",
|
||||||
|
"--policy",
|
||||||
|
"BEFORE_DAYS",
|
||||||
|
"--before-days",
|
||||||
|
"30",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "OSS",
|
||||||
|
command: "asset-center oss unbind",
|
||||||
|
mode: "dry-run",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"oss",
|
||||||
|
"unbind",
|
||||||
|
"--policy-id",
|
||||||
|
"policy-test-001",
|
||||||
|
"--dry-run",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- Validation (expect usage error) ---
|
||||||
|
{
|
||||||
|
category: "校验",
|
||||||
|
command: "asset-center get (缺 asset-id)",
|
||||||
|
mode: "参数校验",
|
||||||
|
args: ["asset-center", "get", "--quiet", "--workspace-id", workspaceId],
|
||||||
|
expect: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "校验",
|
||||||
|
command: "asset-center favorite (缺 --id)",
|
||||||
|
mode: "参数校验",
|
||||||
|
args: ["asset-center", "favorite", "--quiet", "--workspace-id", workspaceId],
|
||||||
|
expect: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "校验",
|
||||||
|
command: "asset-center download (缺 --out)",
|
||||||
|
mode: "参数校验",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"download",
|
||||||
|
"--id",
|
||||||
|
sampleAssetId,
|
||||||
|
"--quiet",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "校验",
|
||||||
|
command: "asset-center oss bind (BEFORE_DAYS 缺 before-days)",
|
||||||
|
mode: "参数校验",
|
||||||
|
args: [
|
||||||
|
"asset-center",
|
||||||
|
"oss",
|
||||||
|
"bind",
|
||||||
|
"--bucket",
|
||||||
|
"b",
|
||||||
|
"--region",
|
||||||
|
"cn-hangzhou",
|
||||||
|
"--path-prefix",
|
||||||
|
"p/",
|
||||||
|
"--policy",
|
||||||
|
"BEFORE_DAYS",
|
||||||
|
"--workspace-id",
|
||||||
|
workspaceId,
|
||||||
|
],
|
||||||
|
expect: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(`\n🔍 Asset Center 命令调试 — workspace: ${workspaceId}`);
|
||||||
|
console.log(`📦 样本 asset-id: ${sampleAssetId}\n`);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const testCase of cases) {
|
||||||
|
process.stdout.write(` running: ${testCase.command} ... `);
|
||||||
|
const result = runBl(testCase.args);
|
||||||
|
const pass = result.exitCode === testCase.expect;
|
||||||
|
console.log(pass ? "ok" : `FAIL (exit ${result.exitCode})`);
|
||||||
|
results.push({ ...testCase, ...result, pass });
|
||||||
|
}
|
||||||
|
|
||||||
|
const passed = results.filter((row) => row.pass).length;
|
||||||
|
const failed = results.filter((row) => !row.pass).length;
|
||||||
|
|
||||||
|
// Markdown report
|
||||||
|
const lines = [
|
||||||
|
`# Asset Center 命令测试报告`,
|
||||||
|
``,
|
||||||
|
`- **测试时间**: ${new Date().toISOString().replace("T", " ").slice(0, 19)} (UTC)`,
|
||||||
|
`- **Workspace**: \`${workspaceId}\``,
|
||||||
|
`- **样本 Asset ID**: \`${sampleAssetId}\``,
|
||||||
|
`- **策略**: 只读命令真实调用;写操作/下载/OSS 变更一律 \`--dry-run\``,
|
||||||
|
`- **汇总**: ${passed} 通过 / ${failed} 失败 / ${results.length} 总计`,
|
||||||
|
``,
|
||||||
|
`## 测试结果`,
|
||||||
|
``,
|
||||||
|
`| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |`,
|
||||||
|
`|---|------|------|------|------|------|------|----------|`,
|
||||||
|
];
|
||||||
|
|
||||||
|
results.forEach((row, index) => {
|
||||||
|
lines.push(
|
||||||
|
`| ${index + 1} | ${row.category} | \`${row.command}\` | ${row.mode} | ${status(row.exitCode, row.expect)} | ${row.exitCode} | ${row.durationMs}ms | ${summarizeOutput(row).replace(/\|/g, "\\|")} |`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
lines.push(``, `## 失败详情`, ``);
|
||||||
|
for (const row of results.filter((row) => !row.pass)) {
|
||||||
|
lines.push(`### ${row.command}`, ``);
|
||||||
|
lines.push(`**stderr:**`, `\`\`\``, row.stderr || "(empty)", `\`\`\``, ``);
|
||||||
|
lines.push(`**stdout:**`, `\`\`\``, row.stdout.slice(0, 500) || "(empty)", `\`\`\``, ``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
``,
|
||||||
|
`## 模式说明`,
|
||||||
|
``,
|
||||||
|
`| 模式 | 说明 |`,
|
||||||
|
`|------|------|`,
|
||||||
|
`| 真实调用 | 只读 API,不修改数据 |`,
|
||||||
|
`| dry-run | 输出 \`{ api, data, gateway }\` 请求体,不发起写操作 |`,
|
||||||
|
`| 参数校验 | 预期 exit code 2(用法错误) |`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reportPath = join(root, "packages/commands/src/commands/asset-center/TEST-REPORT.md");
|
||||||
|
writeFileSync(reportPath, lines.join("\n"));
|
||||||
|
console.log(`\n📄 Report written: ${reportPath}`);
|
||||||
|
console.log(`✅ ${passed} passed, ❌ ${failed} failed\n`);
|
||||||
|
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
Reference in New Issue
Block a user