108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
mod app;
|
||
mod hoststats;
|
||
mod hotkeys;
|
||
mod localfs;
|
||
mod profiles;
|
||
mod resolve;
|
||
mod session;
|
||
mod sftp;
|
||
mod sshconfig;
|
||
mod term;
|
||
mod transport;
|
||
mod ui;
|
||
|
||
use anyhow::Result;
|
||
use clap::{Parser, Subcommand};
|
||
|
||
/// KomiTerm — минималистичный MobaXterm-подобный клиент (SSH/Telnet) на Rust
|
||
#[derive(Parser, Debug)]
|
||
#[command(name = "komiterm")]
|
||
struct Cli {
|
||
#[command(subcommand)]
|
||
command: Option<Command>,
|
||
|
||
/// Имена сохранённых сессий — каждая откроется в своём табе
|
||
/// (`komiterm myserver staging-box`). Без аргументов и без subcommand —
|
||
/// открывается менеджер сессий и запускается один таб.
|
||
session: Vec<String>,
|
||
}
|
||
|
||
#[derive(Subcommand, Debug)]
|
||
enum Command {
|
||
/// Управление профилями входа (логин + пароль/ключ/agent),
|
||
/// переиспользуемыми между сессиями.
|
||
Profile {
|
||
#[command(subcommand)]
|
||
action: ProfileAction,
|
||
},
|
||
}
|
||
|
||
#[derive(Subcommand, Debug)]
|
||
enum ProfileAction {
|
||
/// Добавить новый профиль (интерактивно спросит логин и способ входа)
|
||
Add,
|
||
/// Показать все сохранённые профили
|
||
List,
|
||
/// Удалить профиль по имени
|
||
Remove { name: String },
|
||
}
|
||
|
||
#[tokio::main]
|
||
async fn main() -> Result<()> {
|
||
let cli = Cli::parse();
|
||
|
||
if let Some(Command::Profile { action }) = cli.command {
|
||
return handle_profile_action(action);
|
||
}
|
||
|
||
let sessions = session::SessionStore::load()?;
|
||
|
||
// Пустой список — не ошибка: app::run покажет список сессий прямо
|
||
// в TUI (уже в raw-режиме), а не будет спрашивать тут через stdin.
|
||
let targets = cli
|
||
.session
|
||
.iter()
|
||
.map(|name| {
|
||
sessions
|
||
.get(name)
|
||
.cloned()
|
||
.ok_or_else(|| anyhow::anyhow!("сессия '{name}' не найдена"))
|
||
})
|
||
.collect::<Result<Vec<_>>>()?;
|
||
|
||
let credentials = profiles::CredentialStore::load()?;
|
||
let ssh_config = sshconfig::SshConfig::load_default()?;
|
||
|
||
app::run(targets, sessions, credentials, ssh_config).await
|
||
}
|
||
|
||
fn handle_profile_action(action: ProfileAction) -> Result<()> {
|
||
let mut store = profiles::CredentialStore::load()?;
|
||
match action {
|
||
ProfileAction::Add => {
|
||
let profile = profiles::prompt_new_profile()?;
|
||
store.profiles.retain(|p| p.name != profile.name);
|
||
store.profiles.push(profile);
|
||
store.save()?;
|
||
println!("Профиль сохранён в {}", profiles::CredentialStore::config_path()?.display());
|
||
}
|
||
ProfileAction::List => {
|
||
if store.profiles.is_empty() {
|
||
println!("Профилей пока нет. Добавь через `komiterm profile add`.");
|
||
}
|
||
for p in &store.profiles {
|
||
println!("- {} ({})", p.name, p.username);
|
||
}
|
||
}
|
||
ProfileAction::Remove { name } => {
|
||
let before = store.profiles.len();
|
||
store.profiles.retain(|p| p.name != name);
|
||
if store.profiles.len() == before {
|
||
anyhow::bail!("профиль '{name}' не найден");
|
||
}
|
||
store.save()?;
|
||
println!("Профиль '{name}' удалён.");
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|