Skip to main content

zad_cli/cli/
help_agent.rs

1//! Implementation of the top-level `--help-agent` flag mandated by
2//! `OSS_SPEC.md` §12.1.
3//!
4//! The output is a compact, plain-text, prompt-injectable description of
5//! the CLI. Agents splice it into a larger prompt via command
6//! substitution (`$(zad --help-agent)`) so they don't need to probe the
7//! tool from scratch. To keep the text from going stale, command names
8//! and descriptions are introspected from the clap tree — the single
9//! source of truth for `--help` too — rather than hand-maintained here.
10
11use std::fmt::Write as _;
12
13use clap::CommandFactory;
14
15use super::Cli;
16
17pub fn render() -> String {
18    let cmd = Cli::command();
19    let binary = cmd.get_name().to_string();
20    let version = zad::version();
21    let about = cmd
22        .get_about()
23        .map(|s| s.to_string())
24        .unwrap_or_else(|| String::from("A CLI."));
25
26    let mut out = String::new();
27
28    // (1) One-sentence description, (6) binary name + version.
29    let _ = writeln!(out, "{binary} {version} — {about}");
30    out.push('\n');
31
32    // (2) Top-level commands, each with their leaf verbs for context.
33    out.push_str("Commands:\n");
34    for sub in cmd.get_subcommands() {
35        if sub.is_hide_set() {
36            continue;
37        }
38        let name = sub.get_name();
39        let desc = sub.get_about().map(|s| s.to_string()).unwrap_or_default();
40        let _ = writeln!(out, "  {binary} {name:<10} {desc}");
41        for inner in sub.get_subcommands() {
42            if inner.is_hide_set() {
43                continue;
44            }
45            let iname = inner.get_name();
46            let idesc = inner.get_about().map(|s| s.to_string()).unwrap_or_default();
47            let _ = writeln!(out, "    {binary} {name} {iname:<10} {idesc}");
48        }
49    }
50    out.push('\n');
51
52    // (3) Most important flags + env vars.
53    out.push_str("Global flags:\n");
54    out.push_str("  --debug          Emit debug-level logs to stderr.\n");
55    out.push_str("  --help-agent     Print this block (compact, prompt-injectable).\n");
56    out.push_str("  --help           Per-command usage with flag descriptions.\n");
57    out.push_str("  --version        Print version and exit.\n");
58    out.push('\n');
59    out.push_str("Environment variables:\n");
60    out.push_str("  ZAD_HOME_OVERRIDE     Override $HOME when resolving ~/.zad (tests only).\n");
61    out.push_str("  ZAD_SECRETS_MEMORY    `1` = in-memory keychain backend (tests only).\n");
62    out.push_str("  ZAD_PERMISSIONS_PATH  Pin the local permissions file to this exact path (per-service schema).\n");
63    out.push_str("  ZAD_PERMISSIONS_ROOT  Pin the local permissions root; resolved as <root>/<service>/permissions.toml.\n");
64    out.push('\n');
65
66    // (4) Pointer to `commands`, (5) pointer to `docs` / `man`.
67    out.push_str("Discovery (see OSS_SPEC.md §12):\n");
68    let _ = writeln!(
69        out,
70        "  {binary} commands              List every command, grep-friendly."
71    );
72    let _ = writeln!(
73        out,
74        "  {binary} commands <name>       Flags, types, exit codes for <name>."
75    );
76    let _ = writeln!(
77        out,
78        "  {binary} commands --examples   Realistic example invocations for every command."
79    );
80    let _ = writeln!(
81        out,
82        "  {binary} man [command]         Reference manpages (embedded at build time)."
83    );
84    let _ = writeln!(
85        out,
86        "  {binary} docs [topic]          Topic docs (embedded at build time)."
87    );
88    let _ = writeln!(
89        out,
90        "  {binary} --debug-agent         Troubleshooting context (log paths, env vars, config)."
91    );
92    out.push('\n');
93
94    out.push_str(
95        "Config lives at ~/.zad/; long-lived secrets (bot tokens, API keys) go to the OS\n\
96         keychain via the `secrets` module and never appear in the TOML.\n",
97    );
98
99    out
100}