- 增加新的鉴权域或 token 来源(env / config / flag / 文件)
- 调整 API Key / Console token 解析优先级
- 改
bl auth login/auth status/auth logout流程 - 改 runtime 对 command
auth的 gating 或 credential 注入
argv flags ─┐
env var ──┼─ buildSources(flags) ─┐
config ──┘ │
├─ buildSettings(sources) → ctx.settings
│
├─ resolveApiKey(sources) → model-domain Client
├─ resolveConsole(sources) → console-domain Client
└─ resolveOpenApi(sources) → OpenAPI Client
defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx)
当前 command 鉴权域(AuthRequirement):
apiKey— DashScope / OpenAI-compatible 模型域,用 API key 与 model base URLconsole— Bailian Console Gateway,用 console access token + region/site/switchAgent;workspace_id是独立的 Settings 作用域,不属于 credentialopenapi— 阿里云 OpenAPI 签名域,用 AccessKey ID/Secret 调用 Token Plan 等 OpenAPInone— 本地命令、登录/配置类命令、无需 credential 的命令
~/.bailian/config.json 可同时保存 api_key、access_token 与 access_key_*。登录任一种方式不得删除另一种:
bl auth login --api-key ...先通过只读GET /models校验 Key,校验成功后才原子写入api_key与实际匹配的base_url。显式--base-url只校验该站点,不自动改写;未显式指定时,普通sk-*/sk-ws-*按“当前 Profile 已保存的同类站点 → 基于workspace_id构造的各地域 Workspace 专属站点 → 公共地域”顺序选择首个成功结果,sk-sp-*并行探测国内站与新加坡 Token Plan,其他格式同时尝试两类候选。所有候选明确拒绝时返回AUTH;网络、超时、非 JSON 或 5xx 视为校验无法确定并透传原错误。任一失败都不落盘、不激活 Profile- 未显式传
--config时,sk-sp-*自动写入并激活token-plan;普通sk-*/sk-ws-*仅在当前为token-plan时改写并激活default,否则保留当前 Profile;其他格式不自动切换。显式--config始终优先。sk-sp-*无论写入哪个 Profile 都会携带 Token Plan 的预设默认模型,并在保留已有项的基础上补齐api_key_capabilities,不自动删除任何已有能力。登录仍不得删除其他鉴权域的凭证 bl auth login --console更新access_token以及回调携带的 console 作用域字段;若所选 Profile 没有模型 API Key,会要求控制台页面创建并回传一个普通 API Key 与 Base URL 一并保存。该回调结果不重复执行/models校验,且不会创建或配置 Token Planbl auth login --open-api ...调用 OpenAPI 生成 CLIaccess_token,通过目标 Console 网关的只读loginInfo校验后,才一并写入access_key_id/access_key_secret/access_token和实际console_region/console_site;失败不覆盖原凭证、不激活 Profile。一次成功登录同时产生openapi与console域凭证bl auth logout --console只清access_tokenbl auth logout --open-api只清access_key_id/access_key_secret/security_tokenbl auth logout清api_key+base_url+access_token+access_key_*
解析分工:
resolveApiKey()—auth: "apiKey"命令;优先级--api-key>DASHSCOPE_API_KEY> configapi_keyresolveModelBaseUrl()— model base URL;优先级--base-url>DASHSCOPE_BASE_URL> configbase_url>REGIONS.cn,返回前统一归一化为 URL origin(仅保留协议、host 和显式端口,去除 path、query、fragment)--config只选择 config 文件 block,不提升该 block 的字段优先级。对auth: "apiKey"命令,runtime 会先按叶子命令路径检查所选 Profile 的api_key_capabilities:--api-key/--base-url或DASHSCOPE_API_KEY/DASHSCOPE_BASE_URL任一显式连接覆盖存在时,完全跳过自动降级,继续走统一的 flag > env > selected config file > 默认值- 配置文件显式声明
api_key_capabilities后,命中能力时保留所选 Profile,未命中时仅把 file-backedapi_key/base_url来源切到顶层default,其他 Settings 仍来自所选 Profile - 字段缺失时不启用降级,包括命中内置套餐预设的 Profile;preset 只在 API Key 登录落盘成功后物化写入,升级 preset 需要重新登录
- fallback 反馈写 stderr:text 模式输出本地化句子,
--output json输出两空格缩进的多行warning对象;若后续鉴权失败,warning 与多行error对象以空行分隔,stdout 仍只保留命令结果
- 显式
auth login --config <name>在凭证落盘成功后自动激活目标 Profile;未传--config时继续写当前激活项,失败和 dry-run 不切换 resolveConsole()—auth: "console"命令;当前 token 来自 configaccess_token,region/site/switchAgent 来自 flag > config > 默认resolveOpenApi()—auth: "openapi"命令;优先级--access-key-id/--access-key-secret>ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET> configaccess_key_*。兼容读取旧字段openapi_access_key_*,新写入只写短字段describeAuthState()—auth status/ banner / telemetry 使用的只读快照
命令不要直接解析 token、env 或 config。业务请求统一走 ctx.client;登录/配置命令通过 ctx.authStore / ctx.configStore 的窄接口操作落盘。
- 浏览器回调只监听
127.0.0.1,校验随机state,只对本次控制台 Origin 返回 CORS 头;仅可信 Origin 明确请求 PNA 时返回Access-Control-Allow-Private-Network: true。错误响应也必须可被可信页面读取,不能返回通配 Origin。 - 登录地址始终输出;
--no-browser只禁止自动打开浏览器,仍等待本机回调。必须在打开浏览器前注册完成事件,浏览器启动失败或挂起不阻止回调完成。无 token 的回调不能视为成功;15 分钟未收到凭证返回TIMEOUT,保存失败返回错误,保存期间客户端断开也必须完成清理。 --console --token-stdin与--no-browser互斥,必须显式指定--console-region;只接受非 TTY stdin 的单个原始 token(最多 64 KiB),拒绝空输入、JSON、Bearer 前缀及多 token 输入。此分支不启动浏览器或回调服务,经目标网关loginInfo验证成功才保存 token/site/region 并激活指定 Profile;保留其他凭证,失败和 dry-run 不写入、不激活,dry-run 不读取 stdin。- PNA 响应头只是兼容措施,不能宣称绕过 Chrome LNA 权限或 Safari 混合内容限制。回归覆盖真实 loopback 请求、超时、浏览器启动竞态、stdin 失败不改配置及输出不泄漏 token。
resolveCLIAccessTokenTarget()优先采用有效consoleRegion;地域缺省时识别国内/新加坡 DashScope 和对应*.maas.aliyuncs.com域名(含 Token Plan、Workspace)。未知或不支持的地域报错,不静默回退北京;consoleSite与地域独立,不能把新加坡自动等同国际站。Client.console()收到NotLogined后,以本次实际 Console credential 的 region/site/switchAgent 签发并重试一次。只有原请求重试成功才保存新 token,并更新 Client 内存凭证;失败保留磁盘旧 token。refreshAccessToken()独立调用时默认用只读loginInfo验证;调用方提供validateToken时由其完成验证或原请求重试,回调成功后才落盘。缺少当前 Profile 的 AK/SK 返回null,不跨 Profile 借用凭证。auth generate-access-token只签发并输出,不做 Console 验证、不持久化;首次登录的校验直接走callConsoleGateway,不得递归进入刷新流程。
bl managed-agent * 按调用链分两层:
- 离线命令 —
init、validate、state list/show/rm:auth: "none",只读写本地文件,无需登录;引擎侧传credentials: "none"跳过凭证断言 - 联网命令 —
plan、apply、destroy、state import、skill-list、全部session *:统一声明auth: "apiKey"硬门禁,authStage 经resolveApiKey(sources)解析 Bailian 凭证(flag > env > active profile config),缺失报统一 AUTH;引擎层再断言 Bailian key 非空。例外:plan --no-refresh/plan --dry-run传credentials: "none"并强制refresh: false(不联网、不回写 state,不查 provider key),其中--dry-run连登录也不要求(authStage 的 dry-run 豁免),--no-refresh仍需登录。
bl managed-agent 是 Bailian-only 产品入口:命令不暴露 --provider,init 只生成 providers.bailian,所有远端调用固定传 provider: "bailian"。resolveAgentProjectConfig 在创建 SDK runtime 前通过 assertBailianOnlyProviders 拒绝包含非 Bailian Provider 的手写配置;共享 @openagentpack/sdk 仍可保留多 Provider 能力。
凭证不以真实值写入 process.env,而是经 packages/commands/src/commands/managed-agent/_engine/ 的内存注入管道(resolveAgentProjectConfig)注入 SDK,管道五步:
prepareProviderEnv()— 调用 SDK 的凭证 bootstrap,再把凭证类 env(CREDENTIAL_ENV_KEYS,含兼容别名)中仍为 undefined 的项占位为"",使 agents.yaml 插值阶段能够完成并由 CLI 输出明确的 Bailian-only 配置错误resolveProjectConfig— 完成插值;随后normalizeInterpolatedProviderBlocks()把插值为空导致的 YAMLnull归一为"",避免空 key 在 SDK zod 层提前报 "received null"injectProviderCredentials()— 用ctx.client.exportApiCredential()(lint 限定managed-agent/_engine/**可用)覆写内存 config 对象的 bailian 块:有凭证时api_key无条件覆写;base_url(拼/api/v1/agentstudio后缀,无凭证时用 client 默认域名补齐以满足 schema)/workspace_id(取settings.workspaceId)仅在引用且为空时填充scrubCredentialEnv()— 从process.env删除全部凭证变量(真实凭证此后只存于 config 对象 → provider adapter 实例内存,不驻留 env / 不被子进程继承)assertBailianOnlyProviders(providers)— 拒绝非 Bailian Provider;随后assertProviderCredentials(providers)在 Bailianapi_key为空时给出 CLI 权威AUTH错误和登录 hint;离线命令传credentials: "none"跳过 key 断言,但仍执行 Bailian-only 配置校验
禁止命令层直接 readConfigFile 裸读凭证;Bailian 字段以 CLI 鉴权链为唯一信源。SDK bootstrap 期间读取到的兼容凭证变量也会在配置解析后统一清扫。
managed-agent webhook 是工作空间 API,使用 auth: "apiKey",不加载 agents.yaml、项目 .env 或 State,也不走上述项目凭证 bootstrap。命令通过 managed-agent/_engine/** 的轻量 context,将 ctx.client.exportApiCredential()、解析后的 Base URL 与工作空间作用域仅在内存中交给 SDK,固定 provider: "bailian",并按 runtime 的 settings.timeout 构造请求 AbortSignal。不得用 YAML 临时文件拼接 Provider,也不得另建一条 HTTP 或凭证解析链。
--workspace-id由命令自声明,读取ctx.flags.workspaceId后回退ctx.settings.workspaceId;Base URL 和 API Key 复用 CLI 的 flags → env → Profile 优先级。- 所有 Webhook 写操作声明
risk: "high";--dry-run在创建 SDK context、读取项目文件或请求远端前返回,不要求 API Key。 create/reset-secret成功响应中的signing_secret保留在用户请求的结果中;verbose 诊断和错误输出必须脱敏,不得把成功响应的密钥复制到日志。服务端错误的普通 message 仍原样透传。
-
packages/core/src/types/command.ts:- 如新增鉴权域,扩展
AuthRequirement - 更新
credentialFlagDefs()暴露该域可见的 flag - 必要时新增
*_AUTH_FLAGS workspace_id是作用域字段而非 credential,不要把它放进ConsoleCredential;读取方式按命令auth域区分:auth: "console"命令通过CONSOLE_AUTH_FLAGS自动获得--workspace-id,由buildSettings()解析到settings.workspaceId,命令统一从settings.workspaceId读取auth: "apiKey"/"openapi"/"none"命令如需--workspace-id,必须自声明 flag;因它不会进入 credential/global flags,命令从ctx.flags.workspaceId读取(可回退到settings.workspaceId)
- 如新增鉴权域,扩展
-
packages/core/src/auth/types.ts:- 新增 credential 类型 / source / scope 字段
-
packages/core/src/auth/resolver.ts:- 新增或调整 resolver,保持优先级注释清晰
- 新增/调整 resolver hint 时保持产品无关,不要新增
bl/kscli硬编码;当前遗留的bl auth loginhint 如被触碰,迁到 runtimeenhanceHint
-
packages/core/src/auth/store.ts:- 如果新方式需要持久化,扩展
AuthStore/AuthPersistPatch
- 如果新方式需要持久化,扩展
-
packages/core/src/config/schema.ts:ConfigFile加 disk 字段(snake_case)Settings加运行时字段(如果命令需要读取)
-
packages/core/src/config/loader.ts:buildSources()/buildSettings()把 flag/env/file 读到正确层
-
packages/runtime/src/create-cli.ts:- parse flags 时纳入新的全局/凭证域 flag
globalFlags与ownFlags分流正确
-
packages/runtime/src/middleware.ts:authStage:- 根据
command.auth解析 credential 并注入ctx.client settings.dryRun下是否允许缺 credential 的策略明确
- 根据
-
packages/runtime/src/error-handler.ts:- AUTH hint 增强使用
binName,不要硬编码bl - URL 从
packages/runtime/src/urls.tsimport
- AUTH hint 增强使用
-
packages/commands/src/commands/auth/login.ts:- 新增/调整登录 flag 与流程
- 持久化只走
ctx.authStore.login(...)
-
packages/commands/src/commands/auth/status.ts:- 分别显示 model / console / openapi 鉴权状态,并 mask token
-
packages/commands/src/commands/auth/logout.ts:- 清理范围与双凭证并存规则一致
- 新的业务命令设置正确
auth:- 模型域请求 →
auth: "apiKey" - Console Gateway →
auth: "console" - 阿里云 OpenAPI 请求 →
auth: "openapi" - 本地/登录/配置 →
auth: "none"
- 模型域请求 →
-
README.md/README.zh.md"Authentication" 段落 - 各
skills/<skill>/reference/通过pnpm run sync:skill-assets重建
-
packages/commands/tests/e2e/auth.e2e.test.ts增加新方式的 happy / failure 路径;浏览器回调契约在packages/commands/tests/login-console.test.ts覆盖 - mask token 的输出格式不变(避免泄漏)
- 如调整 resolver 优先级,补 core/runtime 单测覆盖 flag > env > file
本仓库同时存在 bl(packages/cli) 与 kscli(packages/kscli) 两个入口,二者共享 core/runtime 鉴权链路,但暴露的命令不同。如果改动会影响两个入口共用的命令或错误提示,再分别验证它们各自实际暴露的路径;不要假设 kscli 也有 bl auth * 命令。
# 各种凭证组合
unset DASHSCOPE_API_KEY ALIBABA_CLOUD_ACCESS_KEY_ID ALIBABA_CLOUD_ACCESS_KEY_SECRET
HOME=/tmp/empty pnpm -F bailian-cli exec tsx src/main.ts auth status
# flag 注入(凭证域 flag 只在对应业务命令可见,auth status 不接收)
pnpm -F bailian-cli exec tsx src/main.ts text chat --message hi --api-key sk-xxx --dry-run
pnpm -F bailian-cli exec tsx src/main.ts token-plan list-seats --access-key-id ak-xxx --access-key-secret sec-xxx --dry-run
pnpm -F bailian-cli exec tsx src/main.ts auth login --open-api --access-key-id ak-xxx --access-key-secret sec-xxx --dry-run
# env 注入
DASHSCOPE_API_KEY=sk-xxx pnpm -F bailian-cli exec tsx src/main.ts auth status
ALIBABA_CLOUD_ACCESS_KEY_ID=ak-xxx ALIBABA_CLOUD_ACCESS_KEY_SECRET=sec-xxx pnpm -F bailian-cli exec tsx src/main.ts auth statusConsole 登录/网关相关改动:
pnpm -F bailian-cli exec tsx src/main.ts auth login --console
pnpm -F bailian-cli exec tsx src/main.ts usage stats --dry-run --output json --workspace-id ws-xxx注意:usage stats --dry-run 仍会先校验 workspace,必须传入 --workspace-id(或 BAILIAN_WORKSPACE_ID / config workspace_id)。
- ✗ 加了新 token 来源但忘了改 resolver 优先级,实际不生效
- ✗
ConfigFile/Settings加字段但parseConfigFile或buildSettings没读 - ✗
auth login写成功但auth status不识别(两边走的 storage path 不一致) - ✗ token mask 显示完整 token,日志泄漏
- ✗
auth: "console"命令误用apiKey域,config 只有 API key 时会把sk-...发到网关 - ✗ 新增 core resolver hint 时写死产品命令,导致
kscli等入口提示错误 - ✗ Windows 上用
cmd /c start打开带&的 URL 时没把整段目标括起来。auth login --console的needapikey=true会被 cmd 当成下一条命令,浏览器只拿到控制台会话。打开参数走packages/commands/src/commands/shared/local-server.ts的buildOpenInvocation(dsh 插件里有一份同步实现)