//! `/help` (hub spec ยง6.3): getting started with this hub's own address, //! remote name and certificate filled in, the keyboard, and a glossary //! for people coming from GitHub. use super::repo::Quickstart; use super::{Page, Shell}; use crate::auth::Viewer; use crate::Shared; use askama::Template; use axum::extract::State; #[derive(Template)] #[template(path = "help.html")] pub struct HelpPage { shell: Shell, q: Quickstart, base: String, /// The signed-in user, or a placeholder, for example URLs. who: String, /// What an agent's changes are authored as: the signed-in user's /// name, email and newest linked key, or placeholders (#352). author: (String, String, String), } pub async fn help(State(app): State, viewer: Viewer) -> Page { let shell = Shell::new(&app, "/help", &viewer); let who = shell.user.clone().unwrap_or_else(|| "OWNER".into()); let base = app.config.base_url().trim_end_matches('/').to_string(); let mut author = ("YOUR NAME".to_string(), "you@example.org".to_string(), "YOUR_KEY".to_string()); if let Some(name) = &shell.user { if let Ok(Some(a)) = crate::db::accounts::account_by_name(&app.db, name).await { let newest = crate::db::accounts::identities_of(&app.db, a.id).await.ok().and_then(|ids| ids.into_iter().next()); if let Some(id) = newest { author = (id.name, id.email, id.key); } else { author.0 = if a.display_name.is_empty() { a.username } else { a.display_name }; } } } Page(HelpPage { shell, q: Quickstart::new(&app, true), base, who, author }) } impl HelpPage { /// An example repository address on this hub. pub fn repo_url(&self) -> String { format!("{}/{}/REPOSITORY", self.base, self.who) } /// An agent's own home, keyless, with its grant kept (#287), authored /// as you with the key you make its grant with (#352): without an /// author its changes are unattributed, and a grant made by a newer key /// than its author key does not count for it. pub fn agent_home_cmd(&self) -> String { let (name, email, key) = &self.author; format!( "mkdir -p ~/.oxy-agents/claude/keys\n\ oxy identity export ~/.oxy-agents/claude/keys/{key}.identity # in a checkout of yours, as you\n\ export OXY_HOME=~/.oxy-agents/claude\n\ oxy config --user author.name \"{name}\"\n\ oxy config --user author.email \"{email}\"\n\ oxy config --user author.key {key}\n\ oxy config --user author.signer keyless\n\ oxy config --user keyless.hub {base}\n\ oxy config --user core.sign seal\n\ oxy auth grant {base} # paste the token, then Ctrl-D", base = self.base ) } /// A repository's trust policy admitting an agent's certified keys (#287). pub fn subject_grant_cmd(&self) -> String { format!( "curl -fsSL {}/.well-known/oxyhub-key -o hub.identity\noxy policy grant-subject agent:claude@{} --issuer hub.identity --scope changes,refs", self.base, self.who ) } pub fn delegate_cmd(&self) -> String { format!( "curl -fsSL {}/.well-known/oxyhub-key -o hub.identity\noxy identity delegate add hub.identity --scope changes,refs", self.base ) } pub fn clone_cmd(&self) -> String { let url = self.repo_url(); match &self.q.ca_url { Some(_) => format!("OXY_CA={} oxy clone {url}", self.q.ca_file), None => format!("oxy clone {url}"), } } }