feat: add session list command in cli (#1586)

This commit is contained in:
Parker Egli
2025-03-12 10:04:01 -06:00
committed by GitHub
parent bdb90a56ba
commit d6cb7c6d87
6 changed files with 147 additions and 59 deletions

View File

@@ -3,4 +3,5 @@ pub mod bench;
pub mod configure;
pub mod info;
pub mod mcp;
pub mod session;
pub mod update;

View File

@@ -0,0 +1,47 @@
use anyhow::Result;
use goose::session::info::{get_session_info, SessionInfo};
pub fn handle_session_list(verbose: bool, format: String) -> Result<()> {
let sessions = match get_session_info() {
Ok(sessions) => sessions,
Err(e) => {
tracing::error!("Failed to list sessions: {:?}", e);
return Err(anyhow::anyhow!("Failed to list sessions"));
}
};
match format.as_str() {
"json" => {
println!("{}", serde_json::to_string(&sessions)?);
}
_ => {
if sessions.is_empty() {
println!("No sessions found");
return Ok(());
} else {
println!("Available sessions:");
for SessionInfo {
id,
path,
metadata,
modified,
} in sessions
{
let description = if metadata.description.is_empty() {
"(none)"
} else {
&metadata.description
};
let output = format!("{} - {} - {}", id, description, modified);
if verbose {
println!(" {}", output);
println!(" Path: {}", path);
} else {
println!("{}", output);
}
}
}
}
}
Ok(())
}

View File

@@ -8,6 +8,7 @@ use goose_cli::commands::bench::{list_suites, run_benchmark};
use goose_cli::commands::configure::handle_configure;
use goose_cli::commands::info::handle_info;
use goose_cli::commands::mcp::run_server;
use goose_cli::commands::session::handle_session_list;
use goose_cli::logging::setup_logging;
use goose_cli::session;
use goose_cli::session::build_session;
@@ -53,6 +54,23 @@ fn extract_identifier(identifier: Identifier) -> session::Identifier {
}
}
#[derive(Subcommand)]
enum SessionCommand {
#[command(about = "List all available sessions")]
List {
#[arg(short, long, help = "List all available sessions")]
verbose: bool,
#[arg(
short,
long,
help = "Output format (text, json)",
default_value = "text"
)]
format: String,
},
}
#[derive(Subcommand)]
enum Command {
/// Configure Goose settings
@@ -77,6 +95,8 @@ enum Command {
visible_alias = "s"
)]
Session {
#[command(subcommand)]
command: Option<SessionCommand>,
/// Identifier for the chat session
#[command(flatten)]
identifier: Option<Identifier>,
@@ -299,27 +319,36 @@ async fn main() -> Result<()> {
let _ = run_server(&name).await;
}
Some(Command::Session {
command,
identifier,
resume,
debug,
extension,
builtin,
}) => {
let mut session = build_session(
identifier.map(extract_identifier),
resume,
extension,
builtin,
debug,
)
.await;
setup_logging(
session.session_file().file_stem().and_then(|s| s.to_str()),
None,
)?;
let _ = session.interactive(None).await;
return Ok(());
match command {
Some(SessionCommand::List { verbose, format }) => {
handle_session_list(verbose, format)?;
return Ok(());
}
None => {
// Run session command by default
let mut session = build_session(
identifier.map(extract_identifier),
resume,
extension,
builtin,
debug,
)
.await;
setup_logging(
session.session_file().file_stem().and_then(|s| s.to_str()),
None,
)?;
let _ = session.interactive(None).await;
return Ok(());
}
}
}
Some(Command::Run {
instructions,

View File

@@ -7,16 +7,9 @@ use axum::{
};
use goose::message::Message;
use goose::session;
use goose::session::info::{get_session_info, SessionInfo};
use serde::Serialize;
#[derive(Serialize)]
struct SessionInfo {
id: String,
path: String,
modified: String,
metadata: session::SessionMetadata,
}
#[derive(Serialize)]
struct SessionListResponse {
sessions: Vec<SessionInfo>,
@@ -44,43 +37,9 @@ async fn list_sessions(
return Err(StatusCode::UNAUTHORIZED);
}
let sessions = match session::list_sessions() {
Ok(sessions) => sessions,
Err(e) => {
tracing::error!("Failed to list sessions: {:?}", e);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
let sessions = get_session_info().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let session_infos = sessions
.into_iter()
.map(|(id, path)| {
// Get last modified time as string
let modified = path
.metadata()
.and_then(|m| m.modified())
.map(|time| {
chrono::DateTime::<chrono::Utc>::from(time)
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string()
})
.unwrap_or_else(|_| "Unknown".to_string());
// Get session description
let metadata = session::read_metadata(&path).expect("Failed to read session metadata");
SessionInfo {
id,
path: path.to_string_lossy().to_string(),
modified,
metadata,
}
})
.collect();
Ok(Json(SessionListResponse {
sessions: session_infos,
}))
Ok(Json(SessionListResponse { sessions }))
}
// Get a specific session's history

View File

@@ -0,0 +1,49 @@
use anyhow::Result;
use serde::Serialize;
use crate::session::{self, SessionMetadata};
#[derive(Serialize)]
pub struct SessionInfo {
pub id: String,
pub path: String,
pub modified: String,
pub metadata: SessionMetadata,
}
pub fn get_session_info() -> Result<Vec<SessionInfo>> {
let sessions = match session::list_sessions() {
Ok(sessions) => sessions,
Err(e) => {
tracing::error!("Failed to list sessions: {:?}", e);
return Err(anyhow::anyhow!("Failed to list sessions"));
}
};
let session_infos = sessions
.into_iter()
.map(|(id, path)| {
// Get last modified time as string
let modified = path
.metadata()
.and_then(|m| m.modified())
.map(|time| {
chrono::DateTime::<chrono::Utc>::from(time)
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string()
})
.unwrap_or_else(|_| "Unknown".to_string());
// Get session description
let metadata = session::read_metadata(&path).expect("Failed to read session metadata");
SessionInfo {
id,
path: path.to_string_lossy().to_string(),
modified,
metadata,
}
})
.collect();
Ok(session_infos)
}

View File

@@ -1,3 +1,4 @@
pub mod info;
pub mod storage;
// Re-export common session types and functions
@@ -6,3 +7,5 @@ pub use storage::{
get_path, list_sessions, persist_messages, read_messages, read_metadata, update_metadata,
Identifier, SessionMetadata,
};
pub use info::{get_session_info, SessionInfo};