use crate::config::Config;
use crate::error::{Result, VirtuosoError};
use crate::models::SessionInfo;
use crate::output::OutputFormat;
use crate::transport::tunnel::SSHClient;
use serde_json::{json, Value};
pub fn list(format: OutputFormat) -> Result<Value> {
if let Ok(cfg) = Config::from_env() {
if cfg.is_remote() {
if let Ok(client) = SSHClient::from_env(cfg.keep_remote_files) {
let _ = SessionInfo::sync_from_remote(&client.runner);
}
}
}
let mut sessions = SessionInfo::list()
.map_err(|e| VirtuosoError::Execution(format!("failed to read sessions: {e}")))?;
let sessions_dir = SessionInfo::sessions_dir();
sessions.retain(|s| {
if s.is_alive() {
true
} else {
let _ = std::fs::remove_file(sessions_dir.join(format!("{}.json", s.id)));
false
}
});
if format == OutputFormat::Json {
return Ok(json!({
"status": "success",
"count": sessions.len(),
"sessions": sessions.iter().map(|s| json!({
"id": s.id,
"port": s.port,
"pid": s.pid,
"host": s.host,
"user": s.user,
"created": s.created,
})).collect::<Vec<_>>(),
}));
}
if sessions.is_empty() {
println!("No active Virtuoso sessions found.");
println!("Start Virtuoso and run RBStart() in CIW to register a session.");
return Ok(json!({"status": "success", "count": 0}));
}
println!(
"{:<20} {:>6} {:>7} {:<12} CREATED",
"SESSION ID", "PORT", "PID", "HOST"
);
println!("{}", "-".repeat(72));
for s in &sessions {
println!(
"{:<20} {:>6} {:>7} {:<12} {}",
s.id, s.port, s.pid, s.host, s.created
);
}
Ok(json!({"status": "success", "count": sessions.len()}))
}
pub fn current() -> Result<Value> {
let live = SessionInfo::list_alive();
match live.len() {
0 => Ok(json!({"status": "success", "session": null, "note": "no live sessions; VB_PORT will be used"})),
1 => Ok(json!({
"status": "success",
"session": live[0].id,
"port": live[0].port,
"auto_selected": true,
})),
_ => {
let ids: Vec<&str> = live.iter().map(|s| s.id.as_str()).collect();
Ok(json!({
"status": "ambiguous",
"sessions": ids,
"note": "use --session <id> to select one",
}))
}
}
}
pub fn cleanup() -> Result<Value> {
let all = SessionInfo::list().unwrap_or_default();
let dir = SessionInfo::sessions_dir();
let mut removed = Vec::new();
for s in &all {
if !s.is_alive() {
let path = dir.join(format!("{}.json", s.id));
if std::fs::remove_file(&path).is_ok() {
removed.push(s.id.clone());
}
}
}
Ok(json!({
"status": "success",
"removed": removed.len(),
"sessions": removed,
}))
}
pub fn history(id: &str, only_skill: bool, only_cmd: bool, limit: usize) -> Result<Value> {
let show_skill = !only_cmd;
let show_cmd = !only_skill;
let skill_entries: Vec<Value> = if show_skill {
crate::history::load_skill(id, limit)
.into_iter()
.map(|e| serde_json::json!({"type":"skill","ts":e.ts,"ok":e.ok,"skill":e.skill,"output":e.output}))
.collect()
} else {
vec![]
};
let cmd_entries: Vec<Value> = if show_cmd {
crate::history::load_cmd(Some(id), limit)
.into_iter()
.map(|e| serde_json::json!({"type":"cmd","ts":e.ts,"cmd":e.cmd,"exit_code":e.exit_code}))
.collect()
} else {
vec![]
};
Ok(json!({
"status": "success",
"session": id,
"skill_count": skill_entries.len(),
"cmd_count": cmd_entries.len(),
"skill": skill_entries,
"cmd": cmd_entries,
}))
}
pub fn show(id: &str, _format: OutputFormat) -> Result<Value> {
let s = SessionInfo::load(id)
.map_err(|e| VirtuosoError::NotFound(format!("session '{id}' not found: {e}")))?;
Ok(json!({
"status": "success",
"session": {
"id": s.id,
"port": s.port,
"pid": s.pid,
"host": s.host,
"user": s.user,
"created": s.created,
"alive": s.is_alive(),
}
}))
}