Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ _Built for AI Agents. Every command works as a structured tool call._
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Cursor, Qoder, Qoder Work, QwenWork (千问办公), Qwen Code, or Gemini CLI with channel attribution preserved
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step

> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
> **Note:** App orchestration, native MCP setup, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.

## Showcase 1: A Cinematic Short Film from One Sentence

Expand Down
3 changes: 2 additions & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
- **素材理解** — 图像、文档、音频、长视频的解析与问答
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Cursor、Qoder、Qoder Work、千问办公(QwenWork)、Qwen Code 或 Gemini CLI,并保留渠道归因
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent

> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
> **注意:** 应用编排、原生 MCP 接入、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。

## 示例 1:一句话生成一部电影短片

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ _Built for AI Agents. Every command works as a structured tool call._
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Cursor, Qoder, Qoder Work, QwenWork (千问办公), Qwen Code, or Gemini CLI with channel attribution preserved
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step

> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
> **Note:** App orchestration, native MCP setup, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.

## Showcase 1: A Cinematic Short Film from One Sentence

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
- **素材理解** — 图像、文档、音频、长视频的解析与问答
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Cursor、Qoder、Qoder Work、千问办公(QwenWork)、Qwen Code 或 Gemini CLI,并保留渠道归因
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent

> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
> **注意:** 应用编排、原生 MCP 接入、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。

## 示例 1:一句话生成一部电影短片

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import {
knowledgeCollectionGet,
knowledgeDocImportOss,
mcpCall,
mcpConnect,
mcpDisconnect,
mcpList,
mcpTools,
searchWeb,
Expand Down Expand Up @@ -337,6 +339,8 @@ export const commands: Record<string, AnyCommand> = {
"knowledge file get": knowledgeFileGet,
"knowledge file delete": knowledgeFileDelete,
"mcp call": mcpCall,
"mcp connect": mcpConnect,
"mcp disconnect": mcpDisconnect,
"mcp list": mcpList,
"mcp tools": mcpTools,
"search web": searchWeb,
Expand Down
142 changes: 122 additions & 20 deletions packages/commands/src/commands/config/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { inflateRawSync } from "node:zlib";
import yaml from "yaml";
import { parse as parseToml } from "smol-toml";
import { qwenworkMcpPath, workbuddyMcpPaths } from "../mcp/agent-config.ts";

/**
* Where an item comes from. Everything discovered on disk today is `local`;
Expand Down Expand Up @@ -397,7 +398,9 @@ function transportOf(entry: Record<string, unknown>): {
const url = typeof entry.url === "string" ? entry.url : undefined;
if (url) {
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
return { transport: type === "sse" ? "sse" : "http", detail: url };
const transportField = typeof entry.transport === "string" ? entry.transport.toLowerCase() : "";
if (type === "sse" || transportField === "sse") return { transport: "sse", detail: url };
return { transport: "http", detail: url };
}
return { transport: "unknown", detail: "" };
}
Expand Down Expand Up @@ -453,22 +456,65 @@ export function listMcpServers(home: string = homedir()): McpServerInfo[] {
const opencode = readJsonSafe(join(home, ".config", "opencode", "opencode.json"));
if (opencode) collectMcpMap(opencode.mcp, "opencode", "global", out, true);

// Cursor, Windsurf, Gemini, QoderWork, OpenClaw, Claude Desktop: all JSON with
// a top-level `mcpServers` map (Claude/Cursor convention).
// Cursor, Windsurf, Gemini, Qoder, Qoder Work, QwenWork (千问办公), OpenClaw,
// Claude Desktop: JSON with a top-level `mcpServers` map.
const cursor = readJsonSafe(join(home, ".cursor", "mcp.json"));
if (cursor) collectMcpMap(cursor.mcpServers, "cursor", "global", out, true);

const qoder = readJsonSafe(join(home, ".qoder", "mcp.json"));
if (qoder) collectMcpMap(qoder.mcpServers, "qoder", "global", out, true);

const qoderwork = readJsonSafe(join(home, ".qoderwork", "mcp.json"));
if (qoderwork) collectMcpMap(qoderwork.mcpServers, "qoderwork", "global", out, true);

const qwenwork = readJsonSafe(qwenworkMcpPath(home));
if (qwenwork) collectMcpMap(qwenwork.mcpServers, "qwenwork", "global", out, true);

const windsurf = readJsonSafe(join(home, ".codeium", "windsurf", "mcp_config.json"));
if (windsurf) collectMcpMap(windsurf.mcpServers, "windsurf", "global", out, true);

const gemini = readJsonSafe(join(home, ".gemini", "settings.json"));
if (gemini) collectMcpMap(gemini.mcpServers, "gemini", "global", out, true);

const qoder = readJsonSafe(join(home, ".qoderwork", "mcp.json"));
if (qoder) collectMcpMap(qoder.mcpServers, "qoderwork", "global", out, true);

const openclaw = readJsonSafe(join(home, ".openclaw", "openclaw.json"));
if (openclaw) collectMcpMap(openclaw.mcpServers, "openclaw", "global", out, true);
if (openclaw) {
const mcp = asRecord(openclaw.mcp);
collectMcpMap(mcp?.servers ?? openclaw.mcpServers, "openclaw", "global", out, true);
}

const zcode = readJsonSafe(join(home, ".zcode", "cli", "config.json"));
if (zcode) collectMcpMap(asRecord(zcode.mcp)?.servers, "zcode", "global", out, true);

for (const file of workbuddyMcpPaths(home)) {
const workbuddy = readJsonSafe(file);
if (workbuddy) collectMcpMap(workbuddy.mcpServers, "workbuddy", file, out, true);
}

const dshPatch = readText(join(home, ".dsh", "cordis.patch.yml"));
if (dshPatch) {
try {
const parsed = yaml.parse(dshPatch) as unknown;
const items = Array.isArray(parsed) ? parsed : [];
const dshServers: Record<string, unknown> = {};
for (const item of items) {
const entries = asRecord(item)?.insert;
const list = Array.isArray(entries) ? entries : [item];
for (const entry of list) {
const record = asRecord(entry);
const config = record ? asRecord(record.config) : undefined;
if (
record?.name === "@deepseek-ai/dsh-mcp-client" &&
typeof config?.serverName === "string"
) {
dshServers[config.serverName] = config;
}
}
}
collectMcpMap(dshServers, "deepseek-harness", "global", out, false);
} catch {
/* ignore malformed yaml */
}
}

