Skip to main content

fez/
guide.rs

1//! The agent bootstrap contract printed by `fez guide`. Tells an LLM how to
2//! discover and invoke capabilities, what the envelope looks like, what the
3//! exit codes mean, and which env vars matter.
4use crate::envelope::Envelope;
5use crate::error::EXIT_CODES;
6use serde_json::json;
7
8/// Render the guide as plain text for humans and `--help`-style reading.
9pub fn text() -> String {
10    let mut s = String::new();
11    s.push_str("fez agent guide\n\n");
12    s.push_str("Discovery loop:\n");
13    s.push_str("  1. fez capabilities            list capability ids\n");
14    s.push_str("  2. fez describe <id> --json    inputs, flags, output kind, examples\n");
15    s.push_str("  3. fez <command> ... --json    invoke; parse the fez/v1 envelope\n\n");
16    s.push_str("Envelope (fez/v1): every --json response is\n");
17    s.push_str(
18        "  {apiVersion:\"fez/v1\", kind, host, status:\"ok\"|\"error\", data?, error?, hints?}\n\n",
19    );
20    s.push_str("Global flags: --host <h> --json --dry-run --force\n\n");
21    s.push_str("Exit codes:\n");
22    for e in EXIT_CODES {
23        s.push_str(&format!("  {:>2}  {:<14} {}\n", e.code, e.label, e.meaning));
24    }
25    s.push_str(
26        "\nEnv vars: FEZ_BRIDGE, FEZ_AUDIT, FEZ_SSH_CONFIG, FEZ_ACTOR, FEZ_CORRELATION_ID.\n",
27    );
28    s
29}
30
31/// The guide as a structured JSON value for `--json`.
32pub fn data() -> serde_json::Value {
33    json!({
34        "discovery": [
35            "fez capabilities",
36            "fez describe <id> --json",
37            "fez <command> ... --json"
38        ],
39        "envelope": {
40            "apiVersion": "fez/v1",
41            "fields": ["kind", "host", "status", "data", "error", "hints"]
42        },
43        "globalFlags": ["--host", "--json", "--dry-run", "--force"],
44        "exitCodes": EXIT_CODES.iter().map(|e| json!({
45            "code": e.code, "label": e.label, "meaning": e.meaning
46        })).collect::<Vec<_>>(),
47        "envVars": [
48            "FEZ_BRIDGE",
49            "FEZ_AUDIT",
50            "FEZ_SSH_CONFIG",
51            "FEZ_ACTOR",
52            "FEZ_CORRELATION_ID"
53        ]
54    })
55}
56
57/// Print the guide. Returns the process exit code (always 0).
58pub fn run(host: &str, as_json: bool) -> i32 {
59    if as_json {
60        println!(
61            "{}",
62            Envelope::ok("AgentGuide", host, data()).to_json_string()
63        );
64    } else {
65        print!("{}", text());
66    }
67    0
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn text_lists_every_exit_code() {
76        let t = text();
77        for e in EXIT_CODES {
78            assert!(t.contains(e.label), "guide text missing {}", e.label);
79        }
80    }
81
82    #[test]
83    fn data_has_exit_codes_and_envelope() {
84        let d = data();
85        assert!(d["exitCodes"].as_array().unwrap().len() == EXIT_CODES.len());
86        assert_eq!(d["envelope"]["apiVersion"], "fez/v1");
87    }
88}