Skip to content

Commit ece0c8d

Browse files
committed
feat(config): activate explicit profile after successful login
1 parent 052960e commit ece0c8d

8 files changed

Lines changed: 71 additions & 18 deletions

File tree

docs/agents/auth-change.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
4545
- `resolveApiKey()``auth: "apiKey"` 命令;优先级 `--api-key` > `DASHSCOPE_API_KEY` > config `api_key`
4646
- `resolveModelBaseUrl()` — model base URL;优先级 `--base-url` > `DASHSCOPE_BASE_URL` > config `base_url` > `REGIONS.cn`,返回前统一去除 query、fragment、尾斜杠和已知 SDK/API Base 后缀,同时保留自定义网关前缀
4747
- `--config` 只选择 config 文件 block,不提升该 block 的字段优先级;内置套餐 Profile(当前为 `token-plan`)的预设仅在登录时物化写入,运行时继续走统一的 flag > env > selected config file > 默认值
48+
- 显式 `auth login --config <name>` 在凭证验证并落盘成功后自动激活目标 Profile;未传
49+
`--config` 时继续写当前激活项,失败和 dry-run 不切换
4850
- `resolveConsole()``auth: "console"` 命令;当前 token 来自 config `access_token`,region/site/switchAgent 来自 flag > config > 默认
4951
- `resolveOpenApi()``auth: "openapi"` 命令;优先级 `--access-key-id/--access-key-secret` > `ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET` > config `access_key_*`。兼容读取旧字段 `openapi_access_key_*`,新写入只写短字段
5052
- `describeAuthState()``auth status` / banner / telemetry 使用的只读快照

docs/agents/config-profile-change.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@
1717

1818
- 解析阶段用局部变量保留“是否显式传入 `--config`”的信息;完成 Config 选择后不进入 `Settings`
1919
- `--config default` 必须显式选择顶层配置并绕过命名激活项。
20-
- `--config``auth login --config ...` 不得隐式修改持久化激活状态。
20+
- 普通命令的显式 `--config` 只覆盖本次选择,不修改持久化激活状态;例外是
21+
`auth login --config ...`,凭证验证并落盘成功后自动激活该 Profile。
2122
- 激活状态只选择配置 block,不改变字段优先级;字段仍为 flag > env > selected config > 默认值。
2223
- Pipeline 等进程内调用链也要复用统一的 `buildSources()`,避免绕过激活状态。
2324
- Console access token 自动刷新等后台读写必须携带 `settings.configName`,不得直接读写顶层 default。
2425

2526
## 3. 保持读写命令交互一致
2627

2728
- `auth login``config set` 等写命令未传 `--config` 时修改当前激活项。
28-
- 写命令显式指定不存在的 `--config <name>` 时,仅在业务操作成功并实际落盘时创建 Profile。
29+
- `auth login --config <name>` 显式指定不存在的 Profile 时,仅在凭证验证成功并实际落盘时
30+
创建和激活;`config set --config <name>` 可创建但不自动激活。
2931
- `config show``auth status` 和业务消费等读命令不得因为显式指定不存在的名称而创建 Profile。
3032
- `auth logout` 默认只清理当前激活项;显式 `--config` 只清理指定项。
3133
- 按凭证域退出时必须清理该域的完整字段集合,例如 OpenAPI 同时清理 AK、SK 和 STS `security_token`
@@ -56,6 +58,8 @@
5658
- 悬空 `active_config` 明确失败。
5759
- 删除激活 Profile 后切回 `default`
5860
- 登录、退出、`config set` 分别覆盖“当前激活项”和“显式不存在名称成功后创建”。
61+
- 显式 `auth login --config <name>` 成功后激活该 Profile,失败或 dry-run 不创建、不切换;
62+
`--config default` 成功后切回 `default`
5963
- Console token 自动刷新不从其他 Profile 借用 AK/SK,也不把新 token 写入其他 Profile。
6064
- `config list/show/use/ui``auth status` 和依赖默认模型的消费命令覆盖对应 E2E。
6165
- `config ui` 覆盖保存时保留未管理字段,并继续允许空值清除 UI 管理字段。

