Metadata-Version: 2.4
Name: bkai-init
Version: 0.1.2rc29
Summary: Synchronize AIDEV agent packages through the application OpenAPI.
Author: Tencent BlueKing
License-Expression: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: Jinja2<4,>=3.1.6
Requires-Dist: PyYAML>=6.0
Requires-Dist: requests>=2.31
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"

# bkai-init

将模块维护的 Agent Package 同步到 AIDEV。资源操作只调用平台 app 应用态接口，不回退 private；未传调用用户名时，直接查询 BK User 解析当前租户的 bk_admin。

[协议与接入目录](docs/protocol.md) · [接入方案](docs/aidev-agent-cli-manifest-v1.html) · [主子智能体 Demo](demo/readme.md) · [支持范围](docs/protocol.md#支持范围)

静态规则和公共默认值统一维护在 [bkai_init/settings.py](bkai_init/settings.py)，包括变量白名单、code 命名、协议字段与范围校验。内部代码统一通过 `from bkai_init import settings` 获取；凭据仍由调用参数提供，不写入 settings。

包顶层公开 `BkaiInit`、四类异常（`BkaiCliError`、`APIError`、`ConfigurationError`、`ManifestError`）及 `__version__`。服务类、返回类型从 `bkai_init.services` 导入；`SyncClient` 从 `services.sync`，`InspectionClient` / `inspection_as_dict` 从 `services.inspection` 导入。

## pip 安装

在发布相应版本的包仓库安装：

```bash
python -m pip install "bkai-init==0.1.2"
bkai-init --help
```

使用内部包源时配置 `PIP_INDEX_URL`。此处版本仅作示例，需先确认目标包源已有该版本；本地构建不等于发布。

## 快速生成模板

```bash
# 默认单智能体；目标必须是新目录，无需凭据或 --space
bkai-init init ./bk-job --agent-code ai-job --name "作业助手"

# 按需生成并关联 Skill、知识库和带快捷指令的子智能体
bkai-init init ./bk-job-full --agent-code ai-job --name "作业助手" \
  --with-skill job_skill --with-knowledgebase job_docs --with-subagent ai-job-child

bkai-init validate -f ./bk-job/bkai.yaml --space system-bkaidev
```

不指定 code/name 时默认 ai-demo / 初始化助手。目录已存在（包括空目录、符号链接）直接报错，不覆盖；不调用平台、不加载凭据、不发布。写入前使用现有完整协议校验。写入异常可能留下部分文件，检查后改用新目录重试。

默认生成 bkai.yaml 和 agents/main.yaml；可选依赖同时加入资源清单及主智能体引用。知识库只生成 knowledgebase.yaml，不放示例 Markdown，避免误触发镜像删除；添加实际文档后同步前须确认删除范围。Skill 生成 skill.yaml / SKILL.md，自定义镜像按[协议](docs/protocol.md#文件白名单与变量替换)另行补充。MCP/角色不由 init 生成，按需手工配置。

Python 同样不需要凭据：

```python
from bkai_init import BkaiInit

manifest = BkaiInit.init("./bk-job", agent_code="ai-job", name="作业助手",
                        skill_code="job_skill", knowledgebase_code="job_docs",
                        subagent_code="ai-job-child")
BkaiInit(space="system-bkaidev").validate(manifest)
```

init 只是生成模板，不是完整业务实现；按用途完善 Prompt 和 Skill。新建主子智能体的同步仍遵循下文子智能体发布约束。

`model.context_window` 是会话轮次，只接受 1～30 的整数，默认模板填写 16；不是模型上下文 token 数，不能填写 256。所有资源在远程请求前校验，非法值不会自动截断。

开发提交前在包目录运行 `make check`，执行全量本地测试（包含模板、Demo 与字段边界校验），不加载环境凭据或同步线上资源。安装仓库提交钩子后，修改 bkai-init 文件时会自动执行此检查，包含 settings.py。

需要在初始化时开放给所有人使用，在对应 Agent YAML 的 `spec` 中添加 `user_scope: public`。省略时保留平台现有范围，`custom` 表示指定人可使用；主子智能体分别配置。通过 plan 预览后再执行 sync，权限修改对已有发布版本也生效。目标平台需支持使用范围的写入和回读，详见[人员使用范围](docs/protocol.md#智能体人员使用范围)。

## CLI 调用

先按[协议](docs/protocol.md)准备 `bkai.yaml` 及资源。CLI 不自动加载 env 文件。Demo 把连接配置放在 `demo/.env`（从 [demo/.env.example](demo/.env.example) 复制，不要提交）：

```bash
cp demo/.env.example demo/.env
set -a
source demo/.env
set +a

# 仅本地校验，不需要凭据
bkai-init validate -f /bk-job/bkai.yaml --space system-bkaidev

# 查看、对比线上配置（不写入）
bkai-init show -f /bk-job/bkai.yaml --tenant-id system --space system-bkaidev --format json
bkai-init diff -f /bk-job/bkai.yaml --tenant-id system --space system-bkaidev

# 同步会创建或更新资源
bkai-init sync -f /bk-job/bkai.yaml --tenant-id system --space system-bkaidev --confirm
```

| 参数 / 环境变量 | 说明 |
| --- | --- |
| `--base-url` / `BKAI_BASE_URL` | 可选覆盖项。未设置时使用 `BK_API_URL_TMPL`，将 `{api_name}` 替换为 `bk-aidev`；支持服务根地址、网关 stage 或完整 `/openapi/aidev/app/v1` 前缀 |
| `--app-code` / `BKAI_APP_CODE` | 必需。蓝鲸应用编码，未设置时使用 `BKPAAS_APP_ID` |
| `--app-secret` / `BKAI_APP_SECRET` | 必需。应用密钥，未设置时使用 `BKPAAS_APP_SECRET` |
| `--access-token` / `BKAI_ACCESS_TOKEN` | 可选，兼容 `ACCESS_TOKEN`；token 不替代应用空间授权 |
| `--username`/ `BKAI_USERNAME` | 所有输入均按 login_name 查询转换为实际 bk_username；未传时默认 bk_admin |
| `--bk-user-base-url` / `BK_USER_BASE_URL` | BK User 可选优先覆盖地址，包含 prod，如 `https://gateway.example/api/bk-user/prod`；CLI 未传时由 `BK_API_URL_TMPL` 的 api_name=bk-user、stage=prod 推导 |
| `--tenant-id` | 默认 `system`，只通过参数传入 |
| `--space` | 默认 `bkaidev`，显式传值时不可为空；多租户传完整空间 ID，如 `system-bkaidev`，不会自动拼接租户前缀。所有资源统一使用此空间，旧 `metadata.space` 不再生效 |
| `--timeout` | 单次请求超时，默认 60 秒 |
| `--var KEY=VALUE` | 可重复；仅替换协议 YAML 与已声明 Skill 根目录 Dockerfile，详见[变量规则](docs/protocol.md#文件白名单与变量替换) |

CLI 不自动读取 `BKAI_SPACE_ID`；需要时显式传 `--space "$BKAI_SPACE_ID"`。不在命令示例、镜像或 Git 中保存真实凭据。

以上三项逐项按 **命令行参数 > BKAI 环境变量 > PaaS 内置环境变量** 取值，可混合来源。空的 BKAI 环境变量视为未设置；命令行显式传空值会报缺参，不回退。`BK_API_URL_TMPL` 例如 `https://gateway.example/api/{api_name}/`，同时生成 `https://gateway.example/api/bk-aidev/prod` 和 `https://gateway.example/api/bk-user/prod`。`{stage}` 默认替换为 `prod`；模板没有 stage 时默认补 `prod`，已有明确 stage 路径则保留。Python 类仍显式接收调用参数，不自动加载环境变量。

PaaS 已注入 `BK_API_URL_TMPL`、`BKPAAS_APP_ID`、`BKPAAS_APP_SECRET` 时无需重复配置 BKAI 同名用途变量。容器或 Helm Job 需确保这三个变量实际注入进程；CLI 不会获取宿主机或其它容器的环境变量。

Python `BkaiInit()` 同样默认使用 `bkaidev`；多租户部署使用 `BkaiInit(space="system-bkaidev")`。下列显式传入 `system-bkaidev` 的示例适用于多租户部署。

### 传入部署变量

Demo Skill 的 Dockerfile 使用短镜像名 `bkdbm-aidev-skills-env:0.0.1-alpha.13`。平台构建时按 `SKILL_SANDBOX_BASE_IMAGE_PREFIX`（对齐 Helm `global.imageRegistry`）补仓库前缀；Helm Job 不必传 registry 变量。需要覆盖完整地址时，再在已有 Dockerfile 中使用变量：

在 Skill 根目录 Dockerfile 中引用基础镜像：

```dockerfile
FROM {{ SKILL_BASE_IMAGE }}
```

```bash
bkai-init sync -f /bk-job/bkai.yaml --space system-bkaidev --confirm \
  --var SKILL_BASE_IMAGE=registry.example.com/team/skill:1.0
```

Python 调用同样支持：

```python
from bkai_init import BkaiInit

initializer = BkaiInit(
    space="system-bkaidev",
    variables={"SKILL_BASE_IMAGE": "registry.example.com/team/skill:1.0"},
)
initializer.validate("/bk-job/bkai.yaml")
# 远程操作另需传入 base_url、app_code、app_secret，见下文。
```

也可以直接在变量引用处配置默认值（标准 Jinja 语法）：

```dockerfile
FROM {{ SKILL_BASE_IMAGE | default("registry.example.com/team/skill:1.0") }}
```

未传该变量时使用默认值；CLI `--var` 或 Python `variables` 显式传值时优先使用传入值，包括空字符串。YAML 示例：`name: "{{ SKILL_NAME | default('demo') }}"`。

仅允许简单变量及 `default("字符串")`，不支持第二个参数、其它过滤器或模板功能，替换结果不再次渲染。不改源文件，不处理 Markdown、脚本或嵌套 Dockerfile。不提供独立的 Skill 镜像字段。

### 查看指定资源

```bash
# 单个资源
bkai-init show -f /bk-job/bkai.yaml --resource agent/ai-job-assist --space system-bkaidev

# 多个资源，也可重复传入 --resource
bkai-init show -f /bk-job/bkai.yaml --space system-bkaidev \
  --resource skill/job_skill knowledgebase/job_docs --format json
```

资源必须在 Package 清单中；省略 `--resource` 查看全包，指定后只请求并输出所选资源，不额外查询其依赖。未知编码或空集合会在请求前报错，全包本地格式校验仍保留。无论选择哪些资源，均使用命令指定的空间。

`show` 不修改资源，不提供权限查询，也不支持 `--exclude-resource`。`diff` 同样支持 `--resource`，资源选择规则与 show 一致。

### 同步预览与 CI 检查

```bash
bkai-init plan -f /bk-job/bkai.yaml --resource agent/ai-job-assist --format json --space system-bkaidev
bkai-init sync -f /bk-job/bkai.yaml --space system-bkaidev
bkai-init diff -f /bk-job/bkai.yaml --resource agent/ai-job-assist --check --space system-bkaidev
```

`plan` / 不带 `--confirm` 的 `sync` 只读取线上配置，不上传、创建、更新或发布。输出实际租户、资源空间、create/update/skip、差异、依赖来源、执行顺序和阻塞原因；支持资源选择、黑名单和发布参数。未选或排除的资源不会同步，其依赖需在线上存在。`ready` 仅表示已执行的检查未发现阻塞，不保证写权限、全部服务端校验或执行成功；已有资源即使无可见差异仍显示 update，与 sync 的行为一致。确认执行时增加 `--confirm`；不再提供 `--dry-run`。

`diff --check`：0 无差异，1 执行错误，2 有差异，3 无法完整比较。不可比较优先于有差异；不加 `--check` 保持原行为。Skill 文件摘要、envs 值、角色标签等未回显内容明确标记未知，不将其视为一致。

### 同步并发布智能体

首次远程操作前，所有输入用户名均按 login_name 查询转换；未传时默认 bk_admin。使用应用凭据、目标租户调用 BK User：`GET /api/v3/open/tenant/virtual-users/-/lookup/?lookups=bk_admin&lookup_field=login_name`，将返回的 `bk_username` 传给 AIDEV。查询失败、未找到或匹配不唯一时停止，不降级到应用 code 或其它身份。调用应用需获准访问该接口；不需要 access token。`init` / `validate` 不查询用户。Python 不隐式读取环境变量，可传 `username="operator"`，或传 `bk_user_base_url="https://gateway.example/api/bk-user/prod"` 自动查询。

Agent YAML 可配置 `spec.admins: ["user_a", "user_b"]`，成员填写目标租户的 `login_name`，不直接填写 `bk_username`。bkai-init 在任何资源写入前分别查询 BK User 普通人员和虚拟用户，将唯一匹配的登录名转换成 `bk_username`；调用应用须有两个 lookup 接口权限。查询失败、未找到、同名歧义或用户已禁用/过期时停止，不回退透传登录名。每项最长 64 字符，不含逗号、空白或控制字符；映射后去重，只追加开发者中心管理员、保留已有成员。省略、null、[] 时跳过，不继承主智能体名单。只在 `sync --confirm` 写入，plan/diff 仅显示登录名和追加意图，不校验用户映射，也不将无法回读的管理员名单判断为一致。平台需支持管理员追加接口，失败则停止后续同步与发布；已成功的配置和权限不自动回滚。

开启 `--publish` 后，`--publish_config_only=0`（默认）显式请求发布到开发者中心，`1` 只发布配置。平台仍可按代码版本一致策略跳过重新部署。bkai-init 只调用发布接口，不轮询或等待部署完成；pending/running 标记为已提交，successful 按接口结果记录发布完成。若子智能体仍在发布，停止同步引用它的主智能体，即使子智能体已有旧发布版本；请在平台确认新版本完成后再同步主智能体。发布请求失败不自动重试。

```bash
# 显式开启发布；默认常规发布，先子后主
bkai-init sync -f /bk-job/bkai.yaml --confirm --publish --space system-bkaidev

# 只预览包括发布的计划，不执行
bkai-init plan -f /bk-job/bkai.yaml --publish --space system-bkaidev

# 如需仅发布配置，显式指定 1
bkai-init sync -f /bk-job/bkai.yaml --confirm --publish --publish_config_only=1 --space system-bkaidev
```

Skill 未在 `skill.yaml` 或 `SKILL.md` 中指定版本时，由平台比较 ZIP hash：变化时在已有最高版本的补丁号上加 1，未变化则复用版本覆盖更新；首次创建为 `0.0.1`。指定版本则覆盖该版本。Package 版本不再作为 Skill 版本兜底。

确认执行后，未指定 `--publish` 时仅同步草稿。Agent 不接受配置版本；发布请求不传 version，由平台分配，计划中显示 `auto`。脚本检查发布响应的状态和版本，不回读或等待部署完成；不覆盖已发布版本，发布请求失败时停止。

主智能体仍只能引用已发布的子智能体和该版本中的指令。开启发布后，所选子智能体先同步并提交发布请求；返回 pending/running 时停止同步引用它的主智能体，避免读取旧发布快照。确认子智能体完成后，再次同步主智能体，从当前发布版本展开指令引用。未选或排除的子智能体不会发布。发布请求失败、状态异常或缺少版本时停止，不自动回滚或重试。

### 指定同步资源

```bash
# 一个或多个资源；--resource 可重复传入
bkai-init sync -f /bk-job/bkai.yaml --space system-bkaidev --confirm \
  --resource skill/job_skill knowledgebase/job_docs

# 跳过手工修改的资源，避免覆盖
bkai-init sync -f /bk-job/bkai.yaml --space system-bkaidev --confirm \
  --exclude-resource agent/ai-job-assist
```

类型使用小写 `kind/code`。省略 `--resource` 表示全包同步，黑名单优先；未知资源或 Python 空集合会报错。不自动同步未选中的依赖，只选 Agent 时依赖需已存在。

**同步前先查看 plan / diff。** Agent 更新是整配置覆盖，失败不保证回滚。知识库目录含 Markdown 时会同步 ZIP，并移除线上目标目录中不在本地的内容。完整边界见[协议说明](docs/protocol.md#知识库文档同步)。

APIGW MCP 查询始终携带 YAML 中的 `agent_code`。平台 app by-code 接口允许智能体尚未创建时按 APIGW 数据解析；智能体已存在时仍校验其空间授权及与显式空间的一致性。平台需已支持这一初始化规则。公开和非公开 MCP 都需提前完成相应网关授权，查询成功不代表目标智能体已获运行权限；`plan` 不提前创建智能体，不自动申请或授予权限。

### 同步知识库文档

```bash
bkai-init sync -f /bk-job/bkai.yaml --tenant-id system --space system-bkaidev \
  --resource knowledgebase/job_docs --confirm
```

目录内的 Markdown 和图片按原层级打包，不替换正文变量、不包含根目录 `knowledgebase.yaml`。无文档时仅更新知识库配置，不清空线上目录。

流程：app `upload/url` 获取临时授权 → PUT 直传 BKRepo → app `upload/status` 校验大小及 SHA256 → app `knowledges/archive/import` 提交。提交成功后继续同步后续资源，不等待导入任务完成。上传失败时停止，不回退 private 或网关文件上传。

申请临时地址只发送空间、模块和文件名，不发送 `file_size`、`sha256`。上传后仍使用本地大小和摘要对比平台返回的实际元数据，确认一致后才提交导入。

上传连接不携带应用凭据，不输出签名 URL。上传超时会先核对上传状态，不自动重复 PUT；导入接口若直接返回失败或部分成功则命令报错，不自动取消或重提，应先到平台确认。`show` 仍仅查看配置；`diff` 不比较文档内容，会标记无法完整比较。

## Python 调用

安装同一 pip 包后，统一从 `bkai_init` 导入：

```python
import logging
import os
import sys

from bkai_init import BkaiInit, BkaiCliError

logging.basicConfig(level=logging.INFO, stream=sys.stdout)

# 无凭据也可进行本地校验
BkaiInit().validate("bkai.yaml")

initializer = BkaiInit(
    base_url=os.environ["BKAI_BASE_URL"],
    app_code=os.environ["BKAI_APP_CODE"],
    app_secret=os.environ["BKAI_APP_SECRET"],
    access_token=os.environ.get("BKAI_ACCESS_TOKEN") or os.environ.get("ACCESS_TOKEN"),
    tenant_id="system",
    space="system-bkaidev",
)

try:
    online = initializer.show("bkai.yaml", resources={"agent/ai-job-assist"})
    differences = initializer.diff("bkai.yaml", resources={"agent/ai-job-assist"})
    preview = initializer.plan("bkai.yaml", publish=True, publish_config_only=False)
    # 显式选择要写入的资源；省略 resources 表示全包同步
    report = initializer.sync(
        "bkai.yaml",
        resources={"skill/job_skill", "knowledgebase/job_docs"},
        excludes={"agent/ai-job-assist"},
    )
    # 完整同步并发布主子智能体时，显式传 publish=True（默认常规发布）
    # report = initializer.sync("bkai.yaml", publish=True, publish_config_only=False)
except BkaiCliError as exc:
    logging.error("初始化失败：%s", exc)
    raise
```

返回值分别为 `PackageInspection`、`tuple[ResourceDiff, ...]`、`PackagePlan` 和 `SyncReport`，均可从包级导入。同步资源必须与自己的清单编码一致。Python 的 `sync()` 本身就是显式执行，不需要 confirm 参数；只读预览调用 `plan()`。

过程日志统一使用 logging。CLI 默认输出 bkai.yaml 事项进度、业务步骤及结束汇总；接口正常调用只显示一行方法和 URL，不显示输入、输出、状态码或处理结果。失败时保留缩进、格式化并脱敏的接口错误详情和反馈摘要。接口日志仅在应用显式启用 DEBUG 时输出到 stderr；应用密钥、令牌和签名 URL 参数脱敏，文件仅输出元信息，不输出认证请求头或文件内容。类调用由应用配置日志，不修改 root logger。show 的 JSON/YAML 和 diff 的结果不带日志前缀；CLI 执行错误返回 1 并写入 stdout，参数错误由 argparse 输出到 stderr。

## Dockerfile 与镜像调用

[基础 Dockerfile](Dockerfile) 不复制源码，只通过 pip 安装指定版本。交付顺序为：**构建包 → 发布包 → 构建基础镜像 → 构建模块镜像**。

在本目录执行：

```bash
make build
make publish                  # 显式发布到配置的包仓库
make image PACKAGE_VERSION=0.1.2 IMAGE=bkai-init:local

# 模块镜像继承基础镜像，只增加初始化目录
podman build -f demo/Dockerfile \
  --build-arg BKAI_INIT_IMAGE=bkai-init:local \
  -t bkai-init-demo:local demo
```

基础镜像支持 `PYTHON_BASE_IMAGE`、`BKAI_INIT_PACKAGE`、`BKAI_INIT_VERSION` 和 pip 包源构建参数；可通过 `make image IMAGE_BUILD_ARGS="..."` 传入。不要通过构建参数传真实包源凭据。

[模块 Dockerfile](demo/Dockerfile) 将清单复制到 `/app/package`，工作目录是 `/app`。已有包源版本时，`make demo-test PACKAGE_VERSION=0.1.2 SPACE=system-bkaidev` 可构建镜像并仅执行 validate。镜像不写入 `.env`；运行时把它挂到 `/app/.env`，并用 `--env-file` 注入进程。

```bash
# 先本地验证，不访问平台
podman run --rm bkai-init-demo:local validate -f /app/package/bkai.yaml --space system-bkaidev

# demo/.env 已填写时，默认命令即 sync -f /app/package/bkai.yaml
podman run --rm --env-file demo/.env bkai-init-demo:local
```

模块镜像已包含 `/bk-dbm/bkai.yaml` 时，完整初始化并只发布配置：

```bash
podman run --rm \
  bk-dbm:local sync -f /bk-dbm/bkai.yaml \
  --tenant-id system --space "$BKAI_SPACE_ID" \
  --base-url "$BKAI_BASE_URL" --app-code "$BKAI_APP_CODE" \
  --app-secret "$BKAI_APP_SECRET" \
  --bk-user-base-url "$BK_USER_BASE_URL" \
  --confirm --publish --publish_config_only=1
```

容器调用不用 `-e`；按需追加 `--access-token "$BKAI_ACCESS_TOKEN"` 或 `--username "$BKAI_USERNAME"`。真实密钥不要写进示例或文件；参数可能出现在进程及容器检查信息中，不开启 shell 命令跟踪或记录展开后的命令。CLI 的环境变量回退能力仍保留。

## Helm 接入

模块镜像需包含 `bkai-init` 和清单目录，并推送到集群可访问的镜像仓库。将[Job 模板](demo/helm/templates/bkai-init-job.yaml)合入业务 Chart，通过 values 控制：

```yaml
image:
  repository: example.invalid/modules/bk-job
  tag: "1.0.0"
  pullPolicy: IfNotPresent

bkai:
  enabled: true
  tenantId: system
  space: system-bkaidev
  packagePath: /app/package/bkai.yaml
  excludeResources: []
```

- `enabled: false` 不生成 Job；启用时在 `post-install`、`post-upgrade` 执行 `sync --confirm`。模板固定携带确认参数，避免只预览而未同步。
- Job 复用模块的 `image`；`packagePath` 为镜像内路径，`excludeResources` 映射为多个 `--exclude-resource`。
- 业务 Chart 必须将现有环境变量注入逻辑接入 Job，提供 `BKAI_BASE_URL`、`BKAI_APP_CODE`、`BKAI_APP_SECRET`，或对应 PaaS 内置变量 `BK_API_URL_TMPL`、`BKPAAS_APP_ID`、`BKPAAS_APP_SECRET`，按需传 token。示例模板未内置这部分逻辑，不增加 `credentialsSecretName` 开关。
- 当前示例 Chart 执行全包同步但不发布；首次主子初始化时需由业务 Chart 显式给 Job 添加 `--publish`，默认常规发布。示例不是部署成功证明。
- 所有用户名都需要转换，需向 Job 注入 `BK_USER_BASE_URL` 或 `BK_API_URL_TMPL`，用于查询租户初始化身份。

```bash
helm lint demo/helm
helm template bkai-init-demo demo/helm
```

以上只检查或渲染模板，不部署到集群。独立 Job 参考 [k8s-job.yaml](demo/k8s-job.yaml)，其中 Secret 仅是向容器注入环境变量的一种示例，需按部署环境调整。

## 本地开发与验证

```bash
make init
make test                     # 单元测试，不调用线上接口
make clean                    # 清理虚拟环境与构建产物

cd demo                       # 复制给使用方后，在该目录执行
cp .env.example .env          # 填写连接配置；不提交
make build                    # 基于 bkai-init 镜像构建本地镜像
make exec                     # 用本地镜像启动容器并进入
make validate SPACE=system-bkaidev
make show RESOURCES="agent/ai-bkai-demo"
make diff
make diff RESOURCES="agent/ai-bkai-demo" CHECK=true
make plan PUBLISH=true
make sync \
  RESOURCES="skill/bkai_init_demo_skill knowledgebase/bkai_init_demo_docs"
```

Demo 入口在 [demo/Makefile](demo/Makefile)。`make build` 以 `BKAI_INIT_IMAGE`（默认 `bkai-init:local`）为底构建本地镜像，容器内 `/app` 包含 `package/`，运行时再挂上 `.env`；`make exec` 用该镜像启动容器并进入 shell；`make sync` 读取同目录 `.env`，在镜像中执行 `bkai-init sync --confirm -f /app/package/bkai.yaml`。参数：`PACKAGE_IN_IMAGE` 默认 `/app/package/bkai.yaml`，`BKAI_TENANT_ID` 未设置时租户为 system，`SPACE` 优先于 env 中的 `BKAI_SPACE_ID`，两者均未设置时使用 CLI 默认空间 bkaidev；多租户部署需设置完整空间 ID（如 system-bkaidev）；`RESOURCES` 用于 show/diff/plan/sync，`EXCLUDE_RESOURCES` 用于 plan/sync；`CHECK=true` 开启 diff 检查，Make 会将非零子命令退出码包装为自己的失败退出码。plan/sync 可传 `PUBLISH=true` 和 `PUBLISH_CONFIG_ONLY=0`，后者只接受 1 / 0、默认 0，转为 CLI 的 `--publish_config_only=1` / `0`；默认不发布，发布时默认常规发布。`show`、`diff`、`plan`、`sync` 加载 `ENV_FILE`，默认是同目录 `.env`。`make sync` 会写入平台；预览使用 `make plan`。

查询请求（GET、知识上传状态查询及 BK User 用户查询）遇到网络连接异常、超时、HTTP 429 或 5xx 时，依次等待 1、2、4 秒重试，最多请求 4 次；写入请求、其他 HTTP 错误及业务失败不自动重试。

同步按资源事项展示配置相对路径和当前步骤；结束汇总完成、已提交、失败、跳过和未执行数量。知识 ZIP 异步导入仅标记已提交，不表示后台导入完成。失败时输出事项状态列表、失败资源/配置/步骤、脱敏原因和处理建议，以及开始时间、CLI 版本、当前事项重试次数与服务端返回的请求标识（若有）；已完成操作不会自动回滚。发布开启时，pending/running 仅计为已提交，不表示部署完成。

默认同步输出不带 INFO 前缀，正常接口调用只显示缩进的方法和 URL，不生成额外步骤或日志块。Skill 打包和上传确认后显示完成标记；失败接口地址、HTTP 状态及脱敏错误内容缩进显示在当前步骤下，随后输出失败标记和停止摘要，CLI 不重复打印同一错误详情。

`plan` 与未带 `--confirm` 的 `sync` 默认采用相同的资源事项展示，输出检查步骤、计划动作、跳过原因和预检汇总，明确未执行同步或发布；阻塞项提供同样的失败详情与反馈摘要。显式 `plan --format json` 或 `plan --format yaml` 保留纯结构化输出。

MCP 按 code 查询成功时，默认只输出 `【正在查询 MCP】<mcp_code> agent_code: <agent_code> 成功` 一行（未指定 agent_code 时显示 `-`），不另列 URL、参数或查询步骤；失败时保留完整排查详情。
