//! alias: manage command aliases (cc -> claude-code) in the user config. use super::*; use crate::cli::AliasCmd; use crate::output::print_json; use anyhow::{anyhow, Context, Result}; use std::path::PathBuf; /// Ensure the user config file exists and return its path. fn user_config(app: &App) -> Result { let dir = app .paths .config_dir .clone() .ok_or_else(|| anyhow!("cannot determine the user config directory (no HOME set)"))?; std::fs::create_dir_all(&dir)?; let path = dir.join(crate::config::CONFIG_FILE_NAME); if !path.exists() { std::fs::write(&path, concat!("version: \"1.0\"\n", "agents: []\n")) .with_context(|| format!("cannot create {}", path.display()))?; } Ok(path) } /// Read the aliases map from the user config file. fn read_aliases(app: &App) -> Result<(PathBuf, serde_yaml::Mapping)> { let path = user_config(app)?; let text = std::fs::read_to_string(&path) .with_context(|| format!("cannot read {}", path.display()))?; let mut value: serde_yaml::Value = serde_yaml::from_str(&text) .with_context(|| format!("{} is not valid YAML", path.display()))?; let mapping = value .as_mapping_mut() .ok_or_else(|| anyhow!("{} must contain a YAML mapping", path.display()))?; let aliases = mapping .entry(serde_yaml::Value::String("aliases".to_string())) .or_insert_with(|| serde_yaml::Value::Mapping(Default::default())) .as_mapping() .cloned() .unwrap_or_default(); Ok((path, aliases)) } fn write_aliases(app: &App, path: &std::path::Path, aliases: serde_yaml::Mapping) -> Result<()> { let text = std::fs::read_to_string(path) .with_context(|| format!("cannot read {}", path.display()))?; let mut value: serde_yaml::Value = serde_yaml::from_str(&text) .with_context(|| format!("{} is not valid YAML", path.display()))?; if let Some(mapping) = value.as_mapping_mut() { mapping.insert( serde_yaml::Value::String("aliases".to_string()), serde_yaml::Value::Mapping(aliases), ); } std::fs::write(path, serde_yaml::to_string(&value)?) .with_context(|| format!("cannot write {}", path.display())) } pub fn run(app: &App, sub: &AliasCmd) -> Result { match sub { AliasCmd::Add { name, target } => { // The target must be a known agent (or another alias). require_agent(app, target)?; let (path, mut aliases) = read_aliases(app)?; aliases.insert( serde_yaml::Value::String(name.clone()), serde_yaml::Value::String(target.clone()), ); write_aliases(app, &path, aliases)?; app.log.success(&format!("alias '{name}' -> '{target}'")); Ok(0) } AliasCmd::Remove { name } => { let (path, mut aliases) = read_aliases(app)?; let removed = aliases .remove(&serde_yaml::Value::String(name.clone())) .is_some(); if removed { write_aliases(app, &path, aliases)?; app.log.success(&format!("alias '{name}' removed")); } else { app.log.info(&format!("alias '{name}' does not exist")); } Ok(0) } AliasCmd::List => { if app.json() { print_json(&app.config.aliases); return Ok(0); } if app.config.aliases.is_empty() { app.log.info("no aliases defined — 'am alias add cc claude-code'"); return Ok(0); } let mut table = crate::output::Table::new(vec!["ALIAS", "TARGET"]); for (k, v) in &app.config.aliases { table.row(vec![k.clone(), v.clone()]); } print!("{}", table.render()); Ok(0) } } }