mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 14:44:21 +01:00
feat: auto-initialize goose config.yaml (#2102)
This commit is contained in:
@@ -12,6 +12,7 @@ use utoipa::OpenApi;
|
|||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(
|
#[openapi(
|
||||||
paths(
|
paths(
|
||||||
|
super::routes::config_management::init_config,
|
||||||
super::routes::config_management::upsert_config,
|
super::routes::config_management::upsert_config,
|
||||||
super::routes::config_management::remove_config,
|
super::routes::config_management::remove_config,
|
||||||
super::routes::config_management::read_config,
|
super::routes::config_management::read_config,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use goose::providers::providers as get_providers;
|
|||||||
use http::{HeaderMap, StatusCode};
|
use http::{HeaderMap, StatusCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use serde_yaml;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
@@ -320,6 +321,75 @@ pub async fn providers(
|
|||||||
Ok(Json(providers_response))
|
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 {
|
pub fn routes(state: AppState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/config", get(read_all_config))
|
.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", post(add_extension))
|
||||||
.route("/config/extensions/:name", delete(remove_extension))
|
.route("/config/extensions/:name", delete(remove_extension))
|
||||||
.route("/config/providers", get(providers))
|
.route("/config/providers", get(providers))
|
||||||
|
.route("/config/init", post(init_config))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save current values to the config file
|
// 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
|
// Convert to YAML for storage
|
||||||
let yaml_value = serde_yaml::to_string(&values)?;
|
let yaml_value = serde_yaml::to_string(&values)?;
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/config/init": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"operationId": "init_config",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Config initialization check completed",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/config/providers": {
|
"/config/providers": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { useChat } from './hooks/useChat';
|
|||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { useConfig, MalformedConfigError } from './components/ConfigContext';
|
import { useConfig, MalformedConfigError } from './components/ConfigContext';
|
||||||
import { addExtensionFromDeepLink as addExtensionFromDeepLinkV2 } from './components/settings_v2/extensions';
|
import { addExtensionFromDeepLink as addExtensionFromDeepLinkV2 } from './components/settings_v2/extensions';
|
||||||
|
import { initConfig } from './api/sdk.gen';
|
||||||
|
|
||||||
// Views and their options
|
// Views and their options
|
||||||
export type View =
|
export type View =
|
||||||
@@ -91,6 +92,9 @@ export default function App() {
|
|||||||
|
|
||||||
const initializeApp = async () => {
|
const initializeApp = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Initialize config first
|
||||||
|
await initConfig();
|
||||||
|
|
||||||
const config = window.electron.getConfig();
|
const config = window.electron.getConfig();
|
||||||
|
|
||||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
|
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
|
||||||
import type { GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen';
|
import type { GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, InitConfigData, InitConfigResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen';
|
||||||
import { client as _heyApiClient } from './client.gen';
|
import { client as _heyApiClient } from './client.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||||
@@ -57,6 +57,13 @@ export const removeExtension = <ThrowOnError extends boolean = false>(options: O
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initConfig = <ThrowOnError extends boolean = false>(options?: Options<InitConfigData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? _heyApiClient).post<InitConfigResponse, unknown, ThrowOnError>({
|
||||||
|
url: '/config/init',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const providers = <ThrowOnError extends boolean = false>(options?: Options<ProvidersData, ThrowOnError>) => {
|
export const providers = <ThrowOnError extends boolean = false>(options?: Options<ProvidersData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ProvidersResponse2, unknown, ThrowOnError>({
|
return (options?.client ?? _heyApiClient).get<ProvidersResponse2, unknown, ThrowOnError>({
|
||||||
url: '/config/providers',
|
url: '/config/providers',
|
||||||
|
|||||||
@@ -360,6 +360,29 @@ export type RemoveExtensionResponses = {
|
|||||||
|
|
||||||
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
||||||
|
|
||||||
|
export type InitConfigData = {
|
||||||
|
body?: never;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: '/config/init';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InitConfigErrors = {
|
||||||
|
/**
|
||||||
|
* Internal server error
|
||||||
|
*/
|
||||||
|
500: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InitConfigResponses = {
|
||||||
|
/**
|
||||||
|
* Config initialization check completed
|
||||||
|
*/
|
||||||
|
200: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InitConfigResponse = InitConfigResponses[keyof InitConfigResponses];
|
||||||
|
|
||||||
export type ProvidersData = {
|
export type ProvidersData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
Reference in New Issue
Block a user