feat: auto-initialize goose config.yaml (#2102)

This commit is contained in:
Alex Hancock
2025-04-09 11:31:19 -04:00
committed by GitHub
parent 5cc608dd03
commit a3deeef05a
7 changed files with 131 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(
paths(
super::routes::config_management::init_config,
super::routes::config_management::upsert_config,
super::routes::config_management::remove_config,
super::routes::config_management::read_config,

View File

@@ -14,6 +14,7 @@ use goose::providers::providers as get_providers;
use http::{HeaderMap, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_yaml;
use std::collections::HashMap;
use utoipa::ToSchema;
@@ -320,6 +321,75 @@ pub async fn providers(
Ok(Json(providers_response))
}
#[utoipa::path(
post,
path = "/config/init",
responses(
(status = 200, description = "Config initialization check completed", body = String),
(status = 500, description = "Internal server error")
)
)]
pub async fn init_config(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<String>, StatusCode> {
verify_secret_key(&headers, &state)?;
let config = Config::global();
// 200 if config already exists
if config.exists() {
return Ok(Json("Config already exists".to_string()));
}
// Find the workspace root (where the top-level Cargo.toml with [workspace] is)
let workspace_root = match std::env::current_exe() {
Ok(mut exe_path) => {
// Start from the executable's directory and traverse up
while let Some(parent) = exe_path.parent() {
let cargo_toml = parent.join("Cargo.toml");
if cargo_toml.exists() {
// Read the Cargo.toml file
if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
// Check if it contains [workspace]
if content.contains("[workspace]") {
exe_path = parent.to_path_buf();
break;
}
}
}
exe_path = parent.to_path_buf();
}
exe_path
}
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
};
// Check if init-config.yaml exists at workspace root
let init_config_path = workspace_root.join("init-config.yaml");
if !init_config_path.exists() {
return Ok(Json(
"No init-config.yaml found, using default configuration".to_string(),
));
}
// Read init-config.yaml and validate
let init_content = match std::fs::read_to_string(&init_config_path) {
Ok(content) => content,
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
};
let init_values: HashMap<String, Value> = match serde_yaml::from_str(&init_content) {
Ok(values) => values,
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
};
// Save init-config.yaml to ~/.config/goose/config.yaml
match config.save_values(init_values) {
Ok(_) => Ok(Json("Config initialized successfully".to_string())),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}
pub fn routes(state: AppState) -> Router {
Router::new()
.route("/config", get(read_all_config))
@@ -330,5 +400,6 @@ pub fn routes(state: AppState) -> Router {
.route("/config/extensions", post(add_extension))
.route("/config/extensions/:name", delete(remove_extension))
.route("/config/providers", get(providers))
.route("/config/init", post(init_config))
.with_state(state)
}

View File

@@ -210,7 +210,7 @@ impl Config {
}
// Save current values to the config file
fn save_values(&self, values: HashMap<String, Value>) -> Result<(), ConfigError> {
pub fn save_values(&self, values: HashMap<String, Value>) -> Result<(), ConfigError> {
// Convert to YAML for storage
let yaml_value = serde_yaml::to_string(&values)?;