mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat(config): activate explicit profile after successful login
This commit is contained in:
@@ -45,6 +45,8 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
- `resolveApiKey()` — `auth: "apiKey"` 命令;优先级 `--api-key` > `DASHSCOPE_API_KEY` > config `api_key`
|
||||
- `resolveModelBaseUrl()` — model base URL;优先级 `--base-url` > `DASHSCOPE_BASE_URL` > config `base_url` > `REGIONS.cn`,返回前统一去除 query、fragment、尾斜杠和已知 SDK/API Base 后缀,同时保留自定义网关前缀
|
||||
- `--config` 只选择 config 文件 block,不提升该 block 的字段优先级;内置套餐 Profile(当前为 `token-plan`)的预设仅在登录时物化写入,运行时继续走统一的 flag > env > selected config file > 默认值
|
||||
- 显式 `auth login --config <name>` 在凭证验证并落盘成功后自动激活目标 Profile;未传
|
||||
`--config` 时继续写当前激活项,失败和 dry-run 不切换
|
||||
- `resolveConsole()` — `auth: "console"` 命令;当前 token 来自 config `access_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` > config `access_key_*`。兼容读取旧字段 `openapi_access_key_*`,新写入只写短字段
|
||||
- `describeAuthState()` — `auth status` / banner / telemetry 使用的只读快照
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
- 解析阶段用局部变量保留“是否显式传入 `--config`”的信息;完成 Config 选择后不进入 `Settings`。
|
||||
- `--config default` 必须显式选择顶层配置并绕过命名激活项。
|
||||
- `--config` 和 `auth login --config ...` 不得隐式修改持久化激活状态。
|
||||
- 普通命令的显式 `--config` 只覆盖本次选择,不修改持久化激活状态;例外是
|
||||
`auth login --config ...`,凭证验证并落盘成功后自动激活该 Profile。
|
||||
- 激活状态只选择配置 block,不改变字段优先级;字段仍为 flag > env > selected config > 默认值。
|
||||
- Pipeline 等进程内调用链也要复用统一的 `buildSources()`,避免绕过激活状态。
|
||||
- Console access token 自动刷新等后台读写必须携带 `settings.configName`,不得直接读写顶层 default。
|
||||
@@ -25,7 +26,8 @@
|
||||
## 3. 保持读写命令交互一致
|
||||
|
||||
- `auth login`、`config set` 等写命令未传 `--config` 时修改当前激活项。
|
||||
- 写命令显式指定不存在的 `--config <name>` 时,仅在业务操作成功并实际落盘时创建 Profile。
|
||||
- `auth login --config <name>` 显式指定不存在的 Profile 时,仅在凭证验证成功并实际落盘时
|
||||
创建和激活;`config set --config <name>` 可创建但不自动激活。
|
||||
- `config show`、`auth status` 和业务消费等读命令不得因为显式指定不存在的名称而创建 Profile。
|
||||
- `auth logout` 默认只清理当前激活项;显式 `--config` 只清理指定项。
|
||||
- 按凭证域退出时必须清理该域的完整字段集合,例如 OpenAPI 同时清理 AK、SK 和 STS `security_token`。
|
||||
@@ -56,6 +58,8 @@
|
||||
- 悬空 `active_config` 明确失败。
|
||||
- 删除激活 Profile 后切回 `default`。
|
||||
- 登录、退出、`config set` 分别覆盖“当前激活项”和“显式不存在名称成功后创建”。
|
||||
- 显式 `auth login --config <name>` 成功后激活该 Profile,失败或 dry-run 不创建、不切换;
|
||||
`--config default` 成功后切回 `default`。
|
||||
- Console token 自动刷新不从其他 Profile 借用 AK/SK,也不把新 token 写入其他 Profile。
|
||||
- `config list/show/use/ui`、`auth status` 和依赖默认模型的消费命令覆盖对应 E2E。
|
||||
- `config ui` 覆盖保存时保留未管理字段,并继续允许空值清除 UI 管理字段。
|
||||
|
||||
@@ -72,6 +72,7 @@ CLI 应解析并保存以下配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"active_config": "token-plan",
|
||||
"token-plan": {
|
||||
"api_key": "<TOKEN_PLAN_API_KEY>",
|
||||
"base_url": "https://token-plan.cn-beijing.maas.aliyuncs.com",
|
||||
@@ -81,6 +82,9 @@ CLI 应解析并保存以下配置:
|
||||
}
|
||||
```
|
||||
|
||||
凭证验证和配置落盘成功后,CLI 在同一次配置文件写入中将 `token-plan` 设为激活项;验证失败和
|
||||
dry-run 不创建、不切换 Profile。
|
||||
|
||||
用户仍可显式覆盖 Base URL,用于代理、测试或未来新增地域:
|
||||
|
||||
```sh
|
||||
@@ -109,7 +113,7 @@ bl image generate --config token-plan --prompt "一只猫"
|
||||
|
||||
### 3. 激活 Config
|
||||
|
||||
新增命令:
|
||||
登录时显式选择的 Profile 会自动激活;之后也可以主动切换:
|
||||
|
||||
```sh
|
||||
bl config use --name token-plan
|
||||
@@ -225,7 +229,8 @@ Config 激活只改变配置文件 block 的选择,`--config` 本身不提升
|
||||
- 配置文件中的 `active_config` 指向不存在的 Profile:命令失败并提示切回 `default`,不得静默使用其他凭证。
|
||||
- 删除当前激活的 Profile:删除操作同时切回 `default`,或者要求用户先切换;不能保留悬空引用。
|
||||
- `config use --name token-plan` 只切换状态,不创建 Profile,也不执行登录。
|
||||
- `auth login --config token-plan` 只写入指定 Profile,不自动激活,避免登录命令产生隐藏的全局状态变化。
|
||||
- `auth login --config token-plan` 在凭证验证并落盘成功后自动激活该 Profile;验证失败和
|
||||
dry-run 不创建、不切换。
|
||||
|
||||
## `token-plan` 内置 Profile 预设
|
||||
|
||||
@@ -474,7 +479,10 @@ feat(config): add active profile selection
|
||||
- 验证临时 `--config default` 不改变激活状态。
|
||||
- 更新命令导出、`packages/cli/src/commands.ts`、E2E 和生成 reference。
|
||||
|
||||
实现选择:删除当前激活的命名 Profile 时,在同一次配置文件写入中将 `active_config` 重置为 `default`。`auth login --config <name>` 和所有显式 `--config` 仍只作用于本次命令,不修改激活状态。
|
||||
实现选择:删除当前激活的命名 Profile 时,在同一次配置文件写入中将 `active_config` 重置为
|
||||
`default`。普通命令的显式 `--config` 仍只作用于本次命令;`auth login --config <name>` 是
|
||||
例外,在凭证验证和落盘成功的同一次配置写入中激活目标 Profile。`--config default` 登录成功后
|
||||
切回默认配置。
|
||||
|
||||
相关写入交互统一为:`auth login`、`auth logout` 和 `config set` 未传 `--config` 时作用于当前激活项;显式指定名称时作用于该名称。写命令可在成功落盘时创建不存在的 Profile,读命令不创建。Console access token 自动刷新同样限定在当前选中的 Profile,不得回退读写顶层 default。
|
||||
|
||||
@@ -552,4 +560,6 @@ Token Plan 模型消费最终表现为一个可激活的内置 Profile:
|
||||
-> 文本/图片 endpoint
|
||||
```
|
||||
|
||||
用户既可以通过 `--config token-plan` 单次使用,也可以通过 `bl config use --name token-plan` 将其设为默认激活配置。整个过程不引入 Token Plan 模式,也不复制现有模型调用实现。
|
||||
用户执行 `auth login --config token-plan` 成功后,该 Profile 会成为默认激活配置;仍可通过
|
||||
显式 `--config` 做单次覆盖,或使用 `bl config use --name <name>` 主动切换。整个过程不引入
|
||||
Token Plan 模式,也不复制现有模型调用实现。
|
||||
|
||||
@@ -15,7 +15,12 @@ import { AUTH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
interface ValidationServer {
|
||||
baseUrl: string;
|
||||
requests: Array<{ path: string; body: Record<string, unknown> }>;
|
||||
requests: Array<{
|
||||
path: string;
|
||||
body: Record<string, unknown>;
|
||||
authorization?: string;
|
||||
sourceConfig?: string;
|
||||
}>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -29,6 +34,8 @@ async function startValidationServer(statusCode = 200): Promise<ValidationServer
|
||||
requests.push({
|
||||
path: request.url ?? "",
|
||||
body: rawBody ? (JSON.parse(rawBody) as Record<string, unknown>) : {},
|
||||
authorization: request.headers.authorization,
|
||||
sourceConfig: request.headers["x-dashscope-source-config"] as string | undefined,
|
||||
});
|
||||
response.writeHead(statusCode, { "Content-Type": "application/json" });
|
||||
if (statusCode >= 400) {
|
||||
@@ -207,6 +214,8 @@ describe("e2e: auth", () => {
|
||||
expect(validationServer.requests).toHaveLength(1);
|
||||
expect(validationServer.requests[0]).toMatchObject({
|
||||
path: "/compatible-mode/v1/chat/completions",
|
||||
authorization: "Bearer sk-e2e-placeholder",
|
||||
sourceConfig: expect.any(String),
|
||||
body: {
|
||||
model: "qwen3.7-max",
|
||||
stream: false,
|
||||
@@ -297,6 +306,8 @@ describe("e2e: auth", () => {
|
||||
expect(validationServer.requests).toHaveLength(1);
|
||||
expect(validationServer.requests[0]).toMatchObject({
|
||||
path: "/compatible-mode/v1/chat/completions",
|
||||
authorization: "Bearer sk-sp-e2e-placeholder",
|
||||
sourceConfig: expect.any(String),
|
||||
body: {
|
||||
model: "qwen3.7-max",
|
||||
stream: false,
|
||||
@@ -309,6 +320,7 @@ describe("e2e: auth", () => {
|
||||
unknown
|
||||
>;
|
||||
expect(config.api_key).toBeUndefined();
|
||||
expect(config.active_config).toBe("token-plan");
|
||||
expect(config["token-plan"]).toMatchObject({
|
||||
api_key: "sk-sp-e2e-placeholder",
|
||||
base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com",
|
||||
@@ -377,7 +389,16 @@ describe("e2e: auth", () => {
|
||||
try {
|
||||
const login = await runCommandE2e(
|
||||
AUTH_ROUTES,
|
||||
["auth", "login", "--api-key", "sk-invalid", "--base-url", validationServer.baseUrl],
|
||||
[
|
||||
"auth",
|
||||
"login",
|
||||
"--config",
|
||||
"failed-profile",
|
||||
"--api-key",
|
||||
"sk-invalid",
|
||||
"--base-url",
|
||||
validationServer.baseUrl,
|
||||
],
|
||||
{
|
||||
BAILIAN_CONFIG_DIR: configDir,
|
||||
DASHSCOPE_API_KEY: "",
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface AuthStore {
|
||||
stored(): { apiKey: boolean; console: boolean; openapi: boolean; baseUrl?: string };
|
||||
/** model 域 baseUrl 链(flag > env > config file > fallback)。 */
|
||||
resolveBaseUrl(fallback?: string): string;
|
||||
/** 登录落盘:合并写入,undefined 键忽略。 */
|
||||
/** 登录落盘:合并写入,undefined 键忽略;显式 --config 成功后同时激活目标 Profile。 */
|
||||
login(patch: AuthPersistPatch): Promise<void>;
|
||||
/** 清凭证:console/openapi 只删对应域;all 清全部登录凭证。返回是否有变更。 */
|
||||
logout(scope: "console" | "openapi" | "all"): Promise<boolean>;
|
||||
@@ -50,6 +50,7 @@ export interface AuthStore {
|
||||
|
||||
export function makeAuthStore(sources: ResolutionSources): AuthStore {
|
||||
const configName = sources.configName;
|
||||
const activateAfterLogin = sources.flags.config !== undefined;
|
||||
return {
|
||||
describe: () => describeAuthState(sources),
|
||||
stored() {
|
||||
@@ -69,7 +70,7 @@ export function makeAuthStore(sources: ResolutionSources): AuthStore {
|
||||
existing[key] = key === "base_url" ? normalizeModelBaseUrl(String(value)) : value;
|
||||
}
|
||||
}
|
||||
await writeConfigFile(existing, configName);
|
||||
await writeConfigFile(existing, configName, { activate: activateAfterLogin });
|
||||
},
|
||||
async logout(scope) {
|
||||
const existing = readConfigFile(configName) as Record<string, unknown>;
|
||||
|
||||
@@ -81,9 +81,11 @@ export function readConfigFile(configName?: string): ConfigFile {
|
||||
return parseConfigFile(readRawConfigBlock(raw, configName));
|
||||
}
|
||||
|
||||
/** 写入所选 Profile;登录流程可在同一次原子写入中将显式 Profile 设为激活项。 */
|
||||
export async function writeConfigFile(
|
||||
data: Record<string, unknown>,
|
||||
configName?: string,
|
||||
options: { activate?: boolean } = {},
|
||||
): Promise<void> {
|
||||
const raw = readRawConfigObject();
|
||||
if (configName) {
|
||||
@@ -94,6 +96,9 @@ export async function writeConfigFile(
|
||||
}
|
||||
Object.assign(raw, data);
|
||||
}
|
||||
if (options.activate) {
|
||||
raw[ACTIVE_CONFIG_KEY] = configName ?? "default";
|
||||
}
|
||||
await writeRawConfigObject(raw);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ test("AuthStore:login 合并落盘,logout 按域清理并报告变更", async ()
|
||||
});
|
||||
});
|
||||
|
||||
test("AuthStore:未传 --config 时写当前激活项,显式不存在名称在登录成功后创建", async () => {
|
||||
test("AuthStore:未传 --config 时写当前激活项,显式配置在登录成功后创建并激活", async () => {
|
||||
await inTempConfigDir(async () => {
|
||||
await writeConfigFile({ api_key: "sk-default" });
|
||||
await writeConfigFile({ access_token: "tok-dev" }, "dev");
|
||||
@@ -124,6 +124,12 @@ test("AuthStore:未传 --config 时写当前激活项,显式不存在名称在
|
||||
const newStore = makeAuthStore(buildSources({ config: "new-profile" }));
|
||||
await newStore.login({ access_token: "tok-new" });
|
||||
expect(readConfigFile("new-profile").access_token).toBe("tok-new");
|
||||
expect(readConfigProfiles().active).toBe("new-profile");
|
||||
|
||||
const defaultStore = makeAuthStore(buildSources({ config: "default" }));
|
||||
await defaultStore.login({ api_key: "sk-default-updated" });
|
||||
expect(readConfigFile().api_key).toBe("sk-default-updated");
|
||||
expect(readConfigProfiles().active).toBe("default");
|
||||
|
||||
expect(await activeStore.logout("console")).toBe(true);
|
||||
expect(readConfigFile("dev").access_token).toBeUndefined();
|
||||
|
||||
@@ -43,19 +43,23 @@ Use the `PlainApiKey` returned by `bl token-plan create-key` as a model API key.
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxx
|
||||
bl text chat --config token-plan --message "Hello"
|
||||
bl image generate --config token-plan --prompt "A cat"
|
||||
```
|
||||
|
||||
To make Token Plan the default Profile for commands that omit `--config`, activate it explicitly after login:
|
||||
|
||||
```bash
|
||||
bl config use --name token-plan
|
||||
bl text chat --message "Hello"
|
||||
bl image generate --prompt "A cat"
|
||||
```
|
||||
|
||||
`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.
|
||||
Successful login automatically activates the explicitly selected Profile. Use `bl config list` to
|
||||
inspect it, and switch back when needed:
|
||||
|
||||
```bash
|
||||
bl config list
|
||||
bl config use --name default
|
||||
```
|
||||
|
||||
`auth login --config token-plan` creates or updates that Profile and activates it only after the
|
||||
credential is validated and saved. Failed login and `--dry-run` do not switch Profiles. Use
|
||||
`--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.
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user