test-cli-usability
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest Your CLI's Agent Usability
测试 CLI 的 Agent 可用性
This recipe helps you write scenario tests that verify your CLI tool works well when operated by AI agents (Claude Code, Cursor, Codex, etc.). A CLI that's agent-friendly means:
- All commands can run non-interactively (no stdin prompts that hang)
- Output is parseable and informative
- Error messages are clear enough for an agent to self-correct
- Help text enables discovery (works on every subcommand)
--help
本实践指南帮助你编写场景测试,验证 CLI 工具在被 AI Agent(Claude Code、Cursor、Codex 等)操作时能否正常工作。对 Agent 友好的 CLI 意味着:
- 所有命令都可以非交互式运行(不存在会阻塞的 stdin 提示)
- 输出可解析且信息明确
- 错误消息足够清晰,使 Agent 能够自行纠正
- 帮助文本支持功能发现(每个子命令都可以使用 )
--help
Prerequisites
前置条件
Install the Scenario SDK:
bash
npm install @langwatch/scenario vitest @ai-sdk/openai安装 Scenario SDK:
bash
npm install @langwatch/scenario vitest @ai-sdk/openaior: pip install langwatch-scenario pytest
or: pip install langwatch-scenario pytest
undefinedundefinedStep 1: Identify Your CLI Commands
第 1 步:识别 CLI 命令
List every command your CLI supports. For each, note:
- Does it require interactive input? (MUST have a non-interactive alternative)
- What flags/options does it accept?
- What does it output on success/failure?
列出 CLI 支持的所有命令。对于每个命令,记录:
- 是否需要交互式输入?(必须提供非交互式替代方案)
- 接受哪些 flags/options?
- 成功或失败时会输出什么?
Step 2: Write Scenario Tests
第 2 步:编写场景测试
For each command, write a scenario test where an AI agent discovers and uses it:
typescript
import scenario, { type AgentAdapter, AgentRole } from "@langwatch/scenario";
import { openai } from "@ai-sdk/openai";
import { describe, expect, it } from "vitest";
const myAgent: AgentAdapter = {
role: AgentRole.AGENT,
call: async (input) => {
// Your Claude Code adapter here
},
};
describe("CLI agent usability", () => {
it("discovers and uses the command non-interactively", async () => {
const result = await scenario.run({
name: "CLI command discovery",
description: "Agent discovers and uses the CLI to accomplish a task",
agents: [
myAgent,
scenario.userSimulatorAgent({ model: openai("gpt-5-mini") }),
scenario.judgeAgent({
model: openai("gpt-5-mini"),
criteria: [
"Agent used the CLI command correctly",
"Agent did not get stuck on interactive prompts",
"Agent did not need to pipe 'yes' or use 'expect' scripting",
],
}),
],
});
expect(result.success).toBe(true);
});
});为每个命令编写一个场景测试,让 AI Agent 发现并使用它:
typescript
import scenario, { type AgentAdapter, AgentRole } from "@langwatch/scenario";
import { openai } from "@ai-sdk/openai";
import { describe, expect, it } from "vitest";
const myAgent: AgentAdapter = {
role: AgentRole.AGENT,
call: async (input) => {
// Your Claude Code adapter here
},
};
describe("CLI agent usability", () => {
it("discovers and uses the command non-interactively", async () => {
const result = await scenario.run({
name: "CLI command discovery",
description: "Agent discovers and uses the CLI to accomplish a task",
agents: [
myAgent,
scenario.userSimulatorAgent({ model: openai("gpt-5-mini") }),
scenario.judgeAgent({
model: openai("gpt-5-mini"),
criteria: [
"Agent used the CLI command correctly",
"Agent did not get stuck on interactive prompts",
"Agent did not need to pipe 'yes' or use 'expect' scripting",
],
}),
],
});
expect(result.success).toBe(true);
});
});Step 3: Assert No Interactive Workarounds
第 3 步:断言未使用交互式变通方案
Add this assertion to every test:
typescript
function assertNoInteractiveWorkarounds(state) {
const output = state.messages.map(m =>
typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
).join('\n');
expect(output).not.toMatch(/echo\s+["']?[yY](?:es)?["']?\s*\|/);
expect(output).not.toMatch(/\byes\s*\|/);
expect(output).not.toMatch(/expect\s+-c/);
expect(output).not.toMatch(/printf\s+["']\\n["']\s*\|/);
}If this assertion fails, your CLI has an interactivity bug -- add , , or flags to the offending commands.
--yes--force--non-interactive将此断言添加到每个测试中:
typescript
function assertNoInteractiveWorkarounds(state) {
const output = state.messages.map(m =>
typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
).join('\n');
expect(output).not.toMatch(/echo\s+["']?[yY](?:es)?["']?\s*\|/);
expect(output).not.toMatch(/\byes\s*\|/);
expect(output).not.toMatch(/expect\s+-c/);
expect(output).not.toMatch(/printf\s+["']\\n["']\s*\|/);
}如果此断言失败,说明你的 CLI 存在交互式问题——请为有问题的命令添加 、 或 flag。
--yes--force--non-interactiveStep 4: Test Error Recovery
第 4 步:测试错误恢复
Write scenarios where the agent makes a mistake and must recover:
- Wrong command name -> agent reads and self-corrects
--help - Missing required argument -> agent reads error message and retries
- Authentication failure -> agent follows instructions in error output
编写 Agent 犯错后必须自行恢复的场景:
- 命令名错误 -> Agent 读取 并自行纠正
--help - 缺少必需参数 -> Agent 读取错误消息并重试
- 身份验证失败 -> Agent 按照错误输出中的说明操作
Common Mistakes
常见错误
- Do NOT make commands that require stdin for essential operations -- always provide flag alternatives
- Do NOT use interactive prompts for confirmation without a or
--yesflag--force - Do NOT output errors without actionable guidance (the agent needs to know how to fix it)
- DO make comprehensive on every subcommand
--help - DO use non-zero exit codes for failures (agents check exit codes)
- DO output structured information (the agent can parse it)
- 不要让命令的关键操作依赖 stdin——始终提供 flag 替代方案
- 不要在没有 或
--yesflag 的情况下使用交互式确认提示--force - 不要输出缺少可操作指导的错误信息(Agent 需要知道如何修复)
- 务必确保每个子命令的 内容全面
--help - 务必在失败时使用非零退出码(Agent 会检查退出码)
- 务必输出结构化信息(便于 Agent 解析)