mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
refactor(output): centralize ANSI styling and remove no-color flag
- remove --no-color from GLOBAL_FLAGS and drop Settings.noColor - move ANSI styling decisions into runtime color helpers with NO_COLOR support - update command text renderers to use shared color helpers instead of local ANSI codes - refresh e2e invocations, generated reference, and agent skill guidance
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
>
|
||||
> 实施(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` 无消费者未实现。
|
||||
>
|
||||
> 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 命令内优先级删除。
|
||||
> flag 边界轮(2026-07-06):**域化完成**——flag 拆 `GLOBAL_FLAGS` + `MODEL_AUTH_FLAGS`/`CONSOLE_AUTH_FLAGS`(按命令 `auth` 可见),16 处遮蔽清零,跨域传 flag 报错,help 改为 Flags(自有+域)/Global Flags(全量)三段式,`pipeline run --timeout` 更名 `--step-timeout`,login 凭证输入由自有 flags + `AuthStore.login()` 落盘。workspaceId 已升入 console 域(链 flag > env > file),stats 命令内优先级删除。
|
||||
>
|
||||
> 修订(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)。
|
||||
>
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
> **ctx 是唯一组合根。它在边界处把 flag / env / file / 默认 各源解析成 `identity / settings / credential`,交给命令。**
|
||||
>
|
||||
> 优先级链已**统一为 flag > env > file > 默认**:唯一异类 baseUrl(原 flag>file>env)已在前置独立 commit 翻转,锁定表(`packages/core/tests/config-priority.test.ts`)同步更新,`buildSettings` 逐字段对照锁定表移植。workspaceId 无全局 flag 源(见 §9);verbose/noColor 为 OR 语义、telemetry 的 DO_NOT_TRACK 为业界标准,均非链序问题。
|
||||
> 优先级链已**统一为 flag > env > file > 默认**:唯一异类 baseUrl(原 flag>file>env)已在前置独立 commit 翻转,锁定表(`packages/core/tests/config-priority.test.ts`)同步更新,`buildSettings` 逐字段对照锁定表移植。workspaceId 已升入 console 域(链 flag > env > file);verbose 为 OR 语义、telemetry 的 DO_NOT_TRACK 为业界标准,均非链序问题。
|
||||
|
||||
三条硬规矩:
|
||||
|
||||
@@ -70,7 +70,6 @@ export interface Settings {
|
||||
output: "text" | "json";
|
||||
outputDir?: string;
|
||||
timeout: number;
|
||||
concurrent?: number; // 命令经 getConcurrency 读 → 归 settings
|
||||
defaultTextModel?: string;
|
||||
defaultVideoModel?: string;
|
||||
defaultImageModel?: string;
|
||||
@@ -82,11 +81,7 @@ export interface Settings {
|
||||
consoleSwitchAgent?: number;
|
||||
verbose: boolean;
|
||||
quiet: boolean;
|
||||
noColor: boolean;
|
||||
yes: boolean;
|
||||
dryRun: boolean;
|
||||
nonInteractive: boolean; // 0 消费者,可留可删;留着零风险
|
||||
async: boolean;
|
||||
telemetry: boolean;
|
||||
}
|
||||
```
|
||||
@@ -236,11 +231,16 @@ export function describeAuth(s: ResolutionSources): AuthState; // auth status
|
||||
|
||||
```ts
|
||||
case "run": {
|
||||
// 1) 一次解析(全局+命令 flag 合并)
|
||||
const parsed = parseFlags(res.rest, { ...GLOBAL_FLAGS, ...res.command.flags });
|
||||
// 1) 一次解析(全局 + 凭证域 + 命令 flag 合并)
|
||||
const credDefs = credentialFlagDefs(res.command);
|
||||
const parsed = parseFlags(res.rest, {
|
||||
...GLOBAL_FLAGS,
|
||||
...credDefs,
|
||||
...res.command.flags,
|
||||
});
|
||||
|
||||
// 2) 分流(见下"分流规则"),validate 收收窄后的 ownFlags(与 §2 签名一致,别传 parsed)
|
||||
const globals = pick(parsed, Object.keys(GLOBAL_FLAGS)); // 全局 flag → sources
|
||||
const globals = pick(parsed, [...Object.keys(GLOBAL_FLAGS), ...Object.keys(credDefs)]); // 全局+凭证域 → sources
|
||||
const ownFlags = pick(parsed, Object.keys(res.command.flags ?? {})); // 命令声明的 → ctx.flags
|
||||
const invalid = res.command.validate?.(ownFlags);
|
||||
if (invalid) throw new UsageError(invalid);
|
||||
@@ -262,13 +262,13 @@ case "run": {
|
||||
}
|
||||
```
|
||||
|
||||
**分流规则(重要,含"本次不改遮蔽"的妥协):**
|
||||
**分流规则(重要):**
|
||||
|
||||
> **全局 flag 恒进 `sources`(用 `Object.keys(GLOBAL_FLAGS)`);命令声明的 flag 进 `ctx.flags`(用 `Object.keys(command.flags)`)。同名遮蔽者两边都出现(受控重叠)。**
|
||||
> **全局 flag + 当前命令可见的凭证域 flag 进 `sources`;命令自有 flag 进 `ctx.flags`。**
|
||||
|
||||
为什么这样:约 15 个命令把全局 flag(`consoleSite/consoleRegion/switchAgent`、`async`)重声明成自己的,纯为 help 显示(声明不读)。若"命令声明的 key 一律归 ctx.flags 且不进 sources",这些遮蔽会让 `--console-region` 到不了 `resolveConsole`,区域覆盖静默失效。**本次不清理这些遮蔽**(已记录在钉钉文档),用"全局恒进 sources"规避,行为不变;代价是这 ~15 个 flag 在 ctx.flags 和 sources 各出现一次(auth login 的 apiKey/baseUrl 也在两边,但 auth:none 不解析,无害)。
|
||||
为什么这样:`MODEL_AUTH_FLAGS` / `CONSOLE_AUTH_FLAGS` 只按命令 `auth` 暴露,既保证 `--api-key` / `--console-region` 这类域 flag 能进入 credential/settings 解析链,也避免无关命令误收跨域 flag。历史同名遮蔽已清理,命令自有 flag 与全局/域 flag 不再受控重叠。
|
||||
|
||||
**同名守卫**:registry 构建时断言 —— 命令 flag 与全局同名时,其 FlagDef `type` 必须一致。分流规则依赖"遮蔽都是同型重声明"这一假设;十行断言把口头约定变成机器约定,防止未来有人把 `async` 重声明成 value flag 后,错型值静默流进 sources。
|
||||
**同名守卫**:registry 构建时断言 —— 命令自有 flag 与全局/凭证域 flag 同名即报错。分流规则依赖"同一 key 只归一个域"这一约束,防止未来新增 flag 时把同名值静默分到错误通道。
|
||||
|
||||
`authStage`(`packages/runtime/src/middleware.ts`):
|
||||
|
||||
@@ -292,17 +292,19 @@ const authStage = async (ctx, next) => {
|
||||
|
||||
## 6. 字段迁移对照(旧 `Config` → 去向)
|
||||
|
||||
| 旧 `Config` 字段 | 去向 |
|
||||
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `clientName` / `clientVersion` | **Identity**(`clientName` / `version`) |
|
||||
| `binName` / `npmPackage` | **Identity** |
|
||||
| `apiKey` / `apiKeyEnv` / `fileApiKey` / `fileAccessToken` | **删除** → provider chain 从 sources 读 |
|
||||
| `baseUrl` | **ApiKeyCredential.baseUrl**(resolveApiKey 里解析) |
|
||||
| `consoleSite` / `consoleRegion` / `consoleSwitchAgent` | **Settings**(dry-run 展示)+ **ConsoleCredential**(真实调用),受控重叠 |
|
||||
| `workspaceId` | **Settings** |
|
||||
| `output` / `outputDir` / `timeout` / `default*Model` | **Settings** |
|
||||
| `verbose` / `quiet` / `noColor` / `dryRun` / `async` / `yes` / `telemetry` / `configPath` | **Settings** |
|
||||
| `concurrent`(原本仅 flags) | **Settings**(`getConcurrency` 改读 settings) |
|
||||
| 旧 `Config` 字段 | 去向 |
|
||||
| ----------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `clientName` / `clientVersion` | **Identity**(`clientName` / `version`) |
|
||||
| `binName` / `npmPackage` | **Identity** |
|
||||
| `apiKey` / `apiKeyEnv` / `fileApiKey` / `fileAccessToken` | **删除** → provider chain 从 sources 读 |
|
||||
| `baseUrl` | **ApiKeyCredential.baseUrl**(resolveApiKey 里解析) |
|
||||
| `consoleSite` / `consoleRegion` / `consoleSwitchAgent` | **Settings**(dry-run 展示)+ **ConsoleCredential**(真实调用),受控重叠 |
|
||||
| `workspaceId` | **Settings** |
|
||||
| `output` / `outputDir` / `timeout` / `default*Model` | **Settings** |
|
||||
| `verbose` / `quiet` / `dryRun` / `telemetry` / `configPath` | **Settings** |
|
||||
| `async` / `concurrent` | **命令自有 flag**(`ASYNC_FLAG` / `CONCURRENT_FLAG`) |
|
||||
| `yes` | **命令自有 flag**(`quota request`) |
|
||||
| `nonInteractive` | **删除** |
|
||||
|
||||
---
|
||||
|
||||
@@ -314,10 +316,10 @@ const authStage = async (ctx, next) => {
|
||||
- `client/client.ts:38`:`apiCred?.baseUrl ?? config.baseUrl` → `apiCred.baseUrl`
|
||||
- 命令读 `config.binName`(约 6 处:`auth/status`、`usage/stats`、`mcp/list`、`quota/history`、`quota/request`)→ `identity.binName`(经 ctx)
|
||||
- **console 收口**:约 12 处 `callConsoleGateway(config, token, {api,data})`(`app/list`、`workspace/list`、`usage/*`、`mcp/list`、`quota/*`、`console/call`)→ `ctx.client.callConsole({api,data})`;dry-run 里的 `effectiveConsoleGatewayConfig(config)` → `effectiveConsoleGatewayConfig(settings)`(签名收窄,不走 client)
|
||||
- **workspaceId**:`usage/stats.ts` 的 `resolveWorkspaceId(config, flag)` → `flags.workspaceId ?? requireWorkspace(ctx.settings)`(新 helper 只兜 settings,缺失时报原来的错 + `${identity.binName} workspace list` 提示)。**注意:`--workspace-id` 是命令级 flag、不在 GLOBAL_FLAGS,进不了 sources/settings,flag 的第一优先级必须在命令里显式保住**,否则静默丢失
|
||||
- **workspaceId**:`usage/stats.ts` 的 `resolveWorkspaceId(config, flag)` → `requireWorkspaceId(ctx.settings, identity.binName)`。`--workspace-id` 已升入 `CONSOLE_AUTH_FLAGS`,进入 sources/settings,链为 flag > env > file。
|
||||
- **config/auth 命令**:`readConfigFile/writeConfigFile/resolver` 直接调用 → 走 `ctx.configStore()` / `ctx.authStore()`
|
||||
- **auth/login `validate`**:`!f.console && !f.apiKey`——`apiKey`/`baseUrl` 是它自己声明的 flag(`login.ts:15`),收窄后仍在 `ctx.flags`,**无需改**
|
||||
- **pipeline**:`buildPipelineConfig`(伪造整套 GlobalFlags,`runtime/src/pipeline/bl-config.ts`)→ `buildSettings({ flags: {}, file: readConfigFile(), env })`(flags 已收 Partial,见 §4) + 强制 `output:'json'/quiet/nonInteractive`;pipeline executor 是"迷你边界",给 step 构造 settings/client
|
||||
- **pipeline**:`buildPipelineConfig`(伪造整套 GlobalFlags,`runtime/src/pipeline/bl-config.ts`)→ `buildSettings({ flags: {}, file: readConfigFile(), env })`(flags 已收 Partial,见 §4) + 强制 `output:'json'/quiet`;pipeline executor 是"迷你边界",给 step 构造 settings/client
|
||||
- 其余把 `Config` 当类型用的地方 → `Settings`;ctx 字段 `config` → `settings`(`ctx.config` 仅 2 处、解构 `const { config } = ctx` 约 46 处 + 其函数体内 `config.` → `settings.`)
|
||||
|
||||
**命名注意**:
|
||||
@@ -344,13 +346,13 @@ const authStage = async (ctx, next) => {
|
||||
|
||||
## 9. 本次不做(已在钉钉文档记录,后续单独轮次)
|
||||
|
||||
- **flag 清理**:`nonInteractive` 删 / `async` ↔ 各命令 `--no-wait` 去重 / `yes` 收窄到命令级 / `noColor` 修一致性(registry/progress/banner 里内联 `process.stderr.isTTY` 绕过了 `config.noColor`)。
|
||||
- ~~**flag 清理**~~ **已完成**:`nonInteractive` 删除;`async`/`concurrent` 改为命令级共享定义;`yes` 收窄为 `quota request` 自有;原颜色 CLI flag / Settings 字段删除,颜色由 `NO_COLOR` + 实际输出流 `isTTY` 共同决定,内联判断收束到 runtime helper。
|
||||
- ~~workspaceId 的 flag 源~~ **已完成**(flag 边界轮):`--workspace-id` 升入 `CONSOLE_AUTH_FLAGS`,链为 flag > env > file;stats 删除自有声明与命令内优先级。(优先级链归一与同名遮蔽清理亦已完成:baseUrl 前置翻转见 §0,遮蔽经域化清零见 §5。)
|
||||
- **dry-run 输出规范统一**:各域输出现状不一致 —— model/app 域只打请求 body(不含 URL/baseUrl),console 域额外打 api 名 + region/site 路由信息。应一次定规范、跨域对齐(是否展示路由、展示哪些字段);届时若 console 域不再展示,console 三元组可从 Settings 撤出、收敛为纯 credential。**本次保持现状输出**(e2e 有断言)。
|
||||
- ~~全局↔命令私有 flag 同名遮蔽清理~~ **已完成**(flag 边界轮,经域化):16 处遮蔽全删,凭证 flag 按 `auth` 域可见,跨域报 Unknown flag,守卫升级为同名即抛。
|
||||
- **key ↔ baseUrl 强校验**(region 锁)。落点已就位:`resolveApiKey` 是唯一同时产出 `{token, baseUrl}` 的地方,校验加在它内部即可;baseUrl 不在 Settings,命令侧无法绕过绑定;`AuthStore.login` 已支持 `api_key` + `base_url` 成对落盘。
|
||||
- **多 profile / 多身份**(arkcli 式)。结构已留缝:单一 `ResolutionSources` 边界 + credential 封装。
|
||||
- **IOStreams 注入**(gh `Factory.IOStreams` / vercel `Client.stdout`);与 noColor 修复同属下一轮。
|
||||
- **IOStreams 注入**(gh `Factory.IOStreams` / vercel `Client.stdout`);颜色 stream helper 已先行收束,完整 IOStreams 注入仍留后续轮次。
|
||||
- `ConfigFile`(磁盘格式)不变;无用户可见 CLI 变化。
|
||||
|
||||
外部记录:钉钉文档 `https://alidocs.dingtalk.com/i/nodes/YMyQA2dXW7gYo6MzcZzzERNMWzlwrZgb`(全局 flag 对比、flag 清理项、遮蔽问题)。
|
||||
@@ -61,7 +61,6 @@ describe("e2e: auth", () => {
|
||||
"json",
|
||||
"--timeout",
|
||||
"120",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Would validate and save API key.");
|
||||
@@ -82,14 +81,8 @@ describe("e2e: auth", () => {
|
||||
expect(stderr).not.toContain("Cleared api_key");
|
||||
});
|
||||
|
||||
test("auth logout --dry-run --quiet --no-color", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"auth",
|
||||
"logout",
|
||||
"--dry-run",
|
||||
"--quiet",
|
||||
"--no-color",
|
||||
]);
|
||||
test("auth logout --dry-run --quiet", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli(["auth", "logout", "--dry-run", "--quiet"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("No changes made.");
|
||||
});
|
||||
|
||||
@@ -38,14 +38,8 @@ describe("e2e: config", () => {
|
||||
expect(data.timeout).toBeDefined();
|
||||
});
|
||||
|
||||
test("config show --output text --no-color", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"config",
|
||||
"show",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
test("config show --output text", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli(["config", "show", "--output", "text"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toMatch(/config_file|timeout|base_url/i);
|
||||
});
|
||||
|
||||
@@ -96,13 +96,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota list 文本输出包含英文表头", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"quota",
|
||||
"list",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
const { stdout, stderr, exitCode } = await runCli(["quota", "list", "--output", "text"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Model");
|
||||
expect(stdout).toContain("Req/min");
|
||||
@@ -118,7 +112,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"qwen3.6-plus",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
@@ -254,13 +247,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check 文本输出包含英文表头", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"quota",
|
||||
"check",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
const { stdout, stderr, exitCode } = await runCli(["quota", "check", "--output", "text"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Model");
|
||||
expect(stdout).toContain("RPM Usage/Limit");
|
||||
@@ -276,7 +263,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"qwen3.6-plus",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
@@ -291,7 +277,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"qwen3.6-plus,qwen-plus",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
@@ -335,7 +320,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"qwen3.6-plus",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const hasStatus =
|
||||
|
||||
@@ -146,7 +146,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"qwen3-max",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Model");
|
||||
@@ -165,7 +164,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"qwen3-max",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3-max");
|
||||
@@ -179,7 +177,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"qwen3-max,qwen-turbo",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3-max");
|
||||
@@ -194,7 +191,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"qwen3-max",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Text");
|
||||
@@ -208,7 +204,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"wan2.7-image",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("Unsupported");
|
||||
@@ -222,7 +217,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"wan2.7-image",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const lines = stdout.split("\n").filter((line) => line.includes("wan2.7-image"));
|
||||
@@ -239,7 +233,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"nonexistent-model-xyz-12345",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("nonexistent-model-xyz-12345");
|
||||
@@ -253,7 +246,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"qwen3-max",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const hasAutoStop =
|
||||
|
||||
@@ -177,7 +177,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
wsId,
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -190,7 +189,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
wsId,
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -205,7 +203,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
"qwen3.6-plus",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -220,7 +217,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
"qwen3.6-plus,deepseek-v4-pro",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -235,7 +231,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
"nonexistent-model-xyz-99999",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -250,7 +245,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
"1",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
@@ -265,7 +259,6 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
"Vision",
|
||||
"--output",
|
||||
"text",
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
|
||||
@@ -14,8 +14,7 @@ import {
|
||||
} from "bailian-cli-core";
|
||||
import boxen from "boxen";
|
||||
import chalk, { Chalk, type ChalkInstance } from "chalk";
|
||||
import { emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import { createSpinner } from "bailian-cli-runtime";
|
||||
import { createSpinner, emitBare, emitResult, supportsColor } from "bailian-cli-runtime";
|
||||
|
||||
function formatContextWindow(tokens: number): string {
|
||||
if (tokens >= 1_000_000)
|
||||
@@ -55,8 +54,13 @@ const PREFERENCE_MODE_LABELS: Record<string, string> = {
|
||||
alternative: "Alternative",
|
||||
};
|
||||
|
||||
function formatIntentSummary(intent: IntentProfile, noColor: boolean): string {
|
||||
const colorize = noColor ? new Chalk({ level: 0 }) : chalk;
|
||||
function chalkFor(out: NodeJS.WriteStream): ChalkInstance {
|
||||
return supportsColor(out) ? chalk : new Chalk({ level: 0 });
|
||||
}
|
||||
|
||||
function formatIntentSummary(intent: IntentProfile): string {
|
||||
const colorize = chalkFor(process.stdout);
|
||||
const useColor = supportsColor(process.stdout);
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(colorize.cyan.bold("Intent Analysis"));
|
||||
@@ -121,15 +125,20 @@ function formatIntentSummary(intent: IntentProfile, noColor: boolean): string {
|
||||
return boxen(lines.join("\n"), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
margin: { top: 0, bottom: 0, left: 1, right: 0 },
|
||||
borderColor: "cyan",
|
||||
borderColor: useColor ? "cyan" : undefined,
|
||||
borderStyle: "round",
|
||||
dimBorder: true,
|
||||
dimBorder: useColor,
|
||||
});
|
||||
}
|
||||
|
||||
const RECOMMEND_LABELS = ["Best Pick", "Runner-Up", "Alternative"];
|
||||
|
||||
function renderCard(rec: RecommendedModel, index: number, colorize: ChalkInstance): string {
|
||||
function renderCard(
|
||||
rec: RecommendedModel,
|
||||
index: number,
|
||||
colorize: ChalkInstance,
|
||||
useColor: boolean,
|
||||
): string {
|
||||
const labelColors = [colorize.green.bold, colorize.blue.bold, colorize.magenta.bold];
|
||||
const colorFn = labelColors[index] ?? colorize.white.bold;
|
||||
const label = RECOMMEND_LABELS[index] ?? `#${index + 1}`;
|
||||
@@ -165,19 +174,21 @@ function renderCard(rec: RecommendedModel, index: number, colorize: ChalkInstanc
|
||||
return boxen(lines.join("\n"), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
margin: { top: 0, bottom: 0, left: 1, right: 0 },
|
||||
borderColor: "gray",
|
||||
borderColor: useColor ? "gray" : undefined,
|
||||
borderStyle: "round",
|
||||
dimBorder: true,
|
||||
dimBorder: useColor,
|
||||
});
|
||||
}
|
||||
|
||||
function formatSingleResult(results: RecommendedModel[], noColor: boolean): string {
|
||||
const colorize = noColor ? new Chalk({ level: 0 }) : chalk;
|
||||
return results.map((rec, idx) => renderCard(rec, idx, colorize)).join("\n");
|
||||
function formatSingleResult(results: RecommendedModel[]): string {
|
||||
const colorize = chalkFor(process.stdout);
|
||||
const useColor = supportsColor(process.stdout);
|
||||
return results.map((rec, idx) => renderCard(rec, idx, colorize, useColor)).join("\n");
|
||||
}
|
||||
|
||||
function formatPipelineResult(summary: string, steps: PipelineStep[], noColor: boolean): string {
|
||||
const colorize = noColor ? new Chalk({ level: 0 }) : chalk;
|
||||
function formatPipelineResult(summary: string, steps: PipelineStep[]): string {
|
||||
const colorize = chalkFor(process.stdout);
|
||||
const useColor = supportsColor(process.stdout);
|
||||
const lines: string[] = [];
|
||||
lines.push(` ${colorize.yellow.bold("⚡ Pipeline")} ${summary}`);
|
||||
|
||||
@@ -192,17 +203,19 @@ function formatPipelineResult(summary: string, steps: PipelineStep[], noColor: b
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push(recommendations.map((rec, idx) => renderCard(rec, idx, colorize)).join("\n"));
|
||||
lines.push(
|
||||
recommendations.map((rec, idx) => renderCard(rec, idx, colorize, useColor)).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function formatResult(result: RecommendResult, noColor: boolean): string {
|
||||
function formatResult(result: RecommendResult): string {
|
||||
if (result.type === "pipeline") {
|
||||
return formatPipelineResult(result.summary, result.steps, noColor);
|
||||
return formatPipelineResult(result.summary, result.steps);
|
||||
}
|
||||
return formatSingleResult(result.recommendations, noColor);
|
||||
return formatSingleResult(result.recommendations);
|
||||
}
|
||||
|
||||
function isEmptyResult(result: RecommendResult): boolean {
|
||||
@@ -308,8 +321,8 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
emitBare(formatIntentSummary(intent, settings.noColor));
|
||||
emitBare(formatIntentSummary(intent));
|
||||
emitBare("");
|
||||
emitBare(formatResult(result, settings.noColor));
|
||||
emitBare(formatResult(result));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type AppStreamChunk,
|
||||
type AppCompletionResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
export default defineCommand({
|
||||
description: "Call a Bailian application (agent or workflow)",
|
||||
@@ -137,8 +137,7 @@ export default defineCommand({
|
||||
let fullText = "";
|
||||
let sessionId = "";
|
||||
const writesStreamingStdout = format === "text";
|
||||
const dim = settings.noColor ? "" : "\x1b[2m";
|
||||
const reset = settings.noColor ? "" : "\x1b[0m";
|
||||
const stderrColor = ansi(process.stderr);
|
||||
|
||||
for await (const event of parseSSE(res)) {
|
||||
if (event.data === "[DONE]") break;
|
||||
@@ -160,13 +159,14 @@ export default defineCommand({
|
||||
// Show thoughts if available
|
||||
if (chunk.output?.thoughts && flags.hasThoughts) {
|
||||
for (const t of chunk.output.thoughts) {
|
||||
if (t.thought) process.stderr.write(`${dim}[Thinking] ${t.thought}${reset}\n`);
|
||||
if (t.thought)
|
||||
process.stderr.write(`${stderrColor.dim(`[Thinking] ${t.thought}`)}\n`);
|
||||
if (t.action_name)
|
||||
process.stderr.write(
|
||||
`${dim}[Action] ${t.action_name}: ${t.action_input || ""}${reset}\n`,
|
||||
`${stderrColor.dim(`[Action] ${t.action_name}: ${t.action_input || ""}`)}\n`,
|
||||
);
|
||||
if (t.observation)
|
||||
process.stderr.write(`${dim}[Observation] ${t.observation}${reset}\n`);
|
||||
process.stderr.write(`${stderrColor.dim(`[Observation] ${t.observation}`)}\n`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -176,7 +176,7 @@ export default defineCommand({
|
||||
|
||||
// Show session_id for multi-turn conversation
|
||||
if (sessionId && !settings.quiet) {
|
||||
process.stderr.write(`${dim}Session ID: ${sessionId}${reset}\n`);
|
||||
process.stderr.write(`${stderrColor.dim(`Session ID: ${sessionId}`)}\n`);
|
||||
}
|
||||
|
||||
if (format === "json") {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
detectOutputFormat,
|
||||
type Client,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const MODEL_LIST_API = "zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels";
|
||||
@@ -170,12 +170,8 @@ interface CheckRow {
|
||||
tpmLimit: number;
|
||||
}
|
||||
|
||||
function printTable(rows: CheckRow[], noColor: boolean): void {
|
||||
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
|
||||
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
|
||||
const green = noColor ? (t: string) => t : (t: string) => `\x1b[32m${t}\x1b[0m`;
|
||||
const yellow = noColor ? (t: string) => t : (t: string) => `\x1b[33m${t}\x1b[0m`;
|
||||
const red = noColor ? (t: string) => t : (t: string) => `\x1b[31m${t}\x1b[0m`;
|
||||
function printTable(rows: CheckRow[]): void {
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
const headers = ["Model", "RPM Usage/Limit", "TPM Usage/Limit", "Status"];
|
||||
|
||||
@@ -202,8 +198,8 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
|
||||
Math.max(displayWidth(label), ...tableRows.map((r) => displayWidth(r.cells[col]))),
|
||||
);
|
||||
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => color.dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
@@ -212,16 +208,16 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
|
||||
for (const r of tableRows) {
|
||||
const cells = r.cells.map((cell, col) => {
|
||||
if (col === statusCol) {
|
||||
if (cell === "Rate Limited") return red(padEnd(cell, widths[col]));
|
||||
if (cell === "Near limit") return yellow(padEnd(cell, widths[col]));
|
||||
if (cell === "Normal") return green(padEnd(cell, widths[col]));
|
||||
if (cell === "Rate Limited") return color.red(padEnd(cell, widths[col]));
|
||||
if (cell === "Near limit") return color.yellow(padEnd(cell, widths[col]));
|
||||
if (cell === "Normal") return color.green(padEnd(cell, widths[col]));
|
||||
}
|
||||
return padEnd(cell, widths[col]);
|
||||
});
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\nTotal: ${rows.length} models`) + "\n");
|
||||
process.stdout.write(color.dim(`\nTotal: ${rows.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -313,6 +309,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(checkRows, settings.noColor);
|
||||
printTable(checkRows);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, BailianError, ExitCode } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const HISTORY_API = "zeldaEasy.broadscope-platform.modelInstance.listModelLimitApplications";
|
||||
@@ -53,9 +53,8 @@ function formatNumber(num: number): string {
|
||||
return num.toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function printTable(records: LimitApplicationItem[], noColor: boolean, total: number): void {
|
||||
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
|
||||
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
|
||||
function printTable(records: LimitApplicationItem[], total: number): void {
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
const headers = ["Model", "Token Limit", "Applied At"];
|
||||
|
||||
@@ -69,8 +68,8 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => color.dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
@@ -79,7 +78,7 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
|
||||
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\nTotal: ${total} records`) + "\n");
|
||||
process.stdout.write(color.dim(`\nTotal: ${total} records`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -157,6 +156,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(records, settings.noColor, modelFilter ? records.length : total);
|
||||
printTable(records, modelFilter ? records.length : total);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, BailianError, detectOutputFormat, type Client } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const MODEL_LIST_API = "zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels";
|
||||
@@ -91,9 +91,8 @@ async function fetchAllModelsWithQpm(
|
||||
return allModels;
|
||||
}
|
||||
|
||||
function printTable(models: ModelWithQpm[], noColor: boolean): void {
|
||||
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
|
||||
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
|
||||
function printTable(models: ModelWithQpm[]): void {
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
const headers = ["Model", "Req/min", "Token/min", "Max TPM"];
|
||||
|
||||
@@ -125,8 +124,8 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => color.dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
@@ -135,7 +134,7 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
|
||||
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\nTotal: ${models.length} models`) + "\n");
|
||||
process.stdout.write(color.dim(`\nTotal: ${models.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -217,6 +216,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(models, settings.noColor);
|
||||
printTable(models);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type FlagsDef,
|
||||
type ParsedFlags,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const CHAT_FLAGS = {
|
||||
@@ -180,12 +180,11 @@ export default defineCommand({
|
||||
let textContent = "";
|
||||
let inThinking = false;
|
||||
const writesStreamingStdout = format === "text";
|
||||
const dim = settings.noColor ? "" : "\x1b[2m";
|
||||
const reset = settings.noColor ? "" : "\x1b[0m";
|
||||
const isTTY = process.stdout.isTTY;
|
||||
const statusOut =
|
||||
format === "json" ? process.stderr : isTTY ? process.stdout : process.stderr;
|
||||
const resultOut = process.stdout;
|
||||
const statusColor = ansi(statusOut);
|
||||
|
||||
for await (const event of parseSSE(res)) {
|
||||
if (event.data === "[DONE]") break;
|
||||
@@ -199,7 +198,7 @@ export default defineCommand({
|
||||
if (delta.reasoning_content) {
|
||||
if (writesStreamingStdout && !inThinking) {
|
||||
inThinking = true;
|
||||
statusOut.write(`${dim}Thinking:\n`);
|
||||
statusOut.write(statusColor.dim("Thinking:\n"));
|
||||
}
|
||||
if (writesStreamingStdout) statusOut.write(delta.reasoning_content);
|
||||
}
|
||||
@@ -207,7 +206,7 @@ export default defineCommand({
|
||||
// Handle regular content
|
||||
if (delta.content) {
|
||||
if (writesStreamingStdout && inThinking) {
|
||||
statusOut.write(`${reset}\n\nResponse:\n`);
|
||||
statusOut.write(`${statusColor.reset}\n\nResponse:\n`);
|
||||
inThinking = false;
|
||||
}
|
||||
textContent += delta.content;
|
||||
@@ -218,7 +217,7 @@ export default defineCommand({
|
||||
// Skip unparseable chunks
|
||||
}
|
||||
}
|
||||
if (inThinking) statusOut.write(reset);
|
||||
if (inThinking) statusOut.write(statusColor.reset);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ content: textContent }, format);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { execSync } from "child_process";
|
||||
import { writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { defineCommand, getConfigDir } from "bailian-cli-core";
|
||||
import { fetchLatestVersion } from "bailian-cli-runtime";
|
||||
import { ansi, fetchLatestVersion, type AnsiStyles } from "bailian-cli-runtime";
|
||||
|
||||
const SKILL_SOURCE = "modelstudioai/cli";
|
||||
const SKILL_INSTALL_CMD = `npx skills add ${SKILL_SOURCE} --all -g -y`;
|
||||
@@ -12,17 +12,16 @@ function detectInstallCommand(npmPackage: string): { cmd: string; label: string
|
||||
return { cmd: `npm install -g ${npmPackage}@latest`, label: "npm" };
|
||||
}
|
||||
|
||||
function updateAgentSkill(colors: { green: string; yellow: string; reset: string }): void {
|
||||
const { green, yellow, reset } = colors;
|
||||
function updateAgentSkill(color: AnsiStyles): void {
|
||||
process.stderr.write("\nUpdating agent skill...\n");
|
||||
try {
|
||||
// Reinstall (not `skills update`) into ~/.agents/skills/ and sync to all agent apps.
|
||||
// `--all` on `skills add` means --skill '*' --agent '*' -y (Cursor, Claude Code, etc.).
|
||||
execSync(SKILL_INSTALL_CMD, { stdio: "inherit" });
|
||||
process.stderr.write(`${green}\u2713 Agent skill updated.${reset}\n`);
|
||||
process.stderr.write(`${color.green("\u2713 Agent skill updated.")}\n`);
|
||||
} catch {
|
||||
process.stderr.write(
|
||||
`${yellow}Agent skill update skipped. Run manually: ${SKILL_INSTALL_CMD}${reset}\n`,
|
||||
`${color.yellow(`Agent skill update skipped. Run manually: ${SKILL_INSTALL_CMD}`)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,25 +35,22 @@ export default defineCommand({
|
||||
const npmPackage = identity.npmPackage;
|
||||
const binName = identity.binName;
|
||||
const currentVersion = identity.version;
|
||||
const isTTY = process.stderr.isTTY;
|
||||
const green = isTTY ? "\x1b[32m" : "";
|
||||
const yellow = isTTY ? "\x1b[33m" : "";
|
||||
const reset = isTTY ? "\x1b[0m" : "";
|
||||
const color = ansi(process.stderr);
|
||||
|
||||
process.stderr.write(`Current version: ${yellow}${currentVersion}${reset}\n`);
|
||||
process.stderr.write(`Current version: ${color.yellow(currentVersion)}\n`);
|
||||
|
||||
// Check latest version first
|
||||
process.stderr.write("Checking for updates...\n");
|
||||
const latest = await fetchLatestVersion(5000, npmPackage);
|
||||
|
||||
if (latest && latest === currentVersion) {
|
||||
process.stderr.write(`${green}\u2713 Already up to date (${currentVersion}).${reset}\n`);
|
||||
updateAgentSkill({ green, yellow, reset });
|
||||
process.stderr.write(`${color.green(`\u2713 Already up to date (${currentVersion}).`)}\n`);
|
||||
updateAgentSkill(color);
|
||||
return;
|
||||
}
|
||||
|
||||
if (latest) {
|
||||
process.stderr.write(`Latest version: ${green}${latest}${reset}\n\n`);
|
||||
process.stderr.write(`Latest version: ${color.green(latest)}\n\n`);
|
||||
}
|
||||
|
||||
const { cmd, label } = detectInstallCommand(npmPackage);
|
||||
@@ -68,7 +64,7 @@ export default defineCommand({
|
||||
// `<bin> --version` outputs "<bin> X.Y.Z" — extract just the version number
|
||||
const newVer = rawVer.replace(new RegExp(`^${binName}\\s+`), "");
|
||||
process.stderr.write(
|
||||
`\n${green}\u2713 Update complete: ${currentVersion} \u2192 ${newVer}${reset}\n`,
|
||||
`\n${color.green(`\u2713 Update complete: ${currentVersion} \u2192 ${newVer}`)}\n`,
|
||||
);
|
||||
// Update the cached state so the post-run notification doesn't fire
|
||||
try {
|
||||
@@ -81,9 +77,9 @@ export default defineCommand({
|
||||
/* ignore */
|
||||
}
|
||||
} catch {
|
||||
process.stderr.write(`\n${green}\u2713 Update complete.${reset}\n`);
|
||||
process.stderr.write(`\n${color.green("\u2713 Update complete.")}\n`);
|
||||
}
|
||||
updateAgentSkill({ green, yellow, reset });
|
||||
updateAgentSkill(color);
|
||||
} catch {
|
||||
process.stderr.write("\nAutomatic update failed. Please run manually:\n");
|
||||
process.stderr.write(` ${cmd}\n\n`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, fetchModelList, type Client } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const FREE_TIER_API = "zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota";
|
||||
@@ -68,8 +68,8 @@ function printTable(
|
||||
quotas: FreeTierQuota[],
|
||||
stopMap: Map<string, boolean>,
|
||||
typeMap: Map<string, string>,
|
||||
noColor: boolean,
|
||||
): void {
|
||||
const color = ansi(process.stdout);
|
||||
const headers = ["Model", "Type", "Remaining/Total", "Usage", "Expires", "Auto-Stop"];
|
||||
|
||||
const rows = quotas.map((quota) => {
|
||||
@@ -97,14 +97,9 @@ function printTable(
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
const bold = noColor ? (text: string) => text : (text: string) => `\x1b[1m${text}\x1b[0m`;
|
||||
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
|
||||
const yellow = noColor ? (text: string) => text : (text: string) => `\x1b[33m${text}\x1b[0m`;
|
||||
|
||||
const autoStopCol = headers.length - 1;
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => color.dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
@@ -112,8 +107,8 @@ function printTable(
|
||||
for (const row of rows) {
|
||||
const cells = row.map((cell, col) => {
|
||||
if (col === autoStopCol) {
|
||||
if (cell === "ON") return green(padEnd(cell, widths[col]));
|
||||
if (cell === "OFF") return yellow(padEnd(cell, widths[col]));
|
||||
if (cell === "ON") return color.green(padEnd(cell, widths[col]));
|
||||
if (cell === "OFF") return color.yellow(padEnd(cell, widths[col]));
|
||||
}
|
||||
return padEnd(cell, widths[col]);
|
||||
});
|
||||
@@ -333,6 +328,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(quotas, stopMap, typeMap, settings.noColor);
|
||||
printTable(quotas, stopMap, typeMap);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type Settings,
|
||||
type Client,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const OVERVIEW_API = "zeldaEasy.bailian-telemetry.model.getModelUsageStatistic";
|
||||
@@ -181,13 +181,11 @@ function printOverview(
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
days: number,
|
||||
noColor: boolean,
|
||||
): void {
|
||||
const bold = noColor ? (text: string) => text : (text: string) => `\x1b[1m${text}\x1b[0m`;
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
process.stdout.write(
|
||||
`${dim("Time Range Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
|
||||
`${color.dim("Time Range Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${color.dim(`(${days} days)`)}\n\n`,
|
||||
);
|
||||
|
||||
const rows: [string, string][] = [
|
||||
@@ -203,7 +201,7 @@ function printOverview(
|
||||
|
||||
const maxLabel = Math.max(...rows.map(([label]) => displayWidth(label)));
|
||||
for (const [label, value] of rows) {
|
||||
process.stdout.write(`${bold(padEnd(label, maxLabel + 2))}${value}\n`);
|
||||
process.stdout.write(`${color.bold(padEnd(label, maxLabel + 2))}${value}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,13 +210,11 @@ function printModelTable(
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
days: number,
|
||||
noColor: boolean,
|
||||
): void {
|
||||
const bold = noColor ? (text: string) => text : (text: string) => `\x1b[1m${text}\x1b[0m`;
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
process.stdout.write(
|
||||
`${dim("Time Range Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
|
||||
`${color.dim("Time Range Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${color.dim(`(${days} days)`)}\n\n`,
|
||||
);
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -263,8 +259,8 @@ function printModelTable(
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => color.dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
@@ -274,7 +270,7 @@ function printModelTable(
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\nTotal: ${items.length} models`) + "\n");
|
||||
process.stdout.write(color.dim(`\nTotal: ${items.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -382,7 +378,7 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printModelTable(allItems, startTime, endTime, daysFlag, settings.noColor);
|
||||
printModelTable(allItems, startTime, endTime, daysFlag);
|
||||
} else {
|
||||
const reqDTO: Record<string, unknown> = {
|
||||
startTime,
|
||||
@@ -438,7 +434,7 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printOverview(stat, startTime, endTime, daysFlag, settings.noColor);
|
||||
printOverview(stat, startTime, endTime, daysFlag);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { ansi, emitResult } from "bailian-cli-runtime";
|
||||
import { displayWidth, padEnd } from "bailian-cli-runtime";
|
||||
|
||||
const LIST_WORKSPACES_API = "zeldaEasy.bailian-dash-workspace.space.listWorkspaces";
|
||||
@@ -34,10 +34,8 @@ function extractResponseData(result: Record<string, unknown>): Record<string, un
|
||||
return direct ?? data;
|
||||
}
|
||||
|
||||
function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
|
||||
const bold = noColor ? (text: string) => text : (text: string) => `\x1b[1m${text}\x1b[0m`;
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
|
||||
function printTable(workspaces: WorkspaceInfo[]): void {
|
||||
const color = ansi(process.stdout);
|
||||
|
||||
const headers = ["Name", "Workspace ID", "Default"];
|
||||
|
||||
@@ -51,21 +49,21 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const headerLine = headers.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
const headerLine = headers.map((label, col) => color.bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => color.dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
for (const row of rows) {
|
||||
const cells = row.map((cell, col) => {
|
||||
if (col === 2 && cell === "Yes") return green(padEnd(cell, widths[col]));
|
||||
if (col === 2 && cell === "Yes") return color.green(padEnd(cell, widths[col]));
|
||||
return padEnd(cell, widths[col]);
|
||||
});
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\nTotal: ${workspaces.length}`) + "\n");
|
||||
process.stdout.write(color.dim(`\nTotal: ${workspaces.length}`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -116,6 +114,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(workspaces, settings.noColor);
|
||||
printTable(workspaces);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -75,7 +75,6 @@ export function buildSettings(s: ResolutionSources): Settings {
|
||||
consoleSwitchAgent: flags.consoleSwitchAgent || file.console_switch_agent || undefined,
|
||||
verbose: flags.verbose || env.DASHSCOPE_VERBOSE === "1",
|
||||
quiet: flags.quiet || false,
|
||||
noColor: flags.noColor || env.NO_COLOR !== undefined || !process.stdout.isTTY,
|
||||
dryRun: flags.dryRun || false,
|
||||
telemetry: env.DO_NOT_TRACK === "1" ? false : (file.telemetry ?? true),
|
||||
};
|
||||
|
||||
@@ -123,7 +123,6 @@ export interface Settings {
|
||||
consoleSwitchAgent?: number;
|
||||
verbose: boolean;
|
||||
quiet: boolean;
|
||||
noColor: boolean;
|
||||
dryRun: boolean;
|
||||
telemetry: boolean;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ const GLOBAL_FLAG_KEYS = new Set([
|
||||
"quiet",
|
||||
"verbose",
|
||||
"timeout",
|
||||
"noColor",
|
||||
"dryRun",
|
||||
"help",
|
||||
"console",
|
||||
|
||||
@@ -70,7 +70,6 @@ export const GLOBAL_FLAGS = {
|
||||
timeout: { type: "number", valueHint: "<seconds>", description: "Request timeout" },
|
||||
quiet: { type: "switch", description: "Suppress non-essential output" },
|
||||
verbose: { type: "switch", description: "Print HTTP request/response details" },
|
||||
noColor: { type: "switch", description: "Disable ANSI colors" },
|
||||
dryRun: { type: "switch", description: "Dry run mode" },
|
||||
help: { type: "switch", description: "Show help" },
|
||||
version: { type: "switch", description: "Print version" },
|
||||
|
||||
@@ -91,11 +91,6 @@ test("telemetry:DO_NOT_TRACK=1 一票否决 > file > 默认 true", () => {
|
||||
expect(resolve({}).telemetry).toBe(true);
|
||||
});
|
||||
|
||||
test("noColor:NO_COLOR 只看存在性(空串也算);非 TTY 下恒为 true", () => {
|
||||
expect(resolve({ env: { NO_COLOR: "" } }).noColor).toBe(true);
|
||||
if (!process.stdout.isTTY) expect(resolve({}).noColor).toBe(true);
|
||||
});
|
||||
|
||||
test("apiKey 凭证:flag > env > file,source 字段随之;无 key 抛 AUTH", () => {
|
||||
const all = src({
|
||||
flags: { apiKey: "sk-flag" },
|
||||
|
||||
@@ -22,7 +22,6 @@ function testDeps(identity: Partial<Identity> = {}): { identity: Identity; setti
|
||||
timeout: 30,
|
||||
verbose: false,
|
||||
quiet: true,
|
||||
noColor: true,
|
||||
dryRun: false,
|
||||
telemetry: true,
|
||||
},
|
||||
|
||||
@@ -30,6 +30,13 @@ export { createSpinner, createProgressBar } from "./output/progress.ts";
|
||||
export { printWelcomeBanner, printQuickStart } from "./output/banner.ts";
|
||||
export { maybeShowStatusBar } from "./output/status-bar.ts";
|
||||
export { displayWidth, padEnd } from "./output/cjk-width.ts";
|
||||
export {
|
||||
ansi,
|
||||
isTerminal,
|
||||
supportsColor,
|
||||
type AnsiStyles,
|
||||
type TextStyle,
|
||||
} from "./output/color.ts";
|
||||
|
||||
// Utility facilities consumed by commands
|
||||
export { poll } from "./utils/polling.ts";
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
trackCommandExecution,
|
||||
} from "bailian-cli-core";
|
||||
import { maybeShowStatusBar } from "./output/status-bar.ts";
|
||||
import { ansi } from "./output/color.ts";
|
||||
import { checkForUpdate, getPendingUpdateNotification } from "./utils/update-checker.ts";
|
||||
|
||||
/**
|
||||
@@ -116,14 +117,11 @@ export const versionCheckStage: Middleware = async (ctx, next) => {
|
||||
const isUpdateCommand = ctx.path.length === 1 && ctx.path[0] === "update";
|
||||
const newVersion = getPendingUpdateNotification();
|
||||
if (newVersion && !ctx.settings.quiet && !isUpdateCommand) {
|
||||
const isTTY = process.stderr.isTTY;
|
||||
const yellow = isTTY ? "\x1b[33m" : "";
|
||||
const cyan = isTTY ? "\x1b[36m" : "";
|
||||
const reset = isTTY ? "\x1b[0m" : "";
|
||||
const color = ansi(process.stderr);
|
||||
process.stderr.write(
|
||||
`\n ${yellow}Update available: ${ctx.identity.version} → ${newVersion}${reset}\n`,
|
||||
`\n ${color.yellow(`Update available: ${ctx.identity.version} → ${newVersion}`)}\n`,
|
||||
);
|
||||
process.stderr.write(` Run ${cyan}${ctx.identity.binName} update${reset} to upgrade\n\n`);
|
||||
process.stderr.write(` Run ${color.cyan(`${ctx.identity.binName} update`)} to upgrade\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { API_KEY_PAGE } from "../urls.ts";
|
||||
import { ansi } from "./color.ts";
|
||||
|
||||
const QUICK_START_TASKS = [
|
||||
"Help me generate a set of Amazon e-commerce main images for baseball caps (white background + lifestyle shots + model wear shots)",
|
||||
@@ -7,28 +8,19 @@ const QUICK_START_TASKS = [
|
||||
"Help me analyze this video and write a Xiaohongshu-style post",
|
||||
];
|
||||
|
||||
function colors() {
|
||||
const isTTY = process.stderr.isTTY;
|
||||
return {
|
||||
purple: isTTY ? "\x1b[38;2;147;51;234m" : "",
|
||||
dim: isTTY ? "\x1b[2m" : "",
|
||||
reset: isTTY ? "\x1b[0m" : "",
|
||||
};
|
||||
}
|
||||
|
||||
export function printWelcomeBanner(cliName: string): void {
|
||||
const { purple, reset } = colors();
|
||||
process.stderr.write(`\n Welcome to ${purple}Bailian${reset} CLI!\n\n`);
|
||||
const color = ansi(process.stderr);
|
||||
process.stderr.write(`\n Welcome to ${color.purple("Bailian")} CLI!\n\n`);
|
||||
process.stderr.write(" Get started in 2 steps:\n");
|
||||
process.stderr.write(` 1. Get your API Key: ${API_KEY_PAGE}\n`);
|
||||
process.stderr.write(` 2. Login: ${cliName} auth login --api-key <your-key>\n\n`);
|
||||
}
|
||||
|
||||
export function printQuickStart(): void {
|
||||
const { dim, reset } = colors();
|
||||
const color = ansi(process.stderr);
|
||||
process.stderr.write("\n🎯 Try these with your AI coding assistant:\n\n");
|
||||
QUICK_START_TASKS.forEach((task, i) => {
|
||||
process.stderr.write(`${dim}${i + 1}${reset} ${task}\n`);
|
||||
process.stderr.write(`${color.dim(String(i + 1))} ${task}\n`);
|
||||
});
|
||||
process.stderr.write("\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
export type TextStyle = (text: string) => string;
|
||||
|
||||
export interface AnsiStyles {
|
||||
bold: TextStyle;
|
||||
dim: TextStyle;
|
||||
green: TextStyle;
|
||||
yellow: TextStyle;
|
||||
red: TextStyle;
|
||||
cyan: TextStyle;
|
||||
blue: TextStyle;
|
||||
magenta: TextStyle;
|
||||
white: TextStyle;
|
||||
accent: TextStyle;
|
||||
logo: TextStyle;
|
||||
purple: TextStyle;
|
||||
brandBlue: TextStyle;
|
||||
keyPink: TextStyle;
|
||||
reset: string;
|
||||
}
|
||||
|
||||
const plain: TextStyle = (text) => text;
|
||||
|
||||
function wrap(enabled: boolean, code: string): TextStyle {
|
||||
return enabled ? (text) => `\x1b[${code}m${text}\x1b[0m` : plain;
|
||||
}
|
||||
|
||||
export function isTerminal(out: NodeJS.WriteStream): boolean {
|
||||
return out.isTTY === true;
|
||||
}
|
||||
|
||||
export function supportsColor(out: NodeJS.WriteStream): boolean {
|
||||
return !("NO_COLOR" in process.env) && isTerminal(out);
|
||||
}
|
||||
|
||||
export function ansi(out: NodeJS.WriteStream): AnsiStyles {
|
||||
const enabled = supportsColor(out);
|
||||
return {
|
||||
bold: wrap(enabled, "1"),
|
||||
dim: wrap(enabled, "2"),
|
||||
green: wrap(enabled, "32"),
|
||||
yellow: wrap(enabled, "33"),
|
||||
red: wrap(enabled, "31"),
|
||||
cyan: wrap(enabled, "36"),
|
||||
blue: wrap(enabled, "34"),
|
||||
magenta: wrap(enabled, "35"),
|
||||
white: wrap(enabled, "37"),
|
||||
accent: wrap(enabled, "38;2;59;130;246"),
|
||||
logo: wrap(enabled, "38;2;97;92;237"),
|
||||
purple: wrap(enabled, "38;2;147;51;234"),
|
||||
brandBlue: wrap(enabled, "1;38;2;43;82;255"),
|
||||
keyPink: wrap(enabled, "38;2;236;72;153"),
|
||||
reset: enabled ? "\x1b[0m" : "",
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
import { homedir } from "os";
|
||||
import { maskToken, type Settings, type ApiKeyCredential } from "bailian-cli-core";
|
||||
|
||||
const reset = "\x1b[0m";
|
||||
const dim = "\x1b[2m";
|
||||
const bold = "\x1b[1m";
|
||||
const mmBlue = "\x1b[38;2;43;82;255m";
|
||||
const mmPink = "\x1b[38;2;236;72;153m";
|
||||
import { ansi, isTerminal } from "./color.ts";
|
||||
|
||||
function tildePath(p: string): string {
|
||||
return p.startsWith(homedir()) ? p.replace(homedir(), "~") : p;
|
||||
@@ -16,16 +11,17 @@ export function maybeShowStatusBar(
|
||||
token: string,
|
||||
resolved: ApiKeyCredential,
|
||||
): void {
|
||||
if (settings.quiet || !process.stderr.isTTY) return;
|
||||
if (settings.quiet || !isTerminal(process.stderr)) return;
|
||||
|
||||
const filePath = settings.configPath ? tildePath(settings.configPath) : "~/.bailian/config.json";
|
||||
const authTag = `${resolved.source} · api-key`;
|
||||
const maskedKey = maskToken(token);
|
||||
const color = ansi(process.stderr);
|
||||
|
||||
process.stderr.write(
|
||||
`${bold}${mmBlue}BAILIAN${reset} ` +
|
||||
`${dim}${filePath}${reset} ` +
|
||||
`${dim}|${reset} ` +
|
||||
`${dim}Auth:${reset} ${mmPink}${maskedKey}${reset} ${dim}${authTag}${reset}\n`,
|
||||
`${color.brandBlue("BAILIAN")} ` +
|
||||
`${color.dim(filePath)} ` +
|
||||
`${color.dim("|")} ` +
|
||||
`${color.dim("Auth:")} ${color.keyPink(maskedKey)} ${color.dim(authTag)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export function buildPipelineEnv(): PipelineEnv {
|
||||
const settings: Settings = {
|
||||
...buildSettings(sources),
|
||||
output: "json",
|
||||
noColor: true,
|
||||
quiet: true,
|
||||
};
|
||||
const identity: Identity = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
credentialFlagDefs,
|
||||
} from "bailian-cli-core";
|
||||
import { camelToKebab } from "./args.ts";
|
||||
import { ansi } from "./output/color.ts";
|
||||
|
||||
export type { Command, AnyCommand, FlagDef, FlagsDef } from "bailian-cli-core";
|
||||
|
||||
@@ -186,11 +187,10 @@ export class CommandRegistry {
|
||||
return lines.map((l) => ` ${a(l.flag.padEnd(maxLen + 2))} ${d(l.desc)}`).join("\n");
|
||||
}
|
||||
|
||||
// Color helpers — no-ops when output is not a TTY
|
||||
private bold = (s: string, out: NodeJS.WriteStream) => (out.isTTY ? `\x1b[1m${s}\x1b[0m` : s);
|
||||
private accent = (s: string, out: NodeJS.WriteStream) =>
|
||||
out.isTTY ? `\x1b[38;2;59;130;246m${s}\x1b[0m` : s;
|
||||
private dim = (s: string, out: NodeJS.WriteStream) => (out.isTTY ? `\x1b[2m${s}\x1b[0m` : s);
|
||||
// Color helpers — no-ops when output is not a TTY.
|
||||
private bold = (s: string, out: NodeJS.WriteStream) => ansi(out).bold(s);
|
||||
private accent = (s: string, out: NodeJS.WriteStream) => ansi(out).accent(s);
|
||||
private dim = (s: string, out: NodeJS.WriteStream) => ansi(out).dim(s);
|
||||
|
||||
printHelp(commandPath: string[], out: NodeJS.WriteStream = process.stdout): void {
|
||||
if (commandPath.length === 0) {
|
||||
@@ -254,16 +254,11 @@ ${d(` ${this.cliName} pipeline run workflow.yaml --dry-run --output json`)}
|
||||
"██████╔╝██║ ██║██║███████╗██║██║ ██║██║ ╚████║",
|
||||
"╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
||||
];
|
||||
const PURPLE = "\x1b[38;2;97;92;237m";
|
||||
const RESET = "\x1b[0m";
|
||||
const color = ansi(out);
|
||||
|
||||
out.write("\n");
|
||||
for (const line of LOGO) {
|
||||
if (out.isTTY) {
|
||||
out.write(`${PURPLE}${line}${RESET}\n`);
|
||||
} else {
|
||||
out.write(line + "\n");
|
||||
}
|
||||
out.write(`${color.logo(line)}\n`);
|
||||
}
|
||||
|
||||
const b = (s: string) => this.bold(s, out);
|
||||
|
||||
@@ -34,6 +34,15 @@ Auto-generated from the CLI source at build time. Before running an unfamiliar c
|
||||
|
||||
Do not guess flags — use the reference files or `--help`.
|
||||
|
||||
### Color output
|
||||
|
||||
When an agent needs plain text without ANSI color codes (for parsing, logs, or
|
||||
snapshots), run the command with `NO_COLOR=1`:
|
||||
|
||||
```bash
|
||||
NO_COLOR=1 bl config show --output text
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to use which command
|
||||
|
||||
@@ -92,7 +92,6 @@ Available on every command (in addition to command-specific flags):
|
||||
| `--timeout <seconds>` | number | no | Request timeout |
|
||||
| `--quiet` | switch | no | Suppress non-essential output |
|
||||
| `--verbose` | switch | no | Print HTTP request/response details |
|
||||
| `--no-color` | switch | no | Disable ANSI colors |
|
||||
| `--dry-run` | switch | no | Dry run mode |
|
||||
| `--help` | switch | no | Show help |
|
||||
| `--version` | switch | no | Print version |
|
||||
|
||||
Reference in New Issue
Block a user