Skip to content

Commit 346ecee

Browse files
committed
refactor(cli): 重构命令行参数解析并优化启动流程
- 使用 yargs 库替代原有手写解析,增强参数解析的健壮性和可维护性 - 添加严格参数校验,提升错误提示的清晰度 - 统一处理 --resume 和 --prompt 参数的组合逻辑,避免冲突使用 - 改进启动流程,确保先恢复会话再提交初始提示 - 将 resetStaticView 方法修改为异步以支持启动流程等待 - 替换部分异步调用为 await 确保顺序执行,避免竞态问题 - 更新 CLI 帮助文案,优化用户体验和信息表达 - 调整欢迎界面文本样式,增加标题加粗显示 - 添加相关单元测试 covering 新的参数解析和校验逻辑 - 引入 yargs 及其类型依赖,更新 package.json 和锁文件依赖清单
1 parent f4ded9a commit 346ecee

8 files changed

Lines changed: 487 additions & 152 deletions

File tree

package-lock.json

Lines changed: 143 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
"ignore": "^7.0.5",
3838
"ink": "^7.0.4",
3939
"ink-gradient": "^4.0.1",
40-
"react": "^19.2.5"
40+
"react": "^19.2.5",
41+
"yargs": "^18.0.0"
42+
},
43+
"devDependencies": {
44+
"@types/yargs": "^17.0.35"
4145
}
4246
}

packages/cli/src/cli-args.ts

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,102 @@
11
/**
22
* CLI argument parsing helpers.
3-
* Extracted from cli.tsx for testability.
3+
* Uses yargs for robust argument parsing and validation.
44
*/
55

6-
export function extractInitialPrompt(args: string[]): string | undefined {
7-
const promptIndex = args.findIndex((arg) => arg === "-p" || arg === "--prompt");
8-
if (promptIndex !== -1 && promptIndex + 1 < args.length) {
9-
return args[promptIndex + 1];
10-
}
11-
return undefined;
6+
import Yargs from "yargs";
7+
8+
export interface ParsedCliArgs {
9+
/** Prompt text from -p / --prompt */
10+
prompt: string | undefined;
11+
/**
12+
* Resume session identifier:
13+
* - `undefined` — --resume was not used
14+
* - `true` — --resume was used without a session ID (show picker)
15+
* - `string` — --resume <sessionId> was used
16+
*/
17+
resume: string | true | undefined;
18+
/** True when --version / -v was passed */
19+
version: boolean;
20+
/** True when --help / -h was passed */
21+
help: boolean;
22+
}
23+
24+
export interface CliParseError {
25+
message: string;
1226
}
1327

1428
/**
15-
* Extract the --resume flag value.
16-
*
17-
* Returns:
18-
* - `undefined` — `--resume` was not used
19-
* - `true` — `--resume` was used without a session ID (show session picker)
20-
* - `string` — `--resume <sessionId>` was used (resume specific session)
29+
* Parse CLI arguments with validation.
30+
* Returns parsed args on success, or an error object if the arguments are invalid.
2131
*/
22-
export function extractResumeSessionId(args: string[]): string | true | undefined {
23-
const idx = args.findIndex((arg) => arg === "--resume");
24-
if (idx === -1) {
25-
return undefined;
32+
export function parseCliArgs(argv: string[]): ParsedCliArgs | CliParseError {
33+
let validationError: string | null = null;
34+
35+
const y = Yargs(argv)
36+
.locale("en")
37+
.scriptName("deepcode")
38+
.version(false)
39+
.help(false)
40+
.option("version", {
41+
alias: "v",
42+
type: "boolean",
43+
describe: "Print the version",
44+
})
45+
.option("help", {
46+
alias: "h",
47+
type: "boolean",
48+
describe: "Show this help",
49+
})
50+
.option("resume", {
51+
alias: "r",
52+
type: "string",
53+
describe: "Resume a specific session by its ID. Use without an ID to show session picker.",
54+
})
55+
.option("prompt", {
56+
alias: "p",
57+
type: "string",
58+
describe: "Submit a prompt on launch",
59+
})
60+
.strict()
61+
.exitProcess(false)
62+
.fail((msg) => {
63+
validationError = msg;
64+
})
65+
.check((parsed) => {
66+
// bare --resume conflicts with --prompt
67+
if (parsed.resume === "" && parsed.prompt) {
68+
throw new Error(
69+
"Cannot use --resume without a session ID together with --prompt.\n" +
70+
"Use --resume <sessionId> -p <prompt> to resume a session and send a prompt."
71+
);
72+
}
73+
// empty prompt is meaningless
74+
if (parsed.prompt === "") {
75+
throw new Error("--prompt / -p requires a non-empty value.");
76+
}
77+
return true;
78+
});
79+
80+
const parsed = y.parseSync() as Record<string, unknown>;
81+
82+
if (validationError) {
83+
return { message: validationError };
2684
}
27-
if (idx + 1 < args.length && !args[idx + 1].startsWith("-")) {
28-
return args[idx + 1];
85+
86+
const resumeRaw = parsed.resume as string | undefined;
87+
let resume: ParsedCliArgs["resume"];
88+
if (resumeRaw === undefined) {
89+
resume = undefined;
90+
} else if (resumeRaw === "") {
91+
resume = true;
92+
} else {
93+
resume = resumeRaw;
2994
}
30-
return true;
95+
96+
return {
97+
prompt: parsed.prompt as string | undefined,
98+
resume,
99+
version: parsed.version === true,
100+
help: parsed.help === true,
101+
};
31102
}

0 commit comments

Comments
 (0)