docs/token-plan-profile-integration.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ CLI 应解析并保存以下配置:
7272

7373
```json
7474
{
75+
"active_config": "token-plan",
7576
"token-plan": {
7677
"api_key": "<TOKEN_PLAN_API_KEY>",
7778
"base_url": "https://token-plan.cn-beijing.maas.aliyuncs.com",
@@ -81,6 +82,9 @@ CLI 应解析并保存以下配置:
8182
}
8283
```
8384

85+
凭证验证和配置落盘成功后,CLI 在同一次配置文件写入中将 `token-plan` 设为激活项;验证失败和
86+
dry-run 不创建、不切换 Profile。
87+
8488
用户仍可显式覆盖 Base URL,用于代理、测试或未来新增地域:
8589

8690
```sh
@@ -109,7 +113,7 @@ bl image generate --config token-plan --prompt "一只猫"
109113

110114
### 3. 激活 Config
111115

112-
新增命令
116+
登录时显式选择的 Profile 会自动激活;之后也可以主动切换
113117

114118
```sh
115119
bl config use --name token-plan
@@ -225,7 +229,8 @@ Config 激活只改变配置文件 block 的选择,`--config` 本身不提升
225229
- 配置文件中的 `active_config` 指向不存在的 Profile:命令失败并提示切回 `default`,不得静默使用其他凭证。
226230
- 删除当前激活的 Profile:删除操作同时切回 `default`,或者要求用户先切换;不能保留悬空引用。
227231
- `config use --name token-plan` 只切换状态,不创建 Profile,也不执行登录。
228-
- `auth login --config token-plan` 只写入指定 Profile,不自动激活,避免登录命令产生隐藏的全局状态变化。
232+
- `auth login --config token-plan` 在凭证验证并落盘成功后自动激活该 Profile;验证失败和
233+
dry-run 不创建、不切换。
229234

230235
## `token-plan` 内置 Profile 预设
231236

@@ -474,7 +479,10 @@ feat(config): add active profile selection
474479
- 验证临时 `--config default` 不改变激活状态。
475480
- 更新命令导出、`packages/cli/src/commands.ts`、E2E 和生成 reference。
476481

477-
实现选择:删除当前激活的命名 Profile 时,在同一次配置文件写入中将 `active_config` 重置为 `default``auth login --config <name>` 和所有显式 `--config` 仍只作用于本次命令,不修改激活状态。
482+
实现选择:删除当前激活的命名 Profile 时,在同一次配置文件写入中将 `active_config` 重置为
483+
`default`。普通命令的显式 `--config` 仍只作用于本次命令;`auth login --config <name>`
484+
例外,在凭证验证和落盘成功的同一次配置写入中激活目标 Profile。`--config default` 登录成功后
485+
切回默认配置。
478486

479487
相关写入交互统一为:`auth login``auth logout``config set` 未传 `--config` 时作用于当前激活项;显式指定名称时作用于该名称。写命令可在成功落盘时创建不存在的 Profile,读命令不创建。Console access token 自动刷新同样限定在当前选中的 Profile,不得回退读写顶层 default。
480488

