mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Merge remote-tracking branch 'origin/main' into feat/sandbox
This commit is contained in:
@@ -16,6 +16,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
|
||||
- Sandbox calls use the Bailian API Key as a Bearer token without an E2B SDK or E2B API key. Returned connection credentials and dry-run environment values are redacted by default; use `--show-credentials` only when the raw connection tokens are required.
|
||||
|
||||
## [1.22.0] - 2026-09-08
|
||||
|
||||
### Changed
|
||||
|
||||
- **Project initialization** — `managed-agent project init` now creates `./managed-agent` by default. Use `--project .` to initialize in place. **(BREAKING)**
|
||||
- **Build confirmation** — `managed-agent project build` no longer requires confirmation and rejects `--yes`. Use `--dry-run` for a read-only preview; Publish still requires confirmation. **(BREAKING)**
|
||||
- **Managed Agent SDK** — upgrade to `0.7.1`. Build automatically associates active Agent-local resources while preserving explicit bindings, Skill versions, and File mount paths. Ambiguous Environment or Vault selections are rejected before writing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Project diagnostics** — provide actionable project-root guidance and surface the underlying Build validation error.
|
||||
- **YAML initialization paths** — show the absolute YAML path in creation messages and existing-file errors.
|
||||
|
||||
### Internal
|
||||
|
||||
- Expand project initialization and Build regression coverage, and remove the obsolete Build confirmation flag from the local lifecycle E2E test.
|
||||
|
||||
## [1.21.0] - 2026-09-07
|
||||
|
||||
### Added
|
||||
|
||||
@@ -16,6 +16,23 @@
|
||||
|
||||
- Sandbox 调用使用百炼 API Key 作为 Bearer Token,不依赖 E2B SDK,也不发送 E2B API Key。连接凭据和 dry-run 中的环境变量默认脱敏;仅在确实需要原始连接 Token 时使用 `--show-credentials`。
|
||||
|
||||
## [1.22.0] - 2026-09-08
|
||||
|
||||
### 变更
|
||||
|
||||
- **项目初始化** —— `managed-agent project init` 默认创建 `./managed-agent` 子目录;如需原地初始化,请使用 `--project .`。**(BREAKING)**
|
||||
- **Build 确认机制** —— `managed-agent project build` 无需确认,并且不再接受 `--yes`。使用 `--dry-run` 可只读预览;Publish 仍需显式确认。**(BREAKING)**
|
||||
- **Managed Agent SDK** —— 升级至 `0.7.1`。Build 自动关联 Agent 目录下已启用的资源,保留显式引用、Skill 版本和 File 挂载路径;Environment 或 Vault 选择存在歧义时,在写入前报错。
|
||||
|
||||
### 修复
|
||||
|
||||
- **项目诊断** —— 提供可操作的项目根目录提示,并展示 Build 校验失败的具体原因。
|
||||
- **YAML 初始化路径** —— 创建成功及文件已存在的错误信息均展示 YAML 绝对路径。
|
||||
|
||||
### 内部
|
||||
|
||||
- 补充项目初始化和 Build 回归覆盖,移除本地闭环 E2E 测试中过时的 Build 确认参数。
|
||||
|
||||
## [1.21.0] - 2026-09-07
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- 类型由 `ParsedFlags<typeof FLAGS>` 推导;避免手写 `flags.x as number` 这类断言
|
||||
- 单 flag 必填用 `required: true`;跨 flag / 值相关校验放 `validate`
|
||||
- 默认值 fallback 写在命令实现或 `Settings` 解析层,不要重复解析 env/config
|
||||
- 需要在高风险确认前检查本地路径时,可用异步 `validate`;runtime 会在鉴权和确认前等待它完成。这里只允许本地只读检查,不写文件、不请求远端。非缺参的环境错误应抛出 `BailianError`,避免裸命令调用被当成缺参而仅显示 help。
|
||||
|
||||
### B. 鉴权 / 全局选项
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
|
||||
"keywords": [
|
||||
"agent",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-commands",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
@@ -40,7 +40,7 @@
|
||||
"check": "vp check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openagentpack/sdk": "0.7.0",
|
||||
"@openagentpack/sdk": "0.7.1",
|
||||
"bailian-cli-core": "workspace:*",
|
||||
"bailian-cli-runtime": "workspace:*",
|
||||
"boxen": "catalog:",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import {
|
||||
BailianError,
|
||||
defineCommand,
|
||||
@@ -87,7 +88,7 @@ export default defineCommand({
|
||||
|
||||
if (existsSync(file) && !flags.force) {
|
||||
throw new BailianError(
|
||||
`${file} already exists.`,
|
||||
`${resolve(file)} already exists.`,
|
||||
ExitCode.USAGE,
|
||||
"Pass --force to overwrite.",
|
||||
);
|
||||
@@ -128,7 +129,7 @@ export default defineCommand({
|
||||
if (format === "json") {
|
||||
emitResult({ created: file, provider: "bailian", agent: agentName }, format);
|
||||
} else {
|
||||
emitBare(`Created ${file}`);
|
||||
emitBare(`Created ${resolve(file)}`);
|
||||
emitBare(
|
||||
"Credentials: run `bl auth login --api-key <key> --base-url <url>`, or set DASHSCOPE_API_KEY / BAILIAN_BASE_URL.",
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
BailianError,
|
||||
defineCommand,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
planProjectPublish,
|
||||
previewProjectBuild,
|
||||
type ProjectBuildResolver,
|
||||
resolveDirectoryProjectRoot,
|
||||
validateDirectoryProject,
|
||||
} from "@openagentpack/sdk/project-workspace";
|
||||
import { CREDENTIALS_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts";
|
||||
@@ -44,9 +47,23 @@ export const managedAgentProjectInit = defineCommand({
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--project <directory>]",
|
||||
flags: PROJECT_FLAG,
|
||||
exampleArgs: ["", "--project ./my-agent"],
|
||||
flags: {
|
||||
project: {
|
||||
...PROJECT_FLAG.project,
|
||||
description: {
|
||||
"en-US": "Directory project root (default: ./managed-agent under the current directory)",
|
||||
"zh-CN": "目录项目根路径(默认:当前目录下的 ./managed-agent)",
|
||||
},
|
||||
},
|
||||
},
|
||||
exampleArgs: ["", "--project ./my-agent", "--project ."],
|
||||
notes: [
|
||||
{
|
||||
"en-US":
|
||||
"Without --project, creates a managed-agent/ subdirectory. Enter it before running other project commands. Use --project . to initialize in place or convert the current agents.yaml; existing project files are not overwritten.",
|
||||
"zh-CN":
|
||||
"不传 --project 时创建 managed-agent/ 子目录;后续项目操作请先进入该目录。使用 --project . 可在当前目录初始化或转换 agents.yaml;不会覆盖已有项目文件。",
|
||||
},
|
||||
{
|
||||
"en-US":
|
||||
"New projects include Skill, File, Vault, and Environment examples under each resource directory's _examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside _examples/ to enable it, then configure its Agent reference.",
|
||||
@@ -55,16 +72,17 @@ export const managedAgentProjectInit = defineCommand({
|
||||
},
|
||||
],
|
||||
async run(ctx) {
|
||||
const projectRoot = ctx.flags.project ?? "./managed-agent";
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
would_initialize_project: ctx.flags.project ?? ".",
|
||||
would_initialize_project: projectRoot,
|
||||
},
|
||||
detectOutputFormat(ctx.settings.output),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const result = await initializeDirectoryProject({ projectRoot: ctx.flags.project ?? "." });
|
||||
const result = await initializeDirectoryProject({ projectRoot });
|
||||
emitResult(result, detectOutputFormat(ctx.settings.output));
|
||||
},
|
||||
});
|
||||
@@ -99,18 +117,34 @@ export const managedAgentProjectBuild = defineCommand({
|
||||
"zh-CN": "整理目录源文件并生成不可变的发布 Build",
|
||||
},
|
||||
auth: "none",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
notes: [
|
||||
{
|
||||
"en-US":
|
||||
"This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build.",
|
||||
"Build writes local project files without confirmation, including inferred resource associations and migration of plaintext Vault secrets into .env. Use --dry-run to preview without writing. Publish still requires explicit confirmation before remote changes.",
|
||||
"zh-CN":
|
||||
"该操作会整理项目源文件,将 Vault 明文密钥移入本地 .env,并写入已预览的不可变 Build。",
|
||||
"Build 无需确认即可写入本地项目文件,包括推断的资源关联及将 Vault 明文密钥移入 .env。使用 --dry-run 可只预览不写入。Publish 变更远端资源前仍需显式确认。",
|
||||
},
|
||||
},
|
||||
],
|
||||
usageArgs: "[--project <directory>]",
|
||||
flags: PROJECT_FLAG,
|
||||
exampleArgs: ["--dry-run", "--yes", "--project ./my-agent --yes"],
|
||||
exampleArgs: ["", "--dry-run", "--project ./my-agent"],
|
||||
async validate(flags) {
|
||||
await withAgentErrors(async () => {
|
||||
const root = await resolveDirectoryProjectRoot(flags.project ?? ".");
|
||||
const metadata = await stat(join(root, "project.json")).catch((error: unknown) => {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
||||
throw error;
|
||||
});
|
||||
if (!metadata?.isFile()) {
|
||||
throw new BailianError(
|
||||
`Not a project root: ${root} (project.json is missing).`,
|
||||
ExitCode.USAGE,
|
||||
"Run from the directory containing project.json, or pass --project <directory>.",
|
||||
);
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const root = ctx.flags.project ?? ".";
|
||||
const preview = await previewProjectBuild(root);
|
||||
@@ -120,7 +154,11 @@ export const managedAgentProjectBuild = defineCommand({
|
||||
return;
|
||||
}
|
||||
if (!preview.can_build)
|
||||
throw new BailianError("Directory project is invalid and cannot be built.", ExitCode.GENERAL);
|
||||
throw new BailianError(
|
||||
preview.diagnostics.find((diagnostic) => diagnostic.severity === "error")?.message ??
|
||||
"Directory project is invalid and cannot be built.",
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
const built = await commitProjectBuild({
|
||||
projectRoot: root,
|
||||
baseRevision: preview.project_revision,
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runNodeMain } from "e2e/runner";
|
||||
import { afterEach, describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson } from "./helpers.ts";
|
||||
|
||||
const directories: string[] = [];
|
||||
|
||||
async function temporaryDirectory() {
|
||||
const directory = await realpath(await mkdtemp(join(tmpdir(), "bailian-yaml-init-")));
|
||||
directories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
for (const directory of directories.splice(0)) {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
async function runInit(directory: string, args: string[] = []) {
|
||||
return runNodeMain(
|
||||
fileURLToPath(new URL("./harness/main.ts", import.meta.url)),
|
||||
["managed-agent", "init", ...args],
|
||||
{
|
||||
cwd: directory,
|
||||
env: {
|
||||
BAILIAN_CONFIG_DIR: await temporaryDirectory(),
|
||||
BAILIAN_E2E_ROUTES: JSON.stringify([
|
||||
{ path: "managed-agent init", export: "managedAgentInit" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("e2e: managed-agent init output path", () => {
|
||||
test.each(["default", "relative", "absolute"] as const)(
|
||||
"reports the absolute YAML path for a %s output path",
|
||||
async (pathKind) => {
|
||||
const directory = await temporaryDirectory();
|
||||
const relativePath =
|
||||
pathKind === "default" ? "agents.yaml" : join("config files", "custom agents.yaml");
|
||||
const outputPath = join(directory, relativePath);
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
const args =
|
||||
pathKind === "default"
|
||||
? []
|
||||
: ["--file", pathKind === "absolute" ? outputPath : relativePath];
|
||||
|
||||
const result = await runInit(directory, args);
|
||||
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.stdout.split(/\r?\n/)[0]).toBe(`Created ${outputPath}`);
|
||||
expect(await readFile(outputPath, "utf8")).toContain("agents:");
|
||||
},
|
||||
);
|
||||
|
||||
test("preserves the JSON output contract", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const result = await runInit(directory, ["--file", "custom.yaml", "--output", "json"]);
|
||||
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson(result.stdout)).toEqual({
|
||||
created: "custom.yaml",
|
||||
provider: "bailian",
|
||||
agent: "assistant",
|
||||
});
|
||||
expect((await stat(join(directory, "custom.yaml"))).isFile()).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps dry-run read-only without reporting a created file", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const result = await runInit(directory, ["--dry-run", "--output", "json"]);
|
||||
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson(result.stdout)).toEqual({
|
||||
would_create: "agents.yaml",
|
||||
provider: "bailian",
|
||||
agent: "assistant",
|
||||
would_update_gitignore: true,
|
||||
});
|
||||
expect(await stat(join(directory, "agents.yaml")).catch(() => null)).toBeNull();
|
||||
expect(await stat(join(directory, ".gitignore")).catch(() => null)).toBeNull();
|
||||
});
|
||||
|
||||
test.each(["default", "relative", "absolute"] as const)(
|
||||
"reports the absolute existing %s path without overwriting the YAML",
|
||||
async (pathKind) => {
|
||||
const directory = await temporaryDirectory();
|
||||
const relativePath =
|
||||
pathKind === "default" ? "agents.yaml" : join("config files", "custom agents.yaml");
|
||||
const outputPath = join(directory, relativePath);
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, "Keep user configuration.\n");
|
||||
const args =
|
||||
pathKind === "default"
|
||||
? []
|
||||
: ["--file", pathKind === "absolute" ? outputPath : relativePath];
|
||||
|
||||
const result = await runInit(directory, args);
|
||||
|
||||
expect(result.exitCode).toBe(2);
|
||||
expect(result.stderr).toContain(`${outputPath} already exists.`);
|
||||
expect(result.stderr).toContain("Pass --force to overwrite.");
|
||||
expect(result.stdout).not.toContain("Created ");
|
||||
expect(await readFile(outputPath, "utf8")).toBe("Keep user configuration.\n");
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
cp,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
realpath,
|
||||
rm,
|
||||
stat,
|
||||
symlink,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runNodeMain } from "e2e/runner";
|
||||
import { afterEach, describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson, runCommandHelp } from "./helpers.ts";
|
||||
|
||||
const routes = {
|
||||
"managed-agent project init": "managedAgentProjectInit",
|
||||
"managed-agent project build": "managedAgentProjectBuild",
|
||||
"managed-agent project publish": "managedAgentProjectPublish",
|
||||
"managed-agent project validate": "managedAgentProjectValidate",
|
||||
} as const;
|
||||
const directories: string[] = [];
|
||||
|
||||
async function temporaryDirectory() {
|
||||
const directory = await realpath(await mkdtemp(join(tmpdir(), "bailian-project-init-")));
|
||||
directories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
for (const directory of directories.splice(0)) {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function runInit(directory: string, args: string[] = []) {
|
||||
return runProject(directory, "init", args);
|
||||
}
|
||||
|
||||
async function runProject(directory: string, subcommand: string, args: string[] = [], json = true) {
|
||||
const configRoot = await temporaryDirectory();
|
||||
return runNodeMain(
|
||||
fileURLToPath(new URL("./harness/main.ts", import.meta.url)),
|
||||
["managed-agent", "project", subcommand, ...args, ...(json ? ["--output", "json"] : [])],
|
||||
{
|
||||
cwd: directory,
|
||||
env: {
|
||||
BAILIAN_CONFIG_DIR: configRoot,
|
||||
BAILIAN_E2E_ROUTES: JSON.stringify(
|
||||
Object.entries(routes).map(([path, exportName]) => ({ path, export: exportName })),
|
||||
),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("e2e: managed-agent project init directory defaults", () => {
|
||||
test("build links copied resources and reports ambiguous environment bindings without writing", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const initialized = await runInit(directory);
|
||||
expect(initialized.exitCode, initialized.stderr).toBe(0);
|
||||
const root = join(directory, "managed-agent");
|
||||
for (const [section, id] of [
|
||||
["skills", "example-skill"],
|
||||
["files", "example-file"],
|
||||
["environments", "example-env"],
|
||||
["vaults", "example-vault"],
|
||||
] as const) {
|
||||
await cp(
|
||||
join(root, "agents/assistant", section, "_examples", id),
|
||||
join(root, "agents/assistant", section, id),
|
||||
{ recursive: true },
|
||||
);
|
||||
}
|
||||
const agentPath = join(root, "agents/assistant/agent.json");
|
||||
const original = await readFile(agentPath, "utf8");
|
||||
const preview = await runProject(root, "build", ["--dry-run"]);
|
||||
expect(preview.exitCode, preview.stderr).toBe(0);
|
||||
expect(parseStdoutJson<{ can_build: boolean }>(preview.stdout).can_build).toBe(true);
|
||||
expect(await readFile(agentPath, "utf8")).toBe(original);
|
||||
const built = await runProject(root, "build");
|
||||
expect(built.exitCode, built.stderr).toBe(0);
|
||||
const agent = JSON.parse(await readFile(agentPath, "utf8"));
|
||||
expect(agent).toMatchObject({
|
||||
environment: "example-env",
|
||||
vault: "example-vault",
|
||||
skills: ["example-skill"],
|
||||
files: [{ file: "example-file", mount_path: "/mnt/example.md" }],
|
||||
});
|
||||
const alternatePath = join(root, "agents/assistant/environments/alternate");
|
||||
await mkdir(alternatePath);
|
||||
await writeFile(
|
||||
join(alternatePath, "environment.json"),
|
||||
JSON.stringify({ id: "alternate", config: { type: "cloud" } }),
|
||||
);
|
||||
delete agent.environment;
|
||||
await writeFile(agentPath, JSON.stringify(agent));
|
||||
const beforeConflict = await readFile(agentPath, "utf8");
|
||||
const buildPath = join(root, ".openagentpack/build/agents.yaml");
|
||||
const beforeBuild = await readFile(buildPath, "utf8");
|
||||
for (const json of [false, true]) {
|
||||
const conflict = await runProject(root, "build", [], json);
|
||||
expect(conflict.exitCode).toBe(1);
|
||||
expect(conflict.stderr).toContain("multiple local environment resources");
|
||||
expect(conflict.stderr).toContain("Set 'environment' explicitly");
|
||||
expect(conflict.stderr).not.toMatch(/\p{Script=Han}/u);
|
||||
}
|
||||
expect(await readFile(agentPath, "utf8")).toBe(beforeConflict);
|
||||
expect(await readFile(buildPath, "utf8")).toBe(beforeBuild);
|
||||
});
|
||||
|
||||
test("build checks directories and writes without confirmation while publish stays gated", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const initialized = await runInit(directory);
|
||||
expect(initialized.exitCode, initialized.stderr).toBe(0);
|
||||
const root = join(directory, "managed-agent");
|
||||
const nested = join(root, "agents/assistant/skills");
|
||||
for (const args of [[], ["--dry-run"]]) {
|
||||
const result = await runProject(nested, "build", args, false);
|
||||
expect(result.exitCode, result.stderr).toBe(2);
|
||||
expect(result.stderr).toContain("Not a project root:");
|
||||
expect(result.stderr).not.toMatch(/\p{Script=Han}/u);
|
||||
expect(result.stderr).toContain(`cd '${root}'`);
|
||||
expect(result.stderr).not.toContain("high-risk");
|
||||
expect(result.stderr).not.toContain("Usage:");
|
||||
}
|
||||
const explicit = await runProject(root, "build", ["--project", nested]);
|
||||
expect(explicit.exitCode).toBe(2);
|
||||
expect(explicit.stderr).toContain(`--project '${root}'`);
|
||||
const noMarker = await runProject(directory, "build", [], false);
|
||||
expect(noMarker.exitCode).toBe(2);
|
||||
expect(noMarker.stderr).toContain("project.json");
|
||||
expect(noMarker.stderr).not.toMatch(/\p{Script=Han}/u);
|
||||
expect(noMarker.stderr).not.toContain("high-risk");
|
||||
expect(await stat(join(nested, ".openagentpack")).catch(() => null)).toBeNull();
|
||||
|
||||
const preview = await runProject(root, "build", ["--dry-run"]);
|
||||
expect(preview.exitCode, preview.stderr).toBe(0);
|
||||
expect(await stat(join(root, ".openagentpack/build")).catch(() => null)).toBeNull();
|
||||
const built = await runProject(root, "build");
|
||||
expect(built.exitCode, built.stderr).toBe(0);
|
||||
expect(built.stderr).not.toContain("requires_confirmation");
|
||||
expect((await stat(join(root, ".openagentpack/build/agents.yaml"))).isFile()).toBe(true);
|
||||
const explicitValid = await runProject(nested, "build", ["--project", root]);
|
||||
expect(explicitValid.exitCode, explicitValid.stderr).toBe(0);
|
||||
const storePath = join(root, ".openagentpack/versions/project/store.json");
|
||||
const beforePublish = await readFile(storePath, "utf8");
|
||||
const publish = await runProject(root, "publish");
|
||||
expect(publish.exitCode).toBe(7);
|
||||
expect(publish.stderr).toContain("requires_confirmation");
|
||||
expect(await readFile(storePath, "utf8")).toBe(beforePublish);
|
||||
});
|
||||
|
||||
test("only publish help includes confirmation; build keeps dry-run", async () => {
|
||||
const build = await runCommandHelp(routes, ["managed-agent", "project", "build", "--help"]);
|
||||
expect(build.stderr).not.toContain("--yes");
|
||||
expect(build.stderr).toContain("--dry-run");
|
||||
const publish = await runCommandHelp(routes, ["managed-agent", "project", "publish", "--help"]);
|
||||
expect(publish.stderr).toContain("--yes");
|
||||
const directory = await temporaryDirectory();
|
||||
const removedFlag = await runProject(directory, "build", ["--yes"]);
|
||||
expect(removedFlag.exitCode).toBe(2);
|
||||
expect(removedFlag.stderr).toMatch(/Unknown flag.*--yes/);
|
||||
});
|
||||
|
||||
test("nested build and validate explain the project root without changing directories", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const initialized = await runInit(directory);
|
||||
expect(initialized.exitCode, initialized.stderr).toBe(0);
|
||||
const root = join(directory, "managed-agent");
|
||||
const nested = join(root, "agents/assistant/skills");
|
||||
for (const command of ["build", "validate"]) {
|
||||
const result = await runProject(nested, command, ["--dry-run"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(result.stderr).toContain("Not a project root:");
|
||||
expect(result.stderr).not.toMatch(/\p{Script=Han}/u);
|
||||
expect(result.stderr).toContain(`cd '${root}'`);
|
||||
expect(result.stderr).toContain(`--project '${root}'`);
|
||||
expect(result.stderr).not.toContain("ERR_MODULE_NOT_FOUND");
|
||||
}
|
||||
expect(await stat(join(nested, ".openagentpack")).catch(() => null)).toBeNull();
|
||||
const corrected = await runProject(nested, "build", ["--project", root, "--dry-run"]);
|
||||
expect(corrected.exitCode, corrected.stderr).toBe(0);
|
||||
});
|
||||
|
||||
test("help describes the subdirectory default and explicit in-place initialization", async () => {
|
||||
const result = await runCommandHelp(routes, ["managed-agent", "project", "init", "--help"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stderr).toContain("./managed-agent");
|
||||
expect(result.stderr).toContain("--project .");
|
||||
});
|
||||
|
||||
test("default dry-run reports the child directory without creating it", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const result = await runInit(directory, ["--dry-run"]);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson(result.stdout)).toEqual({ would_initialize_project: "./managed-agent" });
|
||||
expect(await stat(join(directory, "managed-agent")).catch(() => null)).toBeNull();
|
||||
});
|
||||
|
||||
test("initializes only the child, ignores parent source, and rejects repeated init without overwriting", async () => {
|
||||
const directory = await temporaryDirectory();
|
||||
const parentYaml = "not a valid project declaration";
|
||||
await writeFile(join(directory, "agents.yaml"), parentYaml);
|
||||
if (process.platform !== "win32") {
|
||||
await symlink("missing-instructions.md", join(directory, "CLAUDE.md"));
|
||||
}
|
||||
const result = await runInit(directory);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const projectRoot = join(directory, "managed-agent");
|
||||
const initialized = parseStdoutJson<{
|
||||
project_root: string;
|
||||
baseline_version: string;
|
||||
converted_from_yaml: boolean;
|
||||
}>(result.stdout);
|
||||
expect(initialized.project_root).toBe(projectRoot);
|
||||
expect(initialized.baseline_version).toHaveLength(64);
|
||||
expect(initialized.converted_from_yaml).toBe(false);
|
||||
expect(await stat(join(directory, "project.json")).catch(() => null)).toBeNull();
|
||||
expect(await readFile(join(directory, "agents.yaml"), "utf8")).toBe(parentYaml);
|
||||
expect((await stat(join(projectRoot, "agents/assistant/agent.json"))).isFile()).toBe(true);
|
||||
expect(
|
||||
(await stat(join(projectRoot, ".openagentpack/versions/project/store.json"))).isFile(),
|
||||
).toBe(true);
|
||||
const instructions = join(projectRoot, "agents/assistant/instructions.md");
|
||||
await writeFile(instructions, "user changes");
|
||||
const repeated = await runInit(directory);
|
||||
expect(repeated.exitCode).not.toBe(0);
|
||||
expect(repeated.stderr).toContain("already exists");
|
||||
expect(await readFile(instructions, "utf8")).toBe("user changes");
|
||||
});
|
||||
|
||||
test("explicit paths, including dot, remain exact project roots", async () => {
|
||||
for (const target of ["custom-agent", "."]) {
|
||||
const directory = await temporaryDirectory();
|
||||
const result = await runInit(directory, ["--project", target]);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(parseStdoutJson<{ project_root: string }>(result.stdout).project_root).toBe(
|
||||
join(directory, target),
|
||||
);
|
||||
expect(await stat(join(directory, "managed-agent")).catch(() => null)).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -368,7 +368,6 @@ describe("e2e: managed-agent", () => {
|
||||
"build",
|
||||
"--project",
|
||||
projectRoot,
|
||||
"--yes",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -287,8 +287,10 @@ export interface Command<F extends FlagsDef = FlagsDef> {
|
||||
* Cross-flag validation, after parsing and before run. Return an error message
|
||||
* → UsageError; undefined to pass. Single-flag `required` is enforced by the
|
||||
* parser — use this for rules spanning flags or depending on a flag's *value*.
|
||||
* May be async for read-only local preflight. Runtime awaits it before auth
|
||||
* and confirmation. Do not perform remote requests or local writes here.
|
||||
*/
|
||||
validate?: (flags: ParsedFlags<F>) => string | undefined;
|
||||
validate?: (flags: ParsedFlags<F>) => string | undefined | Promise<string | undefined>;
|
||||
run: (ctx: CommandContext<F>) => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "knowledge-studio-cli",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
|
||||
"keywords": [
|
||||
"alibaba-cloud",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-runtime",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -217,7 +217,7 @@ export function createCli(commands: Record<string, AnyCommand>, opts: CliOptions
|
||||
parsedFlags,
|
||||
Object.keys(res.command.flags ?? {}),
|
||||
) as ParsedFlags<FlagsDef>;
|
||||
const invalid = res.command.validate?.(ownFlags);
|
||||
const invalid = await res.command.validate?.(ownFlags);
|
||||
if (invalid) throw new UsageError(invalid);
|
||||
|
||||
// 校验通过 → 建源、解析 settings、组 ctx,进中间件执行命令。
|
||||
|
||||
Generated
+5
-5
@@ -189,8 +189,8 @@ importers:
|
||||
packages/commands:
|
||||
dependencies:
|
||||
'@openagentpack/sdk':
|
||||
specifier: 0.7.0
|
||||
version: 0.7.0
|
||||
specifier: 0.7.1
|
||||
version: 0.7.1
|
||||
bailian-cli-core:
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@@ -1124,8 +1124,8 @@ packages:
|
||||
'@emnapi/core': ^1.7.1
|
||||
'@emnapi/runtime': ^1.7.1
|
||||
|
||||
'@openagentpack/sdk@0.7.0':
|
||||
resolution: {integrity: sha512-MjyKIFvPi4aT1TIcvGUB0bnD4Tly9aMCncjI9+EYG6CPWpuGO4DGj+4zlfBCx1fvtD1G1w4Zi0m8A8ZodqkK9Q==}
|
||||
'@openagentpack/sdk@0.7.1':
|
||||
resolution: {integrity: sha512-OENks8UdfWFanvhJIVbqyUX1+XGavHnrxDiFx4SoJoGRXi45cOgyYtWWbG5VdDI2I5Dpo9eGNaw/JtDnkdxUcg==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
|
||||
'@oxc-project/runtime@0.129.0':
|
||||
@@ -4287,7 +4287,7 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@openagentpack/sdk@0.7.0':
|
||||
'@openagentpack/sdk@0.7.1':
|
||||
dependencies:
|
||||
jszip: 3.10.1
|
||||
yaml: 2.9.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-cli
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-finetune
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-gen
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-managed-agent
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1181,38 +1181,35 @@ bl managed-agent playground --file agents.yaml --no-open
|
||||
|
||||
### `bl managed-agent project build`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent project build` |
|
||||
| **Description** | Organize directory source and generate the immutable Publish Build |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent project build [--project <directory>]` |
|
||||
| **Risk** | `high` |
|
||||
| **Risk message** | This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build. |
|
||||
|
||||
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------ |
|
||||
| **Name** | `managed-agent project build` |
|
||||
| **Description** | Organize directory source and generate the immutable Publish Build |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent project build [--project <directory>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------------- | ------ | -------- | --------------------------------------------------- |
|
||||
| `--project <directory>` | string | no | Directory project root (default: current directory) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Build writes local project files without confirmation, including inferred resource associations and migration of plaintext Vault secrets into .env. Use --dry-run to preview without writing. Publish still requires explicit confirmation before remote changes.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent project build
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent project build --dry-run
|
||||
```
|
||||
|
||||
```bash
|
||||
# Only after explicit user confirmation:
|
||||
bl managed-agent project build --yes
|
||||
```
|
||||
|
||||
```bash
|
||||
# Only after explicit user confirmation:
|
||||
bl managed-agent project build --project ./my-agent --yes
|
||||
bl managed-agent project build --project ./my-agent
|
||||
```
|
||||
|
||||
### `bl managed-agent project init`
|
||||
@@ -1226,12 +1223,13 @@ bl managed-agent project build --project ./my-agent --yes
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------------- | ------ | -------- | --------------------------------------------------- |
|
||||
| `--project <directory>` | string | no | Directory project root (default: current directory) |
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------------- | ------ | -------- | ----------------------------------------------------------------------------- |
|
||||
| `--project <directory>` | string | no | Directory project root (default: ./managed-agent under the current directory) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Without --project, creates a managed-agent/ subdirectory. Enter it before running other project commands. Use --project . to initialize in place or convert the current agents.yaml; existing project files are not overwritten.
|
||||
- New projects include Skill, File, Vault, and Environment examples under each resource directory's \_examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside \_examples/ to enable it, then configure its Agent reference.
|
||||
|
||||
#### Examples
|
||||
@@ -1244,6 +1242,10 @@ bl managed-agent project init
|
||||
bl managed-agent project init --project ./my-agent
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent project init --project .
|
||||
```
|
||||
|
||||
### `bl managed-agent project publish`
|
||||
|
||||
| Field | Value |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-protocol
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-sandbox
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-web-search
|
||||
metadata:
|
||||
version: "1.21.0"
|
||||
version: "1.22.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
Reference in New Issue
Block a user