Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ run-ui:
@echo "Running UI..."
cd ui/desktop && npm install && npm run start-gui

run-ui-playwright:
#!/usr/bin/env sh
just release-binary
echo "Running UI with Playwright debugging..."
RUN_DIR="$HOME/goose-runs/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$RUN_DIR"
echo "Using isolated directory: $RUN_DIR"
cd ui/desktop && ENABLE_PLAYWRIGHT=true GOOSE_PATH_ROOT="$RUN_DIR" npm run start-gui

run-ui-only:
@echo "Running UI..."
cd ui/desktop && npm install && npm run start-gui
Expand Down Expand Up @@ -463,4 +472,4 @@ build-test-tools:

record-mcp-tests: build-test-tools
GOOSE_RECORD_MCP=1 cargo test --package goose --test mcp_integration_test
git add crates/goose/tests/mcp_replays/
git add crates/goose/tests/mcp_replays/
9 changes: 3 additions & 6 deletions crates/goose-cli/src/commands/info.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use console::style;
use etcetera::{choose_app_strategy, AppStrategy};
use goose::config::paths::Paths;
use goose::config::Config;
use serde_yaml;

Expand All @@ -9,11 +9,8 @@ fn print_aligned(label: &str, value: &str, width: usize) {
}

pub fn handle_info(verbose: bool) -> Result<()> {
let data_dir = choose_app_strategy(crate::APP_STRATEGY.clone())?;
let logs_dir = data_dir
.in_state_dir("logs")
.unwrap_or_else(|| data_dir.in_data_dir("logs"));
let sessions_dir = data_dir.in_data_dir("sessions");
let logs_dir = Paths::in_state_dir("logs");
let sessions_dir = Paths::in_data_dir("sessions");

// Get paths using a stored reference to the global config
let config = Config::global();
Expand Down
8 changes: 0 additions & 8 deletions crates/goose-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use etcetera::AppStrategyArgs;
use once_cell::sync::Lazy;
pub mod cli;
pub mod commands;
pub mod logging;
Expand All @@ -11,9 +9,3 @@ pub mod signal;

// Re-export commonly used types
pub use session::CliSession;

pub static APP_STRATEGY: Lazy<AppStrategyArgs> = Lazy::new(|| AppStrategyArgs {
top_level_domain: "Block".to_string(),
author: "Block".to_string(),
app_name: "goose".to_string(),
});
8 changes: 2 additions & 6 deletions crates/goose-cli/src/project_tracker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use etcetera::{choose_app_strategy, AppStrategy};
use goose::config::paths::Paths;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
Expand Down Expand Up @@ -41,11 +41,7 @@ pub struct ProjectInfoDisplay {
impl ProjectTracker {
/// Get the path to the projects.json file
fn get_projects_file() -> Result<PathBuf> {
let projects_file = choose_app_strategy(crate::APP_STRATEGY.clone())
.context("goose requires a home dir")?
.in_data_dir("projects.json");

// Ensure data directory exists
let projects_file = Paths::in_data_dir("projects.json");
if let Some(parent) = projects_file.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
Expand Down
13 changes: 2 additions & 11 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use goose::utils::safe_truncate;

use anyhow::{Context, Result};
use completion::GooseCompleter;
use etcetera::{choose_app_strategy, AppStrategy};
use goose::agents::extension::{Envs, ExtensionConfig};
use goose::agents::types::RetryConfig;
use goose::agents::{Agent, SessionConfig};
Expand All @@ -37,6 +36,7 @@ use rmcp::model::PromptMessage;
use rmcp::model::ServerNotification;
use rmcp::model::{ErrorCode, ErrorData};

use goose::config::paths::Paths;
use goose::conversation::message::{Message, MessageContent};
use rand::{distributions::Alphanumeric, Rng};
use rustyline::EditMode;
Expand Down Expand Up @@ -413,29 +413,20 @@ impl CliSession {
let completer = GooseCompleter::new(self.completion_cache.clone());
editor.set_helper(Some(completer));

// Create and use a global history file in ~/.config/goose directory
// This allows command history to persist across different chat sessions
// instead of being tied to each individual session's messages
let strategy =
choose_app_strategy(crate::APP_STRATEGY.clone()).expect("goose requires a home dir");
let config_dir = strategy.config_dir();
let history_file = config_dir.join("history.txt");
let history_file = Paths::config_dir().join("history.txt");

// Ensure config directory exists
if let Some(parent) = history_file.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}

// Load history from the global file
if history_file.exists() {
if let Err(err) = editor.load_history(&history_file) {
eprintln!("Warning: Failed to load command history: {}", err);
}
}

// Helper function to save history after commands
let save_history =
|editor: &mut rustyline::Editor<GooseCompleter, rustyline::history::DefaultHistory>| {
if let Err(err) = editor.save_history(&history_file) {
Expand Down
4 changes: 2 additions & 2 deletions crates/goose-mcp/src/developer/shell.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use goose::config::get_config_dir;
use std::{env, ffi::OsString, process::Stdio};

use goose::config::paths::Paths;
#[cfg(unix)]
#[allow(unused_imports)] // False positive: trait is used for process_group method
use std::os::unix::process::CommandExt;
Expand Down Expand Up @@ -30,7 +30,7 @@ impl Default for ShellConfig {

// Configure environment based on shell type
let envs = if shell_name == "bash" {
let bash_env = get_config_dir().join(".bash_env").into_os_string();
let bash_env = Paths::config_dir().join(".bash_env").into_os_string();
vec![(OsString::from("BASH_ENV"), bash_env)]
} else {
vec![]
Expand Down
15 changes: 3 additions & 12 deletions crates/goose-server/src/routes/config_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ use axum::{
routing::{delete, get, post},
Json, Router,
};
use etcetera::{choose_app_strategy, AppStrategy};
use goose::config::paths::Paths;
use goose::config::ExtensionEntry;
use goose::config::APP_STRATEGY;
use goose::config::{Config, ConfigError};
use goose::model::ModelConfig;
use goose::providers::base::ProviderMetadata;
Expand Down Expand Up @@ -565,11 +564,7 @@ pub async fn upsert_permissions(
)
)]
pub async fn backup_config() -> Result<Json<String>, StatusCode> {
let config_dir = choose_app_strategy(APP_STRATEGY.clone())
.expect("goose requires a home dir")
.config_dir();

let config_path = config_dir.join("config.yaml");
let config_path = Paths::config_dir().join("config.yaml");

if config_path.exists() {
let file_name = config_path
Expand Down Expand Up @@ -630,11 +625,7 @@ pub async fn recover_config() -> Result<Json<String>, StatusCode> {
)
)]
pub async fn validate_config() -> Result<Json<String>, StatusCode> {
let config_dir = choose_app_strategy(APP_STRATEGY.clone())
.expect("goose requires a home dir")
.config_dir();

let config_path = config_dir.join("config.yaml");
let config_path = Paths::config_dir().join("config.yaml");

if !config_path.exists() {
return Ok(Json("Config file does not exist".to_string()));
Expand Down
21 changes: 3 additions & 18 deletions crates/goose/src/config/base.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use crate::config::paths::Paths;
use fs2::FileExt;
use keyring::Entry;
use once_cell::sync::{Lazy, OnceCell};
use once_cell::sync::OnceCell;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
Expand All @@ -11,12 +11,6 @@ use std::io::Write;
use std::path::{Path, PathBuf};
use thiserror::Error;

pub static APP_STRATEGY: Lazy<AppStrategyArgs> = Lazy::new(|| AppStrategyArgs {
top_level_domain: "Block".to_string(),
author: "Block".to_string(),
app_name: "goose".to_string(),
});

const KEYRING_SERVICE: &str = "goose";
const KEYRING_USERNAME: &str = "secrets";

Expand Down Expand Up @@ -116,18 +110,9 @@ enum SecretStorage {
// Global instance
static GLOBAL_CONFIG: OnceCell<Config> = OnceCell::new();

pub fn get_config_dir() -> PathBuf {
choose_app_strategy(APP_STRATEGY.clone())
.expect("goose requires a home dir")
.config_dir()
}

impl Default for Config {
fn default() -> Self {
// choose_app_strategy().config_dir()
// - macOS/Linux: ~/.config/goose/
// - Windows: ~\AppData\Roaming\Block\goose\config\
let config_dir = get_config_dir();
let config_dir = Paths::config_dir();

std::fs::create_dir_all(&config_dir).expect("Failed to create config directory");

Expand Down
9 changes: 3 additions & 6 deletions crates/goose/src/config/custom_providers.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use crate::config::{Config, APP_STRATEGY};
use crate::config::paths::Paths;
use crate::config::Config;
use crate::model::ModelConfig;
use crate::providers::anthropic::AnthropicProvider;
use crate::providers::base::ModelInfo;
use crate::providers::ollama::OllamaProvider;
use crate::providers::openai::OpenAiProvider;
use anyhow::Result;
use etcetera::{choose_app_strategy, AppStrategy};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;

pub fn custom_providers_dir() -> std::path::PathBuf {
choose_app_strategy(APP_STRATEGY.clone())
.expect("goose requires a home dir")
.config_dir()
.join("custom_providers")
Paths::config_dir().join("custom_providers")
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
3 changes: 2 additions & 1 deletion crates/goose/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ pub mod base;
pub mod custom_providers;
mod experiments;
pub mod extensions;
pub mod paths;
pub mod permission;
pub mod signup_openrouter;
pub mod signup_tetrate;

pub use crate::agents::ExtensionConfig;
pub use base::{get_config_dir, Config, ConfigError, APP_STRATEGY};
pub use base::{Config, ConfigError};
pub use custom_providers::CustomProviderConfig;
pub use experiments::ExperimentManager;
pub use extensions::{
Expand Down
60 changes: 60 additions & 0 deletions crates/goose/src/config/paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use std::path::PathBuf;

pub struct Paths;

impl Paths {
fn get_dir(dir_type: DirType) -> PathBuf {
if let Ok(test_root) = std::env::var("GOOSE_PATH_ROOT") {
let base = PathBuf::from(test_root);
match dir_type {
DirType::Config => base.join("config"),
DirType::Data => base.join("data"),
DirType::State => base.join("state"),
}
} else {
let strategy = choose_app_strategy(AppStrategyArgs {
top_level_domain: "Block".to_string(),
author: "Block".to_string(),
app_name: "goose".to_string(),
})
.expect("goose requires a home dir");

match dir_type {
DirType::Config => strategy.config_dir(),
DirType::Data => strategy.data_dir(),
DirType::State => strategy.state_dir().unwrap_or(strategy.data_dir()),
}
}
}

pub fn config_dir() -> PathBuf {
Self::get_dir(DirType::Config)
}

pub fn data_dir() -> PathBuf {
Self::get_dir(DirType::Data)
}

pub fn state_dir() -> PathBuf {
Self::get_dir(DirType::State)
}

pub fn in_state_dir(subpath: &str) -> PathBuf {
Self::state_dir().join(subpath)
}

pub fn in_config_dir(subpath: &str) -> PathBuf {
Self::config_dir().join(subpath)
}

pub fn in_data_dir(subpath: &str) -> PathBuf {
Self::data_dir().join(subpath)
}
}

enum DirType {
Config,
Data,
State,
}
Loading
Loading