1use crate::config::{CapabilityMode, CommandConfig, Config};
2
3pub fn print(config: &Config) {
4 print!("{}", render(config));
5}
6
7pub fn render(config: &Config) -> String {
8 let mut output = String::new();
9 output.push_str("---\n");
10 output.push_str("name: via\n");
11 output.push_str("description: Use via when a task needs authenticated access to configured services without asking for or handling raw secrets. via resolves credentials from 1Password and runs configured capabilities such as REST API calls or delegated CLIs.\n");
12 output.push_str("---\n\n");
13 output.push_str("# via\n\n");
14 output.push_str("Use `via capabilities --json` before authenticated work to discover configured services and capabilities.\n\n");
15 output.push_str("Rules:\n");
16 output.push_str("- Never ask the user for tokens or passwords.\n");
17 output.push_str("- Never call the underlying secret provider directly.\n");
18 output
19 .push_str("- If provider authentication is not ready, ask the user to run `via login`.\n");
20 output.push_str("- Prefer REST capabilities because secrets stay inside `via`.\n");
21 output.push_str("- Use delegated capabilities only when the configured binary is trusted and its native behavior is required.\n");
22 output.push_str("- Do not print environment variables or credentials.\n");
23 output.push_str("- Run `via config doctor <service>` when a configured service fails.\n\n");
24 output.push_str("Configured capabilities:\n");
25
26 for (service_name, service) in &config.services {
27 output.push('\n');
28 match &service.description {
29 Some(description) => output.push_str(&format!("- `{service_name}`: {description}\n")),
30 None => output.push_str(&format!("- `{service_name}`\n")),
31 }
32 if let Some(hint) = &service.hint {
33 output.push_str(&format!(" - Example: `{hint}`.\n"));
34 }
35 for (command_name, command) in &service.commands {
36 let usage = match command.mode() {
37 CapabilityMode::Rest => rest_usage(service_name, command_name, command),
38 CapabilityMode::Delegated => {
39 format!("via {service_name} {command_name} <tool-args...>")
40 }
41 };
42 match command.description() {
43 Some(description) => output.push_str(&format!(
44 " - `{command_name}`: {description} Use `{usage}`.\n"
45 )),
46 None => output.push_str(&format!(" - `{command_name}`: use `{usage}`.\n")),
47 }
48 }
49 }
50
51 output
52}
53
54fn rest_usage(service_name: &str, command_name: &str, command: &CommandConfig) -> String {
55 match command {
56 CommandConfig::Rest(rest) if !rest.asset_hosts.is_empty() => format!(
57 "via {service_name} {command_name} <path> or via {service_name} {command_name} GET <asset-url> --output <file>"
58 ),
59 _ => format!("via {service_name} {command_name} <path>"),
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 fn config() -> Config {
68 Config::from_toml_str(
69 r#"
70version = 1
71
72[providers.onepassword]
73type = "1password"
74
75[services.github]
76description = "GitHub access"
77hint = "via github api /user"
78provider = "onepassword"
79
80[services.github.secrets]
81token = "op://Private/GitHub/token"
82
83[services.github.commands.api]
84description = "REST access."
85mode = "rest"
86base_url = "https://api.github.com"
87asset_hosts = ["uploads.linear.app"]
88
89[services.github.commands.gh]
90description = "CLI access."
91mode = "delegated"
92program = "gh"
93"#,
94 )
95 .unwrap()
96 }
97
98 #[test]
99 fn renders_agent_rules_and_configured_capabilities() {
100 let output = render(&config());
101
102 assert!(output.contains("Never ask the user for tokens"));
103 assert!(output.contains("Never call the underlying secret provider directly"));
104 assert!(output.contains("via login"));
105 assert!(output.contains("Example: `via github api /user`."));
106 assert!(output.contains("via github api <path>"));
107 assert!(output.contains("via github api GET <asset-url> --output <file>"));
108 assert!(output.contains("via github gh <tool-args...>"));
109 assert!(!output.contains("op://Private"));
110 assert!(!output.contains("op read"));
111 }
112}