@@ -552,4 +560,6 @@ Token Plan 模型消费最终表现为一个可激活的内置 Profile:
552560
-> 文本/图片 endpoint
553561
```
554562

555-
用户既可以通过 `--config token-plan` 单次使用,也可以通过 `bl config use --name token-plan` 将其设为默认激活配置。整个过程不引入 Token Plan 模式,也不复制现有模型调用实现。
563+
用户执行 `auth login --config token-plan` 成功后,该 Profile 会成为默认激活配置;仍可通过
564+
显式 `--config` 做单次覆盖,或使用 `bl config use --name <name>` 主动切换。整个过程不引入
565+
Token Plan 模式,也不复制现有模型调用实现。

packages/commands/tests/e2e/auth.e2e.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import { AUTH_ROUTES } from "./topic-routes.ts";
1515

1616
interface ValidationServer {
1717
baseUrl: string;
18-
requests: Array<{ path: string; body: Record<string, unknown> }>;
18+
requests: Array<{
19+
path: string;
20+
body: Record<string, unknown>;
21+
authorization?: string;
22+
sourceConfig?: string;
23+
}>;
1924
close(): Promise<void>;
2025
}
2126

@@ -29,6 +34,8 @@ async function startValidationServer(statusCode = 200): Promise<ValidationServer
2934
requests.push({
3035
path: request.url ?? "",
3136
body: rawBody ? (JSON.parse(rawBody) as Record<string, unknown>) : {},
37+
authorization: request.headers.authorization,
38+
sourceConfig: request.headers["x-dashscope-source-config"] as string | undefined,
3239
});
3340
response.writeHead(statusCode, { "Content-Type": "application/json" });
3441
if (statusCode >= 400) {
@@ -207,6 +214,8 @@ describe("e2e: auth", () => {
207214
expect(validationServer.requests).toHaveLength(1);
208215
expect(validationServer.requests[0]).toMatchObject({
209216
path: "/compatible-mode/v1/chat/completions",
217+
authorization: "Bearer sk-e2e-placeholder",
218+
sourceConfig: expect.any(String),
210219
body: {
211220
model: "qwen3.7-max",
212221
stream: false,
@@ -297,6 +306,8 @@ describe("e2e: auth", () => {
297306
expect(validationServer.requests).toHaveLength(1);
298307
expect(validationServer.requests[0]).toMatchObject({
299308
path: "/compatible-mode/v1/chat/completions",
309+
authorization: "Bearer sk-sp-e2e-placeholder",
310+
sourceConfig: expect.any(String),
300311
body: {
301312
model: "qwen3.7-max",
302313
stream: false,
@@ -309,6 +320,7 @@ describe("e2e: auth", () => {
309320
unknown
310321
>;
311322
expect(config.api_key).toBeUndefined();
323+
expect(config.active_config).toBe("token-plan");
312324
expect(config["token-plan"]).toMatchObject({
313325
api_key: "sk-sp-e2e-placeholder",
314326
base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com",
@@ -377,7 +389,16 @@ describe("e2e: auth", () => {
377389
try {
378390
const login = await runCommandE2e(
379391
AUTH_ROUTES,
380-
["auth", "login", "--api-key", "sk-invalid", "--base-url", validationServer.baseUrl],
392+
[
393+
"auth",
394+
"login",
395+
"--config",
396+
"failed-profile",
397+
"--api-key",
398+
"sk-invalid",
399+
"--base-url",
400+
validationServer.baseUrl,
401+
],
381402
{
382403
BAILIAN_CONFIG_DIR: configDir,
383404
DASHSCOPE_API_KEY: "",

packages/core/src/auth/store.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface AuthStore {
4040
stored(): { apiKey: boolean; console: boolean; openapi: boolean; baseUrl?: string };
4141
/** model 域 baseUrl 链(flag > env > config file > fallback)。 */
4242
resolveBaseUrl(fallback?: string): string;
43-
/** 登录落盘:合并写入,undefined 键忽略。 */
43+
/** 登录落盘:合并写入,undefined 键忽略;显式 --config 成功后同时激活目标 Profile。 */
4444
login(patch: AuthPersistPatch): Promise<void>;
4545
/** 清凭证:console/openapi 只删对应域;all 清全部登录凭证。返回是否有变更。 */
4646
logout(scope: "console" | "openapi" | "all"): Promise<boolean>;
@@ -50,6 +50,7 @@ export interface AuthStore {
5050

5151
export function makeAuthStore(sources: ResolutionSources): AuthStore {
5252
const configName = sources.configName;
53+
const activateAfterLogin = sources.flags.config !== undefined;
5354
return {
5455
describe: () => describeAuthState(sources),
5556
stored() {
@@ -69,7 +70,7 @@ export function makeAuthStore(sources: ResolutionSources): AuthStore {
6970
existing[key] = key === "base_url" ? normalizeModelBaseUrl(String(value)) : value;
7071
}
7172
}
72-
await writeConfigFile(existing, configName);
73+
await writeConfigFile(existing, configName, { activate: activateAfterLogin });
7374
},
7475
async logout(scope) {
7576
const existing = readConfigFile(configName) as Record<string, unknown>;

packages/core/src/config/loader.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ export function readConfigFile(configName?: string): ConfigFile {
8181
return parseConfigFile(readRawConfigBlock(raw, configName));
8282
}
8383

84+
/** 写入所选 Profile;登录流程可在同一次原子写入中将显式 Profile 设为激活项。 */
8485
export async function writeConfigFile(
8586
data: Record<string, unknown>,
8687
configName?: string,
88+
options: { activate?: boolean } = {},
8789
): Promise<void> {
8890
const raw = readRawConfigObject();
8991
if (configName) {
@@ -94,6 +96,9 @@ export async function writeConfigFile(
9496
}
9597
Object.assign(raw, data);
9698
}
99+
if (options.activate) {
100+
raw[ACTIVE_CONFIG_KEY] = configName ?? "default";
101+
}
97102
await writeRawConfigObject(raw);
98103
}
99104

packages/core/tests/config-store.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ test("AuthStore:login 合并落盘,logout 按域清理并报告变更", async ()
106106
});
107107
});
108108

109-
test("AuthStore:未传 --config 时写当前激活项,显式不存在名称在登录成功后创建", async () => {
109+
test("AuthStore:未传 --config 时写当前激活项,显式配置在登录成功后创建并激活", async () => {
110110
await inTempConfigDir(async () => {
111111
await writeConfigFile({ api_key: "sk-default" });
112112
await writeConfigFile({ access_token: "tok-dev" }, "dev");
@@ -124,6 +124,12 @@ test("AuthStore:未传 --config 时写当前激活项,显式不存在名称在
124124
const newStore = makeAuthStore(buildSources({ config: "new-profile" }));
125125
await newStore.login({ access_token: "tok-new" });
126126
expect(readConfigFile("new-profile").access_token).toBe("tok-new");
127+
expect(readConfigProfiles().active).toBe("new-profile");
128+
129+
const defaultStore = makeAuthStore(buildSources({ config: "default" }));
130+
await defaultStore.login({ api_key: "sk-default-updated" });
131+
expect(readConfigFile().api_key).toBe("sk-default-updated");
132+
expect(readConfigProfiles().active).toBe("default");
127133

128134
expect(await activeStore.logout("console")).toBe(true);
129135
expect(readConfigFile("dev").access_token).toBeUndefined();

skills/bailian-cli/assets/setup.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,23 @@ Use the `PlainApiKey` returned by `bl token-plan create-key` as a model API key.
4343

4444
```bash
4545
bl auth login --config token-plan --api-key sk-sp-xxx
46-
bl text chat --config token-plan --message "Hello"
47-
bl image generate --config token-plan --prompt "A cat"
46+
bl text chat --message "Hello"
47+
bl image generate --prompt "A cat"
4848
```
4949

50-
To make Token Plan the default Profile for commands that omit `--config`, activate it explicitly after login:
50+
Successful login automatically activates the explicitly selected Profile. Use `bl config list` to
51+
inspect it, and switch back when needed:
5152

5253
```bash
53-
bl config use --name token-plan
54-
bl text chat --message "Hello"
55-
bl image generate --prompt "A cat"
54+
bl config list
55+
bl config use --name default
5656
```
5757

58-
`auth login --config token-plan` saves that Profile but does not activate it. Use `bl config list` to inspect the active Profile, `bl config use --name default` to switch back, or `--config default` for a one-command override. Config selection follows explicit `--config` > persisted `active_config` > `default`; credential and endpoint fields inside the selected Profile still follow flag > environment > config.
58+
`auth login --config token-plan` creates or updates that Profile and activates it only after the
59+
credential is validated and saved. Failed login and `--dry-run` do not switch Profiles. Use
60+
`--config default` for a one-command override. Config selection follows explicit `--config` >
61+
persisted `active_config` > `default`; credential and endpoint fields inside the selected Profile
62+
still follow flag > environment > config.
5963

6064
Activation selects the entire Config for every credential domain, not only model consumption. After activating `token-plan`, Token Plan management and Console commands also read their OpenAPI or Console credentials from that Profile. If those credentials remain in `default`, invoke the command with `--config default` or log the corresponding credential domain into `token-plan`.
6165

0 commit comments

Comments
 (0)