use std::path::PathBuf;
use anyhow::{Result, anyhow};
use clap::Args;
use serde_json::{Value, json};
use agentstatedeveloper_core::help;
use crate::config::Config;
#[derive(Debug, Args)]
pub struct HelpArgs {
pub topic: Option<String>,
#[arg(long)]
pub agent: bool,
#[arg(long)]
pub manifest: bool,
#[arg(long)]
pub publish: bool,
#[arg(long, hide = true)]
pub no_proxy: bool,
}
pub fn run(_cfg: &Config, args: HelpArgs) -> Result<()> {
if args.publish {
let path = publish_help_index()?;
println!("Published asd help manifest to {}", path.display());
return Ok(());
}
let value = if args.manifest {
help::manifest()
} else {
help::resolve(args.topic.as_deref(), !args.no_proxy)
};
if args.agent || args.manifest {
println!("{}", serde_json::to_string_pretty(&value)?);
return Ok(());
}
render_help(&value);
Ok(())
}
fn help_index_path() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("AGENTSTATE_HELP_INDEX") {
return Some(PathBuf::from(p));
}
std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config/agentstate/help-index.json"))
}
pub(crate) fn publish_help_index() -> Result<PathBuf> {
let manifest = help::manifest();
let tool = manifest
.get("tool")
.and_then(|t| t.as_str())
.ok_or_else(|| anyhow!("manifest missing `tool`"))?
.to_string();
let path = help_index_path().ok_or_else(|| anyhow!("cannot resolve HOME"))?;
let mut index = std::fs::read_to_string(&path)
.ok()
.and_then(|s| serde_json::from_str::<Value>(&s).ok())
.filter(|v| v.is_object())
.unwrap_or_else(|| json!({ "schema": 1, "tools": {} }));
if !index.get("tools").map(|t| t.is_object()).unwrap_or(false) {
index["tools"] = json!({});
}
index["schema"] = json!(1);
index["tools"][&tool] = json!({
"version": manifest.get("version").cloned().unwrap_or(Value::Null),
"features": manifest.get("features").cloned().unwrap_or_else(|| json!([])),
});
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)?;
}
std::fs::write(
&path,
format!("{}\n", serde_json::to_string_pretty(&index)?),
)
.map_err(|e| anyhow!("writing {}: {e}", path.display()))?;
Ok(path)
}
fn render_help(v: &Value) {
if let Some(groups) = v.get("groups").and_then(|g| g.as_object()) {
println!(
"asd help — {} features (v{}). Call `asd help <feature>` for details.\n",
v.get("feature_count").and_then(|c| c.as_u64()).unwrap_or(0),
v.get("version").and_then(|s| s.as_str()).unwrap_or("?"),
);
for (group, feats) in groups {
println!("{}:", group);
if let Some(arr) = feats.as_array() {
for f in arr {
println!(
" {:<16} {}",
f.get("feature").and_then(|s| s.as_str()).unwrap_or(""),
f.get("synopsis").and_then(|s| s.as_str()).unwrap_or(""),
);
}
}
println!();
}
return;
}
if let Some(matches) = v.get("matches").and_then(|m| m.as_array()) {
println!(
"No exact match for '{}'. Did you mean:",
v.get("query").and_then(|s| s.as_str()).unwrap_or(""),
);
for m in matches {
println!(
" {:<16} {}",
m.get("feature").and_then(|s| s.as_str()).unwrap_or(""),
m.get("synopsis").and_then(|s| s.as_str()).unwrap_or(""),
);
}
return;
}
if let Some(nf) = v.get("not_found").and_then(|s| s.as_str()) {
println!("No asd feature matched '{}'.", nf);
if let Some(hint) = v.get("hint").and_then(|s| s.as_str()) {
println!("{}", hint);
}
return;
}
let get = |k: &str| v.get(k).and_then(|s| s.as_str()).unwrap_or("");
if let Some(from) = v.get("proxied_from").and_then(|s| s.as_str()) {
println!("(via {from})");
}
println!("{} — {}", get("feature"), get("synopsis"));
println!(" syntax: {}", get("syntax"));
if let Some(params) = v.get("params").and_then(|p| p.as_array())
&& !params.is_empty()
{
println!(" params:");
for p in params {
let req = if p.get("required").and_then(|b| b.as_bool()).unwrap_or(false) {
"(required)"
} else {
"(optional)"
};
println!(
" {:<14} {} {}",
p.get("name").and_then(|s| s.as_str()).unwrap_or(""),
req,
p.get("desc").and_then(|s| s.as_str()).unwrap_or(""),
);
}
}
let list = |label: &str, key: &str| {
if let Some(arr) = v.get(key).and_then(|a| a.as_array())
&& !arr.is_empty()
{
println!(" {}:", label);
for item in arr {
if let Some(s) = item.as_str() {
println!(" - {}", s);
}
}
}
};
list("examples", "examples");
list("gotchas", "gotchas");
list("related", "related");
println!(
" (~{} tokens, v{})",
v.get("help_tokens").and_then(|t| t.as_u64()).unwrap_or(0),
get("version"),
);
}