const claudeDesktop = readJsonSafe(claudeDesktopConfigPath(home));
if (claudeDesktop) collectMcpMap(claudeDesktop.mcpServers, "claude-desktop", "global", out, true);
Expand Down Expand Up @@ -507,6 +553,8 @@ function claudeDesktopConfigPath(home: string): string {
interface McpWriteTarget {
file: string;
mapKey: string;
/** When set, the server map lives at parentKey.mapKey (e.g. mcp.servers). */
parentKey?: string;
/** Claude stores project-scoped servers under projects[scope][mapKey]. */
projectScoped: boolean;
}
Expand Down Expand Up @@ -553,15 +601,41 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
mapKey: "mcpServers",
projectScoped: false,
};
if (source === "qoder")
return {
file: join(home, ".qoder", "mcp.json"),
mapKey: "mcpServers",
projectScoped: false,
};
if (source === "qoderwork")
return {
file: join(home, ".qoderwork", "mcp.json"),
mapKey: "mcpServers",
projectScoped: false,
};
if (source === "qwenwork")
return {
file: qwenworkMcpPath(home),
mapKey: "mcpServers",
projectScoped: false,
};
if (source === "openclaw")
return {
file: join(home, ".openclaw", "openclaw.json"),
mapKey: "servers",
parentKey: "mcp",
projectScoped: false,
};
if (source === "zcode")
return {
file: join(home, ".zcode", "cli", "config.json"),
mapKey: "servers",
parentKey: "mcp",
projectScoped: false,
};
if (source === "workbuddy")
return {
file: workbuddyMcpPaths(home)[0] ?? join(home, ".workbuddy-ai", "mcp.json"),
mapKey: "mcpServers",
projectScoped: false,
};
Expand Down Expand Up @@ -604,6 +678,45 @@ function unmaskMcpConfig(submitted: unknown, stored: unknown): unknown {
return submitted;
}

function mcpMapContainer(
root: Record<string, unknown>,
target: McpWriteTarget,
scope: string,
): Record<string, unknown> | undefined {
let container: Record<string, unknown> | undefined = root;
if (target.projectScoped) {
const projects = asRecord(root.projects);
container = projects ? asRecord(projects[scope]) : undefined;
}
if (!container) return undefined;
if (target.parentKey) {
const parent = asRecord(container[target.parentKey]);
return parent;
}
return container;
}

function ensureMcpMapContainer(
root: Record<string, unknown>,
target: McpWriteTarget,
scope: string,
): Record<string, unknown> {
let container: Record<string, unknown> = root;
if (target.projectScoped) {
const projects = asRecord(root.projects) ?? {};
root.projects = projects;
const proj = asRecord(projects[scope]) ?? {};
projects[scope] = proj;
container = proj;
}
if (target.parentKey) {
const parent = asRecord(container[target.parentKey]) ?? {};
container[target.parentKey] = parent;
return parent;
}
return container;
}

/** Create or update one MCP server entry, writing back to its source file. */
export function writeMcpServer(
source: string,
Expand All @@ -620,14 +733,7 @@ export function writeMcpServer(
if (!cfg) throw new Error("Config must be a JSON object.");

const root = readJsonSafe(target.file) ?? {};
let container: Record<string, unknown> = root;
if (target.projectScoped) {
const projects = asRecord(root.projects) ?? {};
root.projects = projects;
const proj = asRecord(projects[scope]) ?? {};
projects[scope] = proj;
container = proj;
}
const container = ensureMcpMapContainer(root, target, scope);
const map = asRecord(container[target.mapKey]) ?? {};
container[target.mapKey] = map;

Expand All @@ -648,11 +754,7 @@ export function deleteMcpServer(
if (!target) throw new Error("This MCP source is read-only and cannot be edited here.");
const root = readJsonSafe(target.file);
if (!root) throw new Error("Config file not found.");
let container: Record<string, unknown> | undefined = root;
if (target.projectScoped) {
const projects = asRecord(root.projects);
container = projects ? asRecord(projects[scope]) : undefined;
}
const container = mcpMapContainer(root, target, scope);
const map = container ? asRecord(container[target.mapKey]) : undefined;
if (!map || !(name in map)) throw new Error("Server not found: " + name);
delete map[name];
Expand Down
5 changes: 4 additions & 1 deletion packages/commands/src/commands/config/ui-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1639,7 +1639,10 @@ const PAGE_HTML = `<!doctype html>
{ id: 'gemini', label: 'Gemini' },
{ id: 'opencode', label: 'OpenCode' },
{ id: 'openclaw', label: 'OpenClaw' },
{ id: 'qoderwork', label: 'QoderWork' }
{ id: 'qoderwork', label: 'QoderWork' },
{ id: 'zcode', label: 'ZCode' },
{ id: 'workbuddy', label: 'WorkBuddy' },
{ id: 'deepseek-harness', label: 'DeepSeek Harness' }
];
function copyButton(getText) {
var b = uiEl('button', 'copy-btn', 'Copy'); b.type = 'button';
Expand Down
Loading
Loading