Skip to content

Commit deab3b3

Browse files
committed
refactor(flags): scope credential flags by command auth domain
- flags split into GLOBAL_FLAGS (all commands) plus MODEL_AUTH_FLAGS / CONSOLE_AUTH_FLAGS, parsed only for commands of the matching auth domain; cross-domain flags now fail with "Unknown flag" instead of being silently ignored - all shadow redeclarations removed; the registry guard now rejects any own flag named after a reserved (global or visible-domain) flag - --workspace-id joins the console domain (chain: flag > env > file); usage stats drops its private declaration and in-command priority - auth login declares its credential args as own command parameters (--api-key / --base-url / --console-site, original behavior intact); auth status no longer accepts credential-domain overrides (use env or config set instead) - command help and the generated reference both show Flags (own + auth domain) plus a full Global Flags section, replacing the footer hint - breaking: pipeline run --timeout renamed to --step-timeout (collided with the global request timeout)
1 parent d31b7f8 commit deab3b3

49 files changed

Lines changed: 436 additions & 328 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/config-flags-refactor.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
>
77
> 实施(2026-07-06):**已按本方案完成**——前置 baseUrl 翻转 + 阶段 0–6 全部落地(含 flags 收窄/分流/同名守卫、console/advisor/pipeline 收口、tracker 传值、边界守卫测试 `packages/commands/tests/boundaries.test.ts`)。全量 `vp check`/单测/关键 e2e 绿;**未 commit,待 review**。实施中的偏差:advisor 匿名调网关促使 `callConsoleGateway``ConsoleGatewayTarget`(token 可选)而非整个 credential;`describeAuth` 更名 `describeAuthState`;`ConfigStore.reset` 无消费者未实现。
88
>
9+
> flag 边界轮(2026-07-06):**域化完成**——flag 拆 `GLOBAL_FLAGS` + `MODEL_AUTH_FLAGS`/`CONSOLE_AUTH_FLAGS`(按命令 `auth`/`authFlags` 可见),16 处遮蔽清零,跨域传 flag 报错,help 改为 Flags(自有+域)/Global Flags(全量)三段式,`pipeline run --timeout` 更名 `--step-timeout`,login 经 `AuthStore.flagInput()` 收凭证输入。workspaceId 已升入 console 域(链 flag > env > file),stats 命令内优先级删除。
10+
>
911
> 修订(2026-07-04 评审后,均已拍板):§8 改 strangler 分阶段 + 阶段 0 行为锁定测试(已落地);§2 store 接口细化(write async / unset / AuthStore.login 揽登录落盘);§5 validate 收 ownFlags + 同名守卫 + authStage dry-run 双域容忍;§7 tracker/workspaceId 修法;§0/§9 优先级链保真口径。dry-run 决策:**保持"无需凭证"现状**,console 三元组归 Settings 服务 dry-run 展示(不引入 ConsoleTarget);dry-run 输出规范统一推后(§9)。
1012
>
1113
> 约束(务必遵守):
@@ -343,9 +345,9 @@ const authStage = async (ctx, next) => {
343345
## 9. 本次不做(已在钉钉文档记录,后续单独轮次)
344346

345347
- **flag 清理**:`nonInteractive` 删 / `async` ↔ 各命令 `--no-wait` 去重 / `yes` 收窄到命令级 / `noColor` 修一致性(registry/progress/banner 里内联 `process.stderr.isTTY` 绕过了 `config.noColor`)。
346-
- **workspaceId 的 flag 源**:今天没有全局 `--workspace-id`,只有 `usage/stats` 自声明的命令级 flag(命令内做 flag > settings 覆盖)。是否提升为全局 flag,与下条同名遮蔽清理**同一轮**看——都是全局↔命令级 flag 的边界问题。(优先级链归一本身已完成:唯一异类 baseUrl 已前置翻转,见 §0。)
348+
- ~~workspaceId 的 flag 源~~ **已完成**(flag 边界轮):`--workspace-id` 升入 `CONSOLE_AUTH_FLAGS`,链为 flag > env > file;stats 删除自有声明与命令内优先级。(优先级链归一与同名遮蔽清理亦已完成:baseUrl 前置翻转见 §0,遮蔽经域化清零见 §5。)
347349
- **dry-run 输出规范统一**:各域输出现状不一致 —— model/app 域只打请求 body(不含 URL/baseUrl),console 域额外打 api 名 + region/site 路由信息。应一次定规范、跨域对齐(是否展示路由、展示哪些字段);届时若 console 域不再展示,console 三元组可从 Settings 撤出、收敛为纯 credential。**本次保持现状输出**(e2e 有断言)。
348-
- **全局↔命令私有 flag 同名遮蔽**清理(§5 提到的 ~15 个)。本次用"全局恒进 sources"规避,不动声明
350+
- ~~全局↔命令私有 flag 同名遮蔽清理~~ **已完成**(flag 边界轮,经域化):16 处遮蔽全删,凭证 flag 按 `auth` 域可见,跨域报 Unknown flag,守卫升级为同名即抛
349351
- **key ↔ baseUrl 强校验**(region 锁)。落点已就位:`resolveApiKey` 是唯一同时产出 `{token, baseUrl}` 的地方,校验加在它内部即可;baseUrl 不在 Settings,命令侧无法绕过绑定;`AuthStore.login` 已支持 `api_key` + `base_url` 成对落盘。
350352
- **多 profile / 多身份**(arkcli 式)。结构已留缝:单一 `ResolutionSources` 边界 + credential 封装。
351353
- **IOStreams 注入**(gh `Factory.IOStreams` / vercel `Client.stdout`);与 noColor 修复同属下一轮。

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,22 +161,22 @@ describe("e2e: auth", () => {
161161
});
162162

163163
test.skipIf(!isDashScopeE2EReady())(
164-
"auth status --output json --quiet --base-url 国内",
164+
"auth status --output json --quiet(base_url 经 env 指定;凭证域 flag 对 status 不可见)",
165165
async () => {
166-
const { stdout, stderr, exitCode } = await runCli([
167-
"auth",
168-
"status",
169-
"--non-interactive",
170-
"--output",
171-
"json",
172-
"--quiet",
173-
"--base-url",
174-
"https://dashscope.aliyuncs.com",
175-
]);
166+
const { stdout, stderr, exitCode } = await runCli(
167+
["auth", "status", "--non-interactive", "--output", "json", "--quiet"],
168+
{ DASHSCOPE_BASE_URL: "https://dashscope.aliyuncs.com" },
169+
);
176170
expect(exitCode, stderr).toBe(0);
177171
const data = parseStdoutJson<{ authenticated?: boolean; api_key?: unknown }>(stdout);
178172
expect(data.authenticated).toBe(true);
179173
expect(data.api_key).toBeDefined();
180174
},
181175
);
176+
177+
test("auth status 不接受凭证域覆盖 flag(--base-url 报 Unknown flag)", async () => {
178+
const { stderr, exitCode } = await runCli(["auth", "status", "--base-url", "https://x.test"]);
179+
expect(exitCode).not.toBe(0);
180+
expect(stderr).toMatch(/Unknown flag.*--base-url/);
181+
});
182182
});

