fix: make help and version successful discovery
Some checks failed
checks / flake (push) Has been cancelled
Some checks failed
checks / flake (push) Has been cancelled
This commit is contained in:
parent
ffab80a1f2
commit
5c81b469e5
5 changed files with 88 additions and 1 deletions
6
PRD.md
6
PRD.md
|
|
@ -813,6 +813,12 @@ JSON mode is a strict framing protocol:
|
|||
- An error includes the path, byte size, and content digest of its Diagnostic
|
||||
Log when one is available.
|
||||
|
||||
CLI discovery is the sole framing exception. `ghidr --help`, every
|
||||
`ghidr <command> --help`, and `ghidr --version` are successful metadata
|
||||
requests rather than operations: they emit plain UTF-8 text on stdout, leave
|
||||
stderr empty, exit with status 0, and never initialize Ghidra, access the
|
||||
Analysis Store, or execute a command.
|
||||
|
||||
Human-readable mode may present progress and concise diagnostics on stderr.
|
||||
|
||||
Every Ghidra/adapter invocation captures diagnostics into a private temporary
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -21,6 +21,17 @@ Each command will identify the sample by its content, reuse compatible cached
|
|||
analysis, and produce versioned JSON by default. Human-readable presentation is
|
||||
available explicitly through `--format human`.
|
||||
|
||||
Agents can discover the complete command surface without initializing Ghidra:
|
||||
|
||||
```console
|
||||
ghidr --help
|
||||
ghidr decompile --help
|
||||
ghidr --version
|
||||
```
|
||||
|
||||
These discovery requests return plain text on stdout with exit status 0 and
|
||||
leave stderr empty.
|
||||
|
||||
## Documents
|
||||
|
||||
- [PRD.md](./PRD.md) defines the product requirements and initial delivery
|
||||
|
|
|
|||
|
|
@ -11,3 +11,10 @@ whether stdout is a terminal or pipe; human-readable presentation requires
|
|||
command has the same framing and semantics in every execution environment.
|
||||
JSON mode never emits interactive prompts; required confirmation is represented
|
||||
as a structured error and satisfied by an explicit flag.
|
||||
|
||||
Clap discovery requests are intentionally outside the operation-result
|
||||
protocol. Root and subcommand `--help`, plus `--version`, return conventional
|
||||
plain UTF-8 text on stdout with status 0 and no stderr. They do not initialize
|
||||
the runtime or execute an operation. This narrow exception keeps the CLI
|
||||
self-describing for both people and automation agents without weakening JSON
|
||||
framing for analysis and cleanup commands.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
io::{self, Write},
|
||||
};
|
||||
|
||||
use clap::Parser as _;
|
||||
use clap::{Parser as _, error::ErrorKind};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -46,6 +46,15 @@ where
|
|||
let arguments: Vec<OsString> = arguments.into_iter().map(Into::into).collect();
|
||||
match Cli::try_parse_from(arguments.clone()) {
|
||||
Ok(cli) => run_parsed(&cli, executor, stdout, stderr),
|
||||
Err(error)
|
||||
if matches!(
|
||||
error.kind(),
|
||||
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion
|
||||
) =>
|
||||
{
|
||||
stdout.write_all(error.to_string().as_bytes())?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Err(error) => {
|
||||
if requests_human_format(&arguments) {
|
||||
stderr.write_all(error.to_string().as_bytes())?;
|
||||
|
|
@ -173,6 +182,35 @@ mod tests {
|
|||
assert_eq!(document["error"]["code"], "invalid_arguments");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovery_requests_use_plain_stdout_and_succeed() {
|
||||
for (arguments, expected) in [
|
||||
(&["ghidr", "--help"][..], "Usage: ghidr"),
|
||||
(
|
||||
&["ghidr", "decompile", "--help"][..],
|
||||
"Usage: ghidr decompile",
|
||||
),
|
||||
(&["ghidr", "--version"][..], "ghidr 0.1.0"),
|
||||
] {
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
let status = run_from(
|
||||
arguments.iter().copied(),
|
||||
&SuccessExecutor,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
)
|
||||
.expect("write succeeds");
|
||||
assert_eq!(status, ExitStatus::Success);
|
||||
assert!(stderr.is_empty());
|
||||
assert!(
|
||||
String::from_utf8(stdout)
|
||||
.expect("help is UTF-8")
|
||||
.contains(expected)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oversized_executor_success_cannot_escape_inline_bound() {
|
||||
let mut stdout = Vec::new();
|
||||
|
|
|
|||
25
tests/cli.rs
25
tests/cli.rs
|
|
@ -10,6 +10,31 @@ use std::{
|
|||
};
|
||||
|
||||
use rustix::process::{Pid, Signal, kill_process};
|
||||
|
||||
#[test]
|
||||
fn help_and_version_are_successful_discovery_output() {
|
||||
let cases = [
|
||||
(vec!["--help"], "Usage: ghidr"),
|
||||
(vec!["doctor", "--help"], "Usage: ghidr doctor"),
|
||||
(vec!["inspect", "--help"], "Usage: ghidr inspect"),
|
||||
(vec!["functions", "--help"], "Usage: ghidr functions"),
|
||||
(vec!["decompile", "--help"], "Usage: ghidr decompile"),
|
||||
(vec!["clean", "--help"], "Usage: ghidr clean"),
|
||||
(vec!["--version"], "ghidr 0.1.0"),
|
||||
];
|
||||
|
||||
for (arguments, expected) in cases {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_ghidr"))
|
||||
.args(arguments)
|
||||
.output()
|
||||
.expect("run discovery request");
|
||||
assert_eq!(output.status.code(), Some(0), "{:?}", output.stderr);
|
||||
assert!(output.stderr.is_empty());
|
||||
let stdout = String::from_utf8(output.stdout).expect("discovery output is UTF-8");
|
||||
assert!(stdout.contains(expected), "{stdout:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctor_reports_ready_with_closed_runtime_paths() {
|
||||
let temp = tempfile::tempdir().expect("temp");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue