feat: follow XDG spec on linux/mac and use windows known folders for config and logs (#1153)
This commit is contained in:
@@ -2,7 +2,7 @@ use rand::{distributions::Alphanumeric, Rng};
|
||||
use std::process;
|
||||
|
||||
use crate::prompt::rustyline::RustylinePrompt;
|
||||
use crate::session::{ensure_session_dir, get_most_recent_session, Session};
|
||||
use crate::session::{ensure_session_dir, get_most_recent_session, legacy_session_dir, Session};
|
||||
use console::style;
|
||||
use goose::agents::extension::{Envs, ExtensionError};
|
||||
use goose::agents::AgentFactory;
|
||||
@@ -121,9 +121,18 @@ pub async fn build_session(
|
||||
if session_file.exists() {
|
||||
let prompt = Box::new(RustylinePrompt::new());
|
||||
return Session::new(agent, prompt, session_file);
|
||||
} else {
|
||||
eprintln!("Session '{}' not found, starting new session", session_name);
|
||||
}
|
||||
|
||||
// LEGACY NOTE: remove this once old paths are no longer needed.
|
||||
if let Some(legacy_dir) = legacy_session_dir() {
|
||||
let legacy_file = legacy_dir.join(format!("{}.jsonl", session_name));
|
||||
if legacy_file.exists() {
|
||||
let prompt = Box::new(RustylinePrompt::new());
|
||||
return Session::new(agent, prompt, legacy_file);
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Session '{}' not found, starting new session", session_name);
|
||||
} else {
|
||||
// Try to resume most recent session
|
||||
if let Ok(session_file) = get_most_recent_session() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use goose::providers::base::ProviderUsage;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -13,8 +14,15 @@ pub fn log_usage(session_file: String, usage: Vec<ProviderUsage>) {
|
||||
};
|
||||
|
||||
// Ensure log directory exists
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
let log_dir = home_dir.join(".config").join("goose").join("logs");
|
||||
if let Ok(home_dir) = choose_app_strategy(crate::APP_STRATEGY.clone()) {
|
||||
// choose_app_strategy().state_dir()
|
||||
// - macOS/Linux: ~/.local/state/goose/logs/
|
||||
// - Windows: ~\AppData\Roaming\Block\goose\data\logs
|
||||
// - Windows has no convention for state_dir, use data_dir instead
|
||||
let log_dir = home_dir
|
||||
.in_state_dir("logs")
|
||||
.unwrap_or_else(|| home_dir.in_data_dir("logs"));
|
||||
|
||||
if let Err(e) = std::fs::create_dir_all(&log_dir) {
|
||||
eprintln!("Failed to create log directory: {}", e);
|
||||
return;
|
||||
@@ -49,6 +57,7 @@ pub fn log_usage(session_file: String, usage: Vec<ProviderUsage>) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use goose::providers::base::{ProviderUsage, Usage};
|
||||
|
||||
use crate::{
|
||||
@@ -59,11 +68,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_session_logging() {
|
||||
run_with_tmp_dir(|| {
|
||||
let home_dir = dirs::home_dir().unwrap();
|
||||
let home_dir = choose_app_strategy(crate::APP_STRATEGY.clone()).unwrap();
|
||||
|
||||
let log_file = home_dir
|
||||
.join(".config")
|
||||
.join("goose")
|
||||
.join("logs")
|
||||
.in_state_dir("logs")
|
||||
.unwrap_or_else(|| home_dir.in_data_dir("logs"))
|
||||
.join("goose.log");
|
||||
|
||||
log_usage(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{Context, Result};
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tracing_appender::rolling::Rotation;
|
||||
@@ -12,17 +13,16 @@ use goose::tracing::langfuse_layer;
|
||||
/// Returns the directory where log files should be stored.
|
||||
/// Creates the directory structure if it doesn't exist.
|
||||
fn get_log_directory() -> Result<PathBuf> {
|
||||
let home = if cfg!(windows) {
|
||||
std::env::var("USERPROFILE").context("USERPROFILE environment variable not set")?
|
||||
} else {
|
||||
std::env::var("HOME").context("HOME environment variable not set")?
|
||||
};
|
||||
// choose_app_strategy().state_dir()
|
||||
// - macOS/Linux: ~/.local/state/goose/logs/cli
|
||||
// - Windows: ~\AppData\Roaming\Block\goose\data\logs\cli
|
||||
// - Windows has no convention for state_dir, use data_dir instead
|
||||
let home_dir = choose_app_strategy(crate::APP_STRATEGY.clone())
|
||||
.context("HOME environment variable not set")?;
|
||||
|
||||
let base_log_dir = PathBuf::from(home)
|
||||
.join(".config")
|
||||
.join("goose")
|
||||
.join("logs")
|
||||
.join("cli"); // Add cli-specific subdirectory
|
||||
let base_log_dir = home_dir
|
||||
.in_state_dir("logs/cli")
|
||||
.unwrap_or_else(|| home_dir.in_data_dir("logs/cli"));
|
||||
|
||||
// Create date-based subdirectory
|
||||
let now = chrono::Local::now();
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use etcetera::AppStrategyArgs;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static APP_STRATEGY: Lazy<AppStrategyArgs> = Lazy::new(|| AppStrategyArgs {
|
||||
top_level_domain: "Block".to_string(),
|
||||
author: "Block".to_string(),
|
||||
app_name: "goose".to_string(),
|
||||
});
|
||||
|
||||
mod commands;
|
||||
mod log_usage;
|
||||
|
||||
@@ -30,8 +30,8 @@ fn shorten_path(path: &str) -> String {
|
||||
let path = PathBuf::from(path);
|
||||
|
||||
// First try to convert to ~ if it's in home directory
|
||||
let home = dirs::home_dir();
|
||||
let path_str = if let Some(home) = home {
|
||||
let home = etcetera::home_dir();
|
||||
let path_str = if let Ok(home) = home {
|
||||
if let Ok(stripped) = path.strip_prefix(home) {
|
||||
format!("~/{}", stripped.display())
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use core::panic;
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use futures::StreamExt;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufRead, Write};
|
||||
@@ -14,8 +15,12 @@ use mcp_core::role::Role;
|
||||
|
||||
// File management functions
|
||||
pub fn ensure_session_dir() -> Result<PathBuf> {
|
||||
let home_dir = dirs::home_dir().ok_or(anyhow::anyhow!("Could not determine home directory"))?;
|
||||
let config_dir = home_dir.join(".config").join("goose").join("sessions");
|
||||
// choose_app_strategy().data_dir()
|
||||
// - macOS/Linux: ~/.local/share/goose/sessions/
|
||||
// - Windows: ~\AppData\Roaming\Block\goose\data\sessions
|
||||
let config_dir = choose_app_strategy(crate::APP_STRATEGY.clone())
|
||||
.expect("goose requires a home dir")
|
||||
.in_data_dir("sessions");
|
||||
|
||||
if !config_dir.exists() {
|
||||
fs::create_dir_all(&config_dir)?;
|
||||
@@ -24,6 +29,15 @@ pub fn ensure_session_dir() -> Result<PathBuf> {
|
||||
Ok(config_dir)
|
||||
}
|
||||
|
||||
/// LEGACY NOTE: remove this once old paths are no longer needed.
|
||||
pub fn legacy_session_dir() -> Option<PathBuf> {
|
||||
// legacy path was in the config dir ~/.config/goose/sessions/
|
||||
// ignore errors if we can't re-create the legacy session dir
|
||||
choose_app_strategy(crate::APP_STRATEGY.clone())
|
||||
.map(|strategy| strategy.in_config_dir("sessions"))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn get_most_recent_session() -> Result<PathBuf> {
|
||||
let session_dir = ensure_session_dir()?;
|
||||
let mut entries = fs::read_dir(&session_dir)?
|
||||
@@ -31,6 +45,19 @@ pub fn get_most_recent_session() -> Result<PathBuf> {
|
||||
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "jsonl"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// LEGACY NOTE: remove this once old paths are no longer needed.
|
||||
if entries.is_empty() {
|
||||
if let Some(old_dir) = legacy_session_dir() {
|
||||
// okay to return the error via ?, since that means we have no sessions in the
|
||||
// new location, and this old location doesn't exist, so a new session will be created
|
||||
let old_entries = fs::read_dir(&old_dir)?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "jsonl"))
|
||||
.collect::<Vec<_>>();
|
||||
entries.extend(old_entries);
|
||||
}
|
||||
}
|
||||
|
||||
if entries.is_empty() {
|
||||
return Err(anyhow::anyhow!("No session files found"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user