mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-04 21:47:10 +08:00
fix: recover CLI parser CI
Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
@@ -148,6 +148,7 @@ Top-level commands:
|
||||
|
||||
`claw acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, claw-code does **not** ship an ACP/Zed daemon or JSON-RPC entrypoint yet, and `claw acp serve` is only a status alias until the real protocol surface lands. Status queries exit 0 and expose the same machine-readable contract via `--output-format json`; malformed ACP invocations exit 1 with `kind: unsupported_acp_invocation`.
|
||||
`--output-format` accepts `text` or `json` in any casing. `CLAW_OUTPUT_FORMAT=json` selects JSON as the default for non-interactive commands, explicit flags override it, repeated flags warn on stderr, and status JSON exposes `format_source`, `format_raw`, and `format_overridden`. Help and doctor output also surface `CLAW_LOG` / `RUST_LOG` as the logging environment knobs.
|
||||
Shorthand prompt mode honors the POSIX `--` end-of-flags separator, so `claw -- "-prompt-with-dash"` and unknown dash-prefixed non-flag text stay on the prompt path instead of being treated as CLI options.
|
||||
`claw dump-manifests` is self-contained: it emits the Rust resolver inventory for the selected workspace (commands, tools, agents, skills, and bootstrap phases) without requiring an upstream Claude Code TypeScript checkout. Use `--manifests-dir PATH` only to scope resolver discovery to another directory.
|
||||
|
||||
The command surface is moving quickly. For the canonical live help text, run:
|
||||
|
||||
@@ -301,6 +301,17 @@ const CLI_OPTION_SUGGESTIONS: &[&str] = &[
|
||||
"-p",
|
||||
];
|
||||
|
||||
fn is_registered_cli_flag_token(value: &str) -> bool {
|
||||
let flag = value.split_once('=').map_or(value, |(flag, _)| flag);
|
||||
CLI_OPTION_SUGGESTIONS.contains(&flag)
|
||||
}
|
||||
|
||||
fn should_reject_unknown_option_like(value: &str) -> bool {
|
||||
is_registered_cli_flag_token(value)
|
||||
|| (value.starts_with("--")
|
||||
&& suggest_closest_term(value, CLI_OPTION_SUGGESTIONS).is_some())
|
||||
}
|
||||
|
||||
type AllowedToolSet = BTreeSet<String>;
|
||||
type RuntimePluginStateBuildOutput = (
|
||||
Option<Arc<Mutex<RuntimeMcpState>>>,
|
||||
@@ -1328,6 +1339,7 @@ fn current_output_format_selection() -> OutputFormatSelection {
|
||||
|
||||
fn cli_has_output_format_flag(args: &[String]) -> bool {
|
||||
args.iter()
|
||||
.take_while(|arg| arg.as_str() != "--")
|
||||
.any(|arg| arg == "--output-format" || arg.starts_with("--output-format="))
|
||||
}
|
||||
|
||||
@@ -1336,6 +1348,9 @@ fn raw_args_request_json_output(args: &[String]) -> bool {
|
||||
let mut index = 0;
|
||||
while index < args.len() {
|
||||
let arg = &args[index];
|
||||
if arg == "--" {
|
||||
break;
|
||||
}
|
||||
if arg == "--output-format" {
|
||||
if let Some(value) = args.get(index + 1) {
|
||||
values.push(value.as_str());
|
||||
@@ -1433,6 +1448,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
// flag parsing. None until `-p <text>` is seen.
|
||||
let mut short_p_prompt: Option<String> = None;
|
||||
let mut rest: Vec<String> = Vec::new();
|
||||
let mut positional_after_separator = false;
|
||||
let mut index = 0;
|
||||
|
||||
while index < args.len() {
|
||||
@@ -1548,6 +1564,11 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
allow_broad_cwd = true;
|
||||
index += 1;
|
||||
}
|
||||
"--" => {
|
||||
positional_after_separator = true;
|
||||
rest.extend(args[index + 1..].iter().cloned());
|
||||
break;
|
||||
}
|
||||
"-p" => {
|
||||
// Claw Code compat: -p "prompt" = one-shot prompt.
|
||||
// #755: consume exactly one token so subsequent flags like
|
||||
@@ -1629,7 +1650,11 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
index += 1;
|
||||
}
|
||||
other if rest.is_empty() && other.starts_with('-') => {
|
||||
return Err(format_unknown_option(other))
|
||||
if should_reject_unknown_option_like(other) {
|
||||
return Err(format_unknown_option(other));
|
||||
}
|
||||
rest.push(other.to_string());
|
||||
index += 1;
|
||||
}
|
||||
other => {
|
||||
rest.push(other.to_string());
|
||||
@@ -1704,6 +1729,21 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
});
|
||||
}
|
||||
|
||||
if positional_after_separator && !rest.is_empty() {
|
||||
let permission_mode = permission_mode_override.unwrap_or_else(default_permission_mode);
|
||||
return Ok(CliAction::Prompt {
|
||||
prompt: rest.join(" "),
|
||||
model,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
allow_broad_cwd,
|
||||
});
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let permission_mode = permission_mode_override.unwrap_or_else(default_permission_mode);
|
||||
// When stdin is not a terminal (pipe/redirect) and no prompt is given on the
|
||||
@@ -2042,9 +2082,7 @@ Usage: claw prompt <text> or echo '<text>' | claw prompt".to_string());
|
||||
allow_broad_cwd,
|
||||
),
|
||||
other => {
|
||||
if looks_like_subcommand_typo(other)
|
||||
&& (rest.len() == 1 || output_format == CliOutputFormat::Json)
|
||||
{
|
||||
if !other.starts_with('-') && looks_like_subcommand_typo(other) && rest.len() == 1 {
|
||||
// #825/#826: emit command_not_found before provider startup for
|
||||
// command-shaped tokens that do not match known subcommands.
|
||||
// Text-mode multi-word prompt shorthand remains available, but
|
||||
@@ -2393,13 +2431,10 @@ fn parse_direct_slash_cli_action(
|
||||
let raw = rest.join(" ");
|
||||
match SlashCommand::parse(&raw) {
|
||||
Ok(Some(SlashCommand::Help)) => Ok(CliAction::Help { output_format }),
|
||||
Ok(Some(SlashCommand::Status)) => Ok(CliAction::Status {
|
||||
model,
|
||||
model_flag_raw: None,
|
||||
permission_mode,
|
||||
output_format,
|
||||
allowed_tools,
|
||||
}),
|
||||
Ok(Some(SlashCommand::Status)) => Err(
|
||||
"interactive_only: /status requires a live session.\nStart `claw` and run it there, or use `claw --resume SESSION.jsonl /status` / `claw --resume latest /status`."
|
||||
.to_string(),
|
||||
),
|
||||
Ok(Some(SlashCommand::Sandbox)) => Ok(CliAction::Sandbox { output_format }),
|
||||
Ok(Some(SlashCommand::Diff)) => Ok(CliAction::Diff { output_format }),
|
||||
Ok(Some(SlashCommand::Version)) => Ok(CliAction::Version { output_format }),
|
||||
@@ -2482,6 +2517,9 @@ fn parse_direct_slash_cli_action(
|
||||
}
|
||||
|
||||
fn format_unknown_option(option: &str) -> String {
|
||||
if option == "--" {
|
||||
return "end_of_flags: `--` terminates flag parsing. Pass literal prompt text after it, for example `claw -- \"-literal prompt\"`.\nRun `claw --help` for usage.".to_string();
|
||||
}
|
||||
let mut message = format!("unknown option: {option}");
|
||||
if let Some(suggestion) = suggest_closest_term(option, CLI_OPTION_SUGGESTIONS) {
|
||||
message.push_str("\nDid you mean ");
|
||||
@@ -12643,6 +12681,10 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
||||
" claw [--model MODEL] [--output-format text|json] TEXT"
|
||||
)?;
|
||||
writeln!(out, " Shorthand non-interactive prompt mode")?;
|
||||
writeln!(
|
||||
out,
|
||||
" Use `--` before TEXT when the prompt itself starts with '-' or '--'"
|
||||
)?;
|
||||
writeln!(
|
||||
out,
|
||||
" claw --resume [SESSION.jsonl|session-id|latest] [/status] [/compact] [...]"
|
||||
@@ -13414,6 +13456,67 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_dash_prefixed_prompt_text_434() {
|
||||
let _guard = env_lock();
|
||||
std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE");
|
||||
|
||||
assert_eq!(
|
||||
parse_args(&["--".to_string(), "-prompt-with-dash".to_string()])
|
||||
.expect("-- should terminate flag parsing"),
|
||||
CliAction::Prompt {
|
||||
prompt: "-prompt-with-dash".to_string(),
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
compact: false,
|
||||
base_commit: None,
|
||||
reasoning_effort: None,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_args(&["-not-a-flag".to_string()])
|
||||
.expect("unknown dash-prefixed shorthand prompt should parse as prompt text"),
|
||||
CliAction::Prompt {
|
||||
prompt: "-not-a-flag".to_string(),
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
compact: false,
|
||||
base_commit: None,
|
||||
reasoning_effort: None,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_args(&["--bogus-flag-like".to_string(), "literal".to_string()])
|
||||
.expect("unknown double-dash text should stay eligible for prompt shorthand"),
|
||||
CliAction::Prompt {
|
||||
prompt: "--bogus-flag-like literal".to_string(),
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
compact: false,
|
||||
base_commit: None,
|
||||
reasoning_effort: None,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
|
||||
assert!(parse_args(&["--".to_string()]).is_ok());
|
||||
|
||||
let error = parse_args(&["--resum".to_string()])
|
||||
.expect_err("nearby real flags should still be rejected as unknown options");
|
||||
assert!(error.contains("unknown option: --resum"));
|
||||
assert!(error.contains("Did you mean --resume?"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_compact_flag_for_prompt_mode() {
|
||||
// given a bare prompt invocation that includes the --compact flag
|
||||
|
||||
Reference in New Issue
Block a user