Skip to main content

zad_cli/cli/
debug_agent.rs

1//! Implementation of the top-level `--debug-agent` flag mandated by
2//! `OSS_SPEC.md` §12.2.
3//!
4//! The output is a compact, plain-text troubleshooting block that an
5//! agent can splice into a prompt via command substitution when it hits
6//! an unexpected failure. It answers: where do logs live? where do
7//! configs live, and in what precedence order? which env vars matter?
8//! how do I turn on verbose output? which commands help me investigate?
9//! and which version am I talking to?
10
11use std::fmt::Write as _;
12
13pub fn render() -> String {
14    let mut out = String::new();
15    let version = zad::version();
16
17    let _ = writeln!(out, "zad {version} — diagnostics for agents");
18    out.push('\n');
19
20    out.push_str("Log files:\n");
21    match zad::logging::log_path() {
22        Some(p) => {
23            let _ = writeln!(out, "  {}", p.display());
24            let _ = writeln!(
25                out,
26                "  (rolled daily; the file log is always on, regardless of --debug)"
27            );
28        }
29        None => out.push_str("  <unable to resolve a log directory on this platform>\n"),
30    }
31    out.push('\n');
32
33    out.push_str("Config precedence (first match wins):\n");
34    out.push_str("  1. Project-local   ~/.zad/projects/<slug>/services/<svc>/\n");
35    out.push_str("  2. Global service  ~/.zad/services/<svc>/\n");
36    out.push_str("  3. Built-in defaults\n");
37    out.push_str("Permissions intersect rather than replace: project-local can only tighten\n");
38    out.push_str("the global file, never loosen it.\n");
39    out.push('\n');
40
41    out.push_str("Resolved paths for the current working directory:\n");
42    match zad::config::path::zad_home() {
43        Ok(p) => {
44            let _ = writeln!(out, "  ZAD_HOME      {}", p.display());
45        }
46        Err(e) => {
47            let _ = writeln!(out, "  ZAD_HOME      <unresolved: {e}>");
48        }
49    }
50    match zad::config::path::project_slug() {
51        Ok(s) => {
52            let _ = writeln!(out, "  project slug  {s}");
53        }
54        Err(e) => {
55            let _ = writeln!(out, "  project slug  <unresolved: {e}>");
56        }
57    }
58    match zad::config::path::project_dir() {
59        Ok(p) => {
60            let _ = writeln!(out, "  project dir   {}", p.display());
61        }
62        Err(e) => {
63            let _ = writeln!(out, "  project dir   <unresolved: {e}>");
64        }
65    }
66    out.push('\n');
67
68    out.push_str("Environment variables:\n");
69    out.push_str("  ZAD_HOME_OVERRIDE   Override $HOME when resolving ~/.zad (tests only).\n");
70    out.push_str("  ZAD_SECRETS_MEMORY  `1` = in-memory keychain backend (tests only).\n");
71    out.push_str(
72        "  RUST_LOG            Standard tracing filter, e.g. `zad=debug`. Overrides --debug.\n",
73    );
74    out.push('\n');
75
76    out.push_str("Verbose output:\n");
77    out.push_str(
78        "  --debug             Emit debug-level logs to stderr in addition to the file log.\n",
79    );
80    out.push('\n');
81
82    out.push_str("Diagnostic commands:\n");
83    out.push_str("  zad --help-agent              Compact prompt-injectable CLI summary.\n");
84    out.push_str("  zad commands                  Enumerate every command.\n");
85    out.push_str("  zad commands <name>           Flags and exit codes for one command.\n");
86    out.push_str("  zad man [command]             Reference manpages embedded at build time.\n");
87    out.push_str("  zad docs [topic]              Topic docs embedded at build time.\n");
88    out.push_str("  zad service list              Configured services for this project.\n");
89    out.push_str(
90        "  zad <svc> permissions show    Effective permissions for a service (global ∩ local).\n",
91    );
92    out.push_str(
93        "  zad <svc> permissions path    Where the TOML files that drive permissions live.\n",
94    );
95    out.push('\n');
96
97    out.push_str("Build metadata:\n");
98    let _ = writeln!(out, "  version       {version}");
99    let _ = writeln!(
100        out,
101        "  target        {}",
102        option_env!("TARGET").unwrap_or(std::env::consts::ARCH)
103    );
104    let _ = writeln!(out, "  profile       {}", profile());
105    out.push('\n');
106
107    out
108}
109
110const fn profile() -> &'static str {
111    if cfg!(debug_assertions) {
112        "debug"
113    } else {
114        "release"
115    }
116}