packages/cli/tests/e2e/console-flags.e2e.test.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,37 @@ describe("e2e: console global flags", () => {
2323
expect(stderr).not.toMatch(/^\s*--region\s/m);
2424
});
2525

26-
test("quota check --help 不重复命令级 region,并提示全局 flags", async () => {
26+
test("quota check --help:Flags 含 console 域鉴权 flag,Global Flags 全量列出", async () => {
2727
const { stderr, exitCode } = await runCli(["quota", "check", "--help"]);
2828
expect(exitCode, stderr).toBe(0);
29-
expect(stderr).toMatch(/Global flags.*always available/i);
29+
expect(stderr).toMatch(/Global Flags:/);
30+
expect(stderr).toMatch(/--console-region <region>/);
3031
expect(stderr).toMatch(/--model <model>/);
3132
expect(stderr).toMatch(/--period <minutes>/);
33+
expect(stderr).toMatch(/--output <format>/);
3234
expect(stderr).not.toMatch(/API region \(default: cn-beijing\)/);
3335
});
3436

37+
test("跨域 flag 拒绝:model 命令传 --console-region 报 Unknown flag", async () => {
38+
const { stderr, exitCode } = await runCli([
39+
"text",
40+
"chat",
41+
"--message",
42+
"hi",
43+
"--console-region",
44+
"cn-hangzhou",
45+
"--dry-run",
46+
]);
47+
expect(exitCode).not.toBe(0);
48+
expect(stderr).toMatch(/Unknown flag.*--console-region/);
49+
});
50+
51+
test("跨域 flag 拒绝:console 命令传 --api-key 报 Unknown flag", async () => {
52+
const { stderr, exitCode } = await runCli(["mcp", "list", "--api-key", "sk-test", "--dry-run"]);
53+
expect(exitCode).not.toBe(0);
54+
expect(stderr).toMatch(/Unknown flag.*--api-key/);
55+
});
56+
3557
test("console call --help 不暴露命令级 region/site,示例使用 --console-region", async () => {
3658
const { stderr, exitCode } = await runCli(["console", "call", "--help"]);
3759
expect(exitCode, stderr).toBe(0);
@@ -42,11 +64,14 @@ describe("e2e: console global flags", () => {
4264
expect(stderr).toMatch(/--console-region cn-beijing/);
4365
});
4466

45-
test("auth login --help 描述 --console 与 --console-site 配合", async () => {
67+
test("auth login --help:自有 flag 含 --console-site,不含其余 console 域 flag", async () => {
4668
const { stderr, exitCode } = await runCli(["auth", "login", "--help"]);
4769
expect(exitCode, stderr).toBe(0);
48-
expect(stderr).toMatch(/--console-site/);
49-
expect(stderr).toMatch(/--console.*console-site|console-site.*domestic|international/i);
70+
expect(stderr).toMatch(/--api-key <key>/);
71+
expect(stderr).toMatch(/--base-url <url>/);
72+
expect(stderr).toMatch(/--console-site <site>/);
73+
expect(stderr).not.toMatch(/--console-region/);
74+
expect(stderr).not.toMatch(/--workspace-id/);
5075
});
5176

5277
test("console call --dry-run 默认 consoleRegion 为 cn-beijing", async () => {

packages/commands/src/commands/app/list.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@ export default defineCommand({
2323
valueHint: "<n>",
2424
description: "Results per page (default: 30)",
2525
},
26-
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
27-
consoleSite: {
28-
type: "string",
29-
valueHint: "<site>",
30-
description: "Console site: domestic, international",
31-
},
32-
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
3326
},
3427
exampleArgs: ["", "--name customer service", "--page 2 --page-size 10", "--output json"],
3528
async run(ctx) {

packages/commands/src/commands/auth/login.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,48 @@ export default defineCommand({
2323
description:
2424
"Sign in via browser; use --console-site to choose domestic (default) or international",
2525
},
26+
consoleSite: {
27+
type: "string",
28+
valueHint: "<site>",
29+
description: "Console site: domestic, international",
30+
},
2631
},
2732
exampleArgs: ["--api-key sk-xxxxx", "--console"],
2833
validate: (f) => (!f.console && !f.apiKey ? "Provide --api-key or --console" : undefined),
2934
async run(ctx) {
3035
const { identity, settings, flags } = ctx;
3136
const store = ctx.authStore();
3237
const deps = { identity, settings, authStore: store };
38+
const key = flags.apiKey;
39+
const baseUrl = flags.baseUrl || undefined;
40+
3341
if (flags.console) {
3442
if (settings.dryRun) {
3543
emitBare(
3644
"Would bind a free port on 127.0.0.1 and open the console login URL in your browser.",
3745
);
3846
return;
3947
}
40-
const hasApiKey = !!(flags.apiKey || store.stored().apiKey);
41-
await runConsoleLogin(resolveConsoleOrigin(settings.consoleSite || "domestic"), deps, {
48+
const hasApiKey = !!(key || store.stored().apiKey);
49+
// 本次登录站点:参数缺省时用配置默认(file 里上次登录存的站点)。
50+
const site = flags.consoleSite || settings.consoleSite || "domestic";
51+
await runConsoleLogin(resolveConsoleOrigin(site), deps, {
4252
needApiKey: !hasApiKey,
4353
});
4454
return;
4555
}
4656

4757
// --api-key path; validate() guarantees apiKey on the non-console branch.
48-
if (flags.apiKey) {
49-
const key = flags.apiKey;
50-
const baseUrl = flags.baseUrl || undefined;
58+
if (!key) return;
5159

52-
if (settings.dryRun) {
53-
emitBare("Would validate and save API key.");
54-
return;
55-
}
56-
if (baseUrl) {
57-
await store.login({ base_url: baseUrl });
58-
}
59-
await validateAndPersistApiKey(deps, key, baseUrl || store.resolveBaseUrl());
60-
printQuickStart();
60+
if (settings.dryRun) {
61+
emitBare("Would validate and save API key.");
62+
return;
63+
}
64+
if (baseUrl) {
65+
await store.login({ base_url: baseUrl });
6166
}
67+
await validateAndPersistApiKey(deps, key, baseUrl || store.resolveBaseUrl());
68+
printQuickStart();
6269
},
6370
});

packages/commands/src/commands/auth/logout.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import { emitBare } from "bailian-cli-runtime";
44
export default defineCommand({
55
description: "Clear stored credentials",
66
auth: "none",
7-
usageArgs: "[--console] [--yes] [--dry-run]",
7+
usageArgs: "[--console] [--dry-run]",
88
flags: {
99
console: {
1010
type: "switch",
1111
description: "Only clear the console access_token, keep api_key intact",
1212
},
13-
yes: { type: "switch", description: "Skip confirmation prompt" },
1413
},
15-
exampleArgs: ["", "--console", "--dry-run", "--yes"],
14+
exampleArgs: ["", "--console", "--dry-run"],
1615
async run(ctx) {
1716
const { settings, flags } = ctx;
1817
const store = ctx.authStore();

packages/commands/src/commands/auth/status.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,6 @@ export default defineCommand({
66
description: "Show current authentication state",
77
auth: "none",
88
exampleArgs: ["", "--output json"],
9-
flags: {
10-
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
11-
consoleSite: {
12-
type: "string",
13-
valueHint: "<site>",
14-
description: "Console site: domestic, international",
15-
},
16-
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
17-
},
189
async run(ctx) {
1910
const { identity, settings } = ctx;
2011
const format = detectOutputFormat(settings.output);

packages/commands/src/commands/console/call.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@ export default defineCommand({
2323
description: "Request data as JSON string",
2424
required: true,
2525
},
26-
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
27-
consoleSite: {
28-
type: "string",
29-
valueHint: "<site>",
30-
description: "Console site: domestic, international",
31-
},
32-
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
3326
},
3427
exampleArgs: [
3528
`--api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`,

packages/commands/src/commands/mcp/list.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ export default defineCommand({
3737
},
3838
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
3939
pageSize: { type: "number", valueHint: "<n>", description: "Results per page (default: 30)" },
40-
consoleRegion: { type: "string", valueHint: "<region>", description: "Console region" },
41-
consoleSite: {
42-
type: "string",
43-
valueHint: "<site>",
44-
description: "Console site: domestic, international",
45-
},
46-
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
4740
},
4841
exampleArgs: ["", "--name finance", "--output json"],
4942
async run(ctx) {

packages/commands/src/commands/pipeline/run.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const RUN_FLAGS = {
3131
description: "Emit lifecycle events: jsonl",
3232
choices: ["jsonl"] as const,
3333
},
34-
timeout: {
34+
stepTimeout: {
3535
type: "number",
3636
valueHint: "<seconds>",
3737
description: "Default step timeout in seconds",
@@ -70,7 +70,7 @@ export default defineCommand({
7070
concurrency: flags.concurrency,
7171
basePath,
7272
dryRun: settings.dryRun,
73-
timeoutSeconds: flags.timeout,
73+
timeoutSeconds: flags.stepTimeout,
7474
})) {
7575
process.stdout.write(JSON.stringify(event) + "\n");
7676
}
@@ -81,7 +81,7 @@ export default defineCommand({
8181
concurrency: flags.concurrency,
8282
basePath,
8383
dryRun: settings.dryRun,
84-
timeoutSeconds: flags.timeout,
84+
timeoutSeconds: flags.stepTimeout,
8585
onEvent: settings.verbose ? logEvent : undefined,
8686
});
8787

0 commit comments

Comments
 (0)