1use crate::mcp::{McpServerConfig, TransportKind};
9use crate::meta_commands::META_COMMANDS;
10use crate::safety::SafetyConfig;
11use crate::storage::StorageConfig;
12use crate::tool::{ToolCtx, ToolRegistry};
13use crate::trust::TrustConfig;
14use documented::{DocumentedFields, DocumentedVariants};
15
16pub struct HelpTopic {
17 pub id: &'static str,
18 pub title: &'static str,
19}
20
21pub const TOPICS: &[HelpTopic] = &[
22 HelpTopic {
23 id: "index",
24 title: "Topic Index",
25 },
26 HelpTopic {
27 id: "tools",
28 title: "Built-in Tools",
29 },
30 HelpTopic {
31 id: "cli",
32 title: "CLI Meta Commands",
33 },
34 HelpTopic {
35 id: "config",
36 title: "Configuration Reference",
37 },
38 HelpTopic {
39 id: "mcp",
40 title: "MCP Setup",
41 },
42 HelpTopic {
43 id: "dsl",
44 title: "Atman DSL Syntax",
45 },
46 HelpTopic {
47 id: "features",
48 title: "Feature Overview",
49 },
50];
51
52pub fn version() -> &'static str {
53 env!("CARGO_PKG_VERSION")
54}
55
56pub fn topic_content(topic: &str, ctx: &ToolCtx) -> Option<String> {
58 match topic {
59 "index" => Some(render_index()),
60 "tools" => ctx
61 .registry
62 .as_ref()
63 .map(|r| render_tools(r))
64 .or_else(|| Some(render_tools_offline())),
65 "cli" => Some(render_cli()),
66 "config" => Some(render_config()),
67 "mcp" => Some(render_mcp()),
68 "dsl" => Some(include_str!("content/dsl.md").to_string()),
69 "features" => Some(include_str!("content/features.md").to_string()),
70 _ => None,
71 }
72}
73
74fn render_index() -> String {
75 let mut out = String::new();
76 out.push_str(&format!("# Atman {} — Help Index\n\n", version()));
77 out.push_str("Call `help.show(topic: \"...\")` with one of:\n\n");
78 for t in TOPICS {
79 out.push_str(&format!("- **{}** — {}\n", t.id, t.title));
80 }
81 out
82}
83
84fn render_tools(registry: &ToolRegistry) -> String {
85 let mut tools: Vec<_> = registry.iter();
86 tools.sort_by(|a, b| a.0.cmp(&b.0));
87 let mut out = String::from("# Built-in Tools\n\n");
88 out.push_str("| Tier | Name | Description |\n|------|------|-------------|\n");
89 for (name, tool) in &tools {
90 let tier = format!("{:?}", tool.tier());
91 let desc = tool
92 .description()
93 .unwrap_or("")
94 .lines()
95 .next()
96 .unwrap_or("");
97 out.push_str(&format!("| {} | `{}` | {} |\n", tier, name, desc));
98 }
99 out.push_str(&format!("\n_{} tools registered._\n", tools.len()));
100 out
101}
102
103fn render_tools_offline() -> String {
104 String::from("# Built-in Tools\n\nTool registry not available in this context.\n")
105}
106
107fn render_cli() -> String {
108 let mut out = String::from("# CLI Meta Commands (REPL `:`commands)\n\n");
109 out.push_str("| Command | Aliases | Description |\n|---------|---------|-------------|\n");
110 for cmd in META_COMMANDS {
111 let aliases = if cmd.aliases.is_empty() {
112 "—".to_string()
113 } else {
114 cmd.aliases
115 .iter()
116 .map(|a| format!("`:{a}`"))
117 .collect::<Vec<_>>()
118 .join(", ")
119 };
120 out.push_str(&format!(
121 "| `:{}` | {} | {} |\n",
122 cmd.name, aliases, cmd.desc
123 ));
124 }
125 out.push_str("\n```\n");
126 for cmd in META_COMMANDS {
127 out.push_str(&format!("{}\n", cmd.usage));
128 }
129 out.push_str("```\n");
130 out
131}
132
133fn render_config() -> String {
134 let mut out = String::from("# Configuration Reference\n\n");
135 out.push_str("Config file: `~/.config/atman/config.toml`\n\n");
136
137 out.push_str("## [safety]\n\n");
138 out.push_str("| Field | Type | Description |\n|-------|------|-------------|\n");
139 for (field, ty) in [
140 ("enabled", "bool"),
141 ("mode", "SafetyMode"),
142 ("auto_rewrite", "bool"),
143 ] {
144 let doc = SafetyConfig::get_field_docs(field).unwrap_or("");
145 out.push_str(&format!("| `{}` | {} | {} |\n", field, ty, doc));
146 }
147
148 out.push_str("\n## [storage]\n\n");
149 out.push_str("| Field | Type | Description |\n|-------|------|-------------|\n");
150 let doc = StorageConfig::get_field_docs("scope").unwrap_or("");
151 out.push_str(&format!("| `scope` | Option<StorageScope> | {} |\n", doc));
152
153 out.push_str("\n## [trust]\n\n");
154 out.push_str("| Field | Type | Description |\n|-------|------|-------------|\n");
155 for (field, ty) in [
156 ("mode", "TrustMode"),
157 ("theme", "Theme"),
158 ("outside", "OutsideBehavior"),
159 ] {
160 let doc = TrustConfig::get_field_docs(field).unwrap_or("");
161 out.push_str(&format!("| `{}` | {} | {} |\n", field, ty, doc));
162 }
163
164 out.push_str("\n## [compaction]\n\n");
165 out.push_str(
166 "| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n",
167 );
168 out.push_str("| `review` | string | `\"manual-only\"` | When to show compaction review modal: `always` / `manual-only` / `never` |\n");
169
170 out.push_str("\n## [registry]\n\n");
171 out.push_str(
172 "| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n",
173 );
174 out.push_str("| `auto_snapshot` | bool | `false` | Auto flow snapshot on every run |\n");
175
176 out.push_str("\n## [suggest]\n\n");
177 out.push_str(
178 "| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n",
179 );
180 out.push_str("| `model` | string | `\"gpt-4o-mini\"` | Model for flow suggestions |\n");
181
182 out.push_str("\n## [interjection]\n\n");
183 out.push_str(
184 "| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n",
185 );
186 out.push_str("| `classifier` | string | — | Model for interjection classification |\n");
187
188 out
189}
190
191fn render_mcp() -> String {
192 let mut out = String::from("# MCP Setup\n\n");
193 out.push_str("Config file: `~/.config/atman/mcp_servers.json`\n\n");
194 out.push_str("## McpServerConfig Fields\n\n");
195 out.push_str("| Field | Type | Description |\n|-------|------|-------------|\n");
196 let fields = [
197 ("name", "String"),
198 ("transport", "TransportKind"),
199 ("command", "String"),
200 ("args", "Vec<String>"),
201 ("env", "Vec<(String, String)>"),
202 ("url", "Option<String>"),
203 ("auth_token", "Option<String>"),
204 ("headers", "Vec<(String, String)>"),
205 ("tier", "Tier"),
206 ("timeout_ms", "u64"),
207 ("disabled", "bool"),
208 ];
209 for (field, ty) in &fields {
210 let doc = McpServerConfig::get_field_docs(field).unwrap_or("");
211 out.push_str(&format!("| `{}` | {} | {} |\n", field, ty, doc));
212 }
213
214 out.push_str("\n## TransportKind\n\n");
215 out.push_str("| Variant | Description |\n|---------|-------------|\n");
216 for v in [
217 TransportKind::Stdio,
218 TransportKind::Http,
219 TransportKind::Sse,
220 ] {
221 let doc = v.get_variant_docs();
222 out.push_str(&format!("| `{:?}` | {} |\n", v, doc));
223 }
224
225 out.push_str("\n## Tier\n\n");
226 out.push_str(
227 "| Tier | Approval Level | Description |\n|------|---------------|-------------|\n",
228 );
229 for (tier, level, desc) in [
230 ("Zero", "Auto", "Read-only, safe — no approval needed"),
231 ("One", "Approve", "Requires user approval"),
232 ("Two", "Approve", "File writes and mutations"),
233 ("Three", "Dangerous", "Network operations, push"),
234 ("Four", "Dangerous", "Shell, terminal — most dangerous"),
235 ] {
236 out.push_str(&format!("| `{}` | {} | {} |\n", tier, level, desc));
237 }
238
239 out.push_str("\n## Example\n\n");
240 out.push_str("```json\n");
241 out.push_str("{\n \"mcpServers\": {\n \"filesystem\": {\n");
242 out.push_str(" \"command\": \"npx\",\n \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/tmp\"],\n");
243 out.push_str(" \"tier\": \"Two\",\n \"timeout_ms\": 30000\n }\n }\n}\n");
244 out.push_str("```\n");
245
246 out.push_str("\nIn a flow, include MCP tools with `\"mcp.*\"` in the `tools` list:\n\n");
247 out.push_str("```\nllm {\n tools: [fs.read, \"mcp.*\"]\n}\n```\n");
248 out
249}