agentstatedeveloper_cli/commands/
help.rs1use std::path::PathBuf;
14
15use anyhow::{Result, anyhow};
16use clap::Args;
17use serde_json::{Value, json};
18
19use agentstatedeveloper_core::help;
20
21use crate::config::Config;
22
23#[derive(Debug, Args)]
24pub struct HelpArgs {
25 pub topic: Option<String>,
27
28 #[arg(long)]
30 pub agent: bool,
31
32 #[arg(long)]
34 pub manifest: bool,
35
36 #[arg(long)]
39 pub publish: bool,
40
41 #[arg(long, hide = true)]
44 pub no_proxy: bool,
45}
46
47pub fn run(_cfg: &Config, args: HelpArgs) -> Result<()> {
48 if args.publish {
49 let path = publish_help_index()?;
50 println!("Published asd help manifest to {}", path.display());
51 return Ok(());
52 }
53
54 let value = if args.manifest {
55 help::manifest()
56 } else {
57 help::resolve(args.topic.as_deref(), !args.no_proxy)
58 };
59
60 if args.agent || args.manifest {
61 println!("{}", serde_json::to_string_pretty(&value)?);
62 return Ok(());
63 }
64 render_help(&value);
65 Ok(())
66}
67
68fn help_index_path() -> Option<PathBuf> {
72 if let Some(p) = std::env::var_os("AGENTSTATE_HELP_INDEX") {
73 return Some(PathBuf::from(p));
74 }
75 std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config/agentstate/help-index.json"))
76}
77
78pub(crate) fn publish_help_index() -> Result<PathBuf> {
82 let manifest = help::manifest();
83 let tool = manifest
84 .get("tool")
85 .and_then(|t| t.as_str())
86 .ok_or_else(|| anyhow!("manifest missing `tool`"))?
87 .to_string();
88 let path = help_index_path().ok_or_else(|| anyhow!("cannot resolve HOME"))?;
89
90 let mut index = std::fs::read_to_string(&path)
93 .ok()
94 .and_then(|s| serde_json::from_str::<Value>(&s).ok())
95 .filter(|v| v.is_object())
96 .unwrap_or_else(|| json!({ "schema": 1, "tools": {} }));
97 if !index.get("tools").map(|t| t.is_object()).unwrap_or(false) {
98 index["tools"] = json!({});
99 }
100 index["schema"] = json!(1);
101 index["tools"][&tool] = json!({
102 "version": manifest.get("version").cloned().unwrap_or(Value::Null),
103 "features": manifest.get("features").cloned().unwrap_or_else(|| json!([])),
104 });
105
106 if let Some(dir) = path.parent() {
107 std::fs::create_dir_all(dir)?;
108 }
109 std::fs::write(
110 &path,
111 format!("{}\n", serde_json::to_string_pretty(&index)?),
112 )
113 .map_err(|e| anyhow!("writing {}: {e}", path.display()))?;
114 Ok(path)
115}
116
117fn render_help(v: &Value) {
120 if let Some(groups) = v.get("groups").and_then(|g| g.as_object()) {
122 println!(
123 "asd help — {} features (v{}). Call `asd help <feature>` for details.\n",
124 v.get("feature_count").and_then(|c| c.as_u64()).unwrap_or(0),
125 v.get("version").and_then(|s| s.as_str()).unwrap_or("?"),
126 );
127 for (group, feats) in groups {
128 println!("{}:", group);
129 if let Some(arr) = feats.as_array() {
130 for f in arr {
131 println!(
132 " {:<16} {}",
133 f.get("feature").and_then(|s| s.as_str()).unwrap_or(""),
134 f.get("synopsis").and_then(|s| s.as_str()).unwrap_or(""),
135 );
136 }
137 }
138 println!();
139 }
140 return;
141 }
142
143 if let Some(matches) = v.get("matches").and_then(|m| m.as_array()) {
145 println!(
146 "No exact match for '{}'. Did you mean:",
147 v.get("query").and_then(|s| s.as_str()).unwrap_or(""),
148 );
149 for m in matches {
150 println!(
151 " {:<16} {}",
152 m.get("feature").and_then(|s| s.as_str()).unwrap_or(""),
153 m.get("synopsis").and_then(|s| s.as_str()).unwrap_or(""),
154 );
155 }
156 return;
157 }
158
159 if let Some(nf) = v.get("not_found").and_then(|s| s.as_str()) {
161 println!("No asd feature matched '{}'.", nf);
162 if let Some(hint) = v.get("hint").and_then(|s| s.as_str()) {
163 println!("{}", hint);
164 }
165 return;
166 }
167
168 let get = |k: &str| v.get(k).and_then(|s| s.as_str()).unwrap_or("");
170 if let Some(from) = v.get("proxied_from").and_then(|s| s.as_str()) {
171 println!("(via {from})");
172 }
173 println!("{} — {}", get("feature"), get("synopsis"));
174 println!(" syntax: {}", get("syntax"));
175 if let Some(params) = v.get("params").and_then(|p| p.as_array())
176 && !params.is_empty()
177 {
178 println!(" params:");
179 for p in params {
180 let req = if p.get("required").and_then(|b| b.as_bool()).unwrap_or(false) {
181 "(required)"
182 } else {
183 "(optional)"
184 };
185 println!(
186 " {:<14} {} {}",
187 p.get("name").and_then(|s| s.as_str()).unwrap_or(""),
188 req,
189 p.get("desc").and_then(|s| s.as_str()).unwrap_or(""),
190 );
191 }
192 }
193 let list = |label: &str, key: &str| {
194 if let Some(arr) = v.get(key).and_then(|a| a.as_array())
195 && !arr.is_empty()
196 {
197 println!(" {}:", label);
198 for item in arr {
199 if let Some(s) = item.as_str() {
200 println!(" - {}", s);
201 }
202 }
203 }
204 };
205 list("examples", "examples");
206 list("gotchas", "gotchas");
207 list("related", "related");
208 println!(
209 " (~{} tokens, v{})",
210 v.get("help_tokens").and_then(|t| t.as_u64()).unwrap_or(0),
211 get("version"),
212 );
213}