feat(asset): implement asset path resolution and streamline file loading

- Introduced a new utility function `FindAsset` in `PlatformPaths` to locate asset files across multiple standard directories, enhancing flexibility in asset management.
- Updated various components to utilize `FindAsset` for loading configuration and prompt files, replacing hardcoded search paths with a more robust solution.
- Improved error handling and logging for asset loading failures, ensuring clearer feedback during runtime.
- Refactored existing code in `gemini_ai_service`, `prompt_builder`, and `platform_paths` to leverage the new asset resolution mechanism, promoting code consistency and maintainability.
This commit is contained in:
scawful
2025-10-10 19:12:16 -04:00
parent bfbba1de13
commit f91aff0b00
5 changed files with 293 additions and 182 deletions

View File

@@ -11,13 +11,13 @@
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h" #include "absl/strings/str_split.h"
#include "absl/strings/strip.h" #include "absl/strings/strip.h"
#include "util/platform_paths.h"
#ifdef YAZE_WITH_JSON #ifdef YAZE_WITH_JSON
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include "httplib.h" #include "httplib.h"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
namespace fs = std::filesystem;
// OpenSSL initialization for HTTPS support // OpenSSL initialization for HTTPS support
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -76,34 +76,29 @@ GeminiAIService::GeminiAIService(const GeminiConfig& config)
std::cerr << "[DEBUG] Building system instruction..." << std::endl; std::cerr << "[DEBUG] Building system instruction..." << std::endl;
} }
// Try to load version-specific system prompt file // Try to load version-specific system prompt file using FindAsset
std::string prompt_file; std::string prompt_file;
if (config_.prompt_version == "v3") { if (config_.prompt_version == "v3") {
prompt_file = "assets/agent/system_prompt_v3.txt"; prompt_file = "agent/system_prompt_v3.txt";
} else if (config_.prompt_version == "v2") { } else if (config_.prompt_version == "v2") {
prompt_file = "assets/agent/system_prompt_v2.txt"; prompt_file = "agent/system_prompt_v2.txt";
} else { } else {
prompt_file = "assets/agent/system_prompt.txt"; prompt_file = "agent/system_prompt.txt";
} }
std::vector<std::string> search_paths = { auto prompt_path = util::PlatformPaths::FindAsset(prompt_file);
prompt_file,
"../" + prompt_file,
"../../" + prompt_file
};
bool loaded = false; bool loaded = false;
for (const auto& path : search_paths) {
std::ifstream file(path); if (prompt_path.ok()) {
std::ifstream file(prompt_path->string());
if (file.good()) { if (file.good()) {
std::stringstream buffer; std::stringstream buffer;
buffer << file.rdbuf(); buffer << file.rdbuf();
config_.system_instruction = buffer.str(); config_.system_instruction = buffer.str();
if (config_.verbose) { if (config_.verbose) {
std::cerr << "[DEBUG] Loaded prompt: " << path << std::endl; std::cerr << "[DEBUG] Loaded prompt: " << prompt_path->string() << std::endl;
} }
loaded = true; loaded = true;
break;
} }
} }
@@ -148,38 +143,22 @@ std::string GeminiAIService::BuildFunctionCallSchemas() {
return schemas; return schemas;
} }
// Fallback: Search for function_schemas.json // Fallback: Search for function_schemas.json using FindAsset
const std::vector<std::string> search_paths = { auto schema_path_or = util::PlatformPaths::FindAsset("agent/function_schemas.json");
"assets/agent/function_schemas.json",
"../assets/agent/function_schemas.json",
"../../assets/agent/function_schemas.json",
};
fs::path schema_path; if (!schema_path_or.ok()) {
bool found = false; if (config_.verbose) {
std::cerr << "⚠️ Function schemas file not found: "
for (const auto& candidate : search_paths) { << schema_path_or.status().message() << std::endl;
fs::path resolved = fs::absolute(candidate);
if (fs::exists(resolved)) {
schema_path = resolved;
found = true;
break;
}
}
if (!found) {
std::cerr << "⚠️ Function schemas file not found. Tried paths:" << std::endl;
for (const auto& path : search_paths) {
std::cerr << " - " << fs::absolute(path).string() << std::endl;
} }
return "[]"; // Return empty array as fallback return "[]"; // Return empty array as fallback
} }
// Load and parse the JSON file // Load and parse the JSON file
std::ifstream file(schema_path); std::ifstream file(schema_path_or->string());
if (!file.is_open()) { if (!file.is_open()) {
std::cerr << "⚠️ Failed to open function schemas file: " std::cerr << "⚠️ Failed to open function schemas file: "
<< schema_path.string() << std::endl; << schema_path_or->string() << std::endl;
return "[]"; return "[]";
} }

View File

@@ -11,6 +11,7 @@
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h" #include "absl/strings/str_join.h"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "util/platform_paths.h"
#include "yaml-cpp/yaml.h" #include "yaml-cpp/yaml.h"
namespace yaze { namespace yaze {
@@ -18,8 +19,6 @@ namespace cli {
namespace { namespace {
namespace fs = std::filesystem;
bool IsYamlBool(const std::string& value) { bool IsYamlBool(const std::string& value) {
const std::string lower = absl::AsciiStrToLower(value); const std::string lower = absl::AsciiStrToLower(value);
return lower == "true" || lower == "false" || lower == "yes" || return lower == "true" || lower == "false" || lower == "yes" ||
@@ -64,44 +63,6 @@ nlohmann::json YamlToJson(const YAML::Node& node) {
} }
} }
std::vector<fs::path> BuildCatalogueSearchPaths(const std::string& explicit_path) {
std::vector<fs::path> paths;
if (!explicit_path.empty()) {
paths.emplace_back(explicit_path);
}
if (const char* env_path = std::getenv("YAZE_AGENT_CATALOGUE")) {
if (*env_path != '\0') {
paths.emplace_back(env_path);
}
}
// Try to get executable directory for better path resolution
fs::path exe_dir;
try {
exe_dir = fs::current_path();
} catch (...) {
exe_dir = ".";
}
const std::vector<std::string> defaults = {
"assets/agent/prompt_catalogue.yaml",
"../assets/agent/prompt_catalogue.yaml",
"../../assets/agent/prompt_catalogue.yaml",
"../../../assets/agent/prompt_catalogue.yaml", // From build/bin/
"../../../../assets/agent/prompt_catalogue.yaml", // From build/bin/yaze.app/Contents/MacOS/
"../Resources/assets/agent/prompt_catalogue.yaml", // macOS app bundle
"assets/z3ed/prompt_catalogue.yaml",
"../assets/z3ed/prompt_catalogue.yaml",
};
for (const auto& candidate : defaults) {
paths.emplace_back(candidate);
}
return paths;
}
} // namespace } // namespace
PromptBuilder::PromptBuilder() = default; PromptBuilder::PromptBuilder() = default;
@@ -116,35 +77,35 @@ void PromptBuilder::ClearCatalogData() {
absl::StatusOr<std::string> PromptBuilder::ResolveCataloguePath( absl::StatusOr<std::string> PromptBuilder::ResolveCataloguePath(
const std::string& yaml_path) const { const std::string& yaml_path) const {
const auto search_paths = BuildCatalogueSearchPaths(yaml_path); // If an explicit path is provided, check it first
for (const auto& candidate : search_paths) { if (!yaml_path.empty()) {
fs::path resolved = candidate; std::error_code ec;
if (resolved.is_relative()) { if (std::filesystem::exists(yaml_path, ec) && !ec) {
try { return yaml_path;
resolved = fs::absolute(resolved);
} catch (const std::exception& e) {
// If we can't resolve the absolute path (e.g., cwd doesn't exist),
// just try the relative path as-is
continue;
}
}
try {
if (fs::exists(resolved)) {
return resolved.string();
}
} catch (const std::exception& e) {
// If checking existence fails, just continue to next path
continue;
} }
} }
return absl::NotFoundError( // Check environment variable override
absl::StrCat("Prompt catalogue not found. Checked paths: ", if (const char* env_path = std::getenv("YAZE_AGENT_CATALOGUE")) {
absl::StrJoin(search_paths, ", ", if (*env_path != '\0') {
[](std::string* out, const fs::path& path) { std::error_code ec;
absl::StrAppend(out, path.string()); if (std::filesystem::exists(env_path, ec) && !ec) {
}))); return std::string(env_path);
}
}
}
// Use PlatformPaths to find the asset in standard locations
// Try the requested path (default is prompt_catalogue.yaml)
std::string relative_path = yaml_path.empty() ?
"agent/prompt_catalogue.yaml" : yaml_path;
auto result = util::PlatformPaths::FindAsset(relative_path);
if (result.ok()) {
return result->string();
}
return result.status();
} }
absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) { absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) {
@@ -548,15 +509,10 @@ std::string PromptBuilder::BuildFewShotExamplesSection() const {
} }
std::string PromptBuilder::BuildConstraintsSection() const { std::string PromptBuilder::BuildConstraintsSection() const {
// Try to load from file first // Try to load from file first using FindAsset
const std::vector<std::string> search_paths = { auto file_path = util::PlatformPaths::FindAsset("agent/tool_calling_instructions.txt");
"assets/agent/tool_calling_instructions.txt", if (file_path.ok()) {
"../assets/agent/tool_calling_instructions.txt", std::ifstream file(file_path->string());
"../../assets/agent/tool_calling_instructions.txt",
};
for (const auto& path : search_paths) {
std::ifstream file(path);
if (file.is_open()) { if (file.is_open()) {
std::string content((std::istreambuf_iterator<char>(file)), std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
@@ -743,15 +699,10 @@ std::string PromptBuilder::BuildContextSection(const RomContext& context) {
} }
std::string PromptBuilder::BuildSystemInstruction() { std::string PromptBuilder::BuildSystemInstruction() {
// Try to load from file first // Try to load from file first using FindAsset
const std::vector<std::string> search_paths = { auto file_path = util::PlatformPaths::FindAsset("agent/system_prompt.txt");
"assets/agent/system_prompt.txt", if (file_path.ok()) {
"../assets/agent/system_prompt.txt", std::ifstream file(file_path->string());
"../../assets/agent/system_prompt.txt",
};
for (const auto& path : search_paths) {
std::ifstream file(path);
if (file.is_open()) { if (file.is_open()) {
std::string content((std::istreambuf_iterator<char>(file)), std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
@@ -876,3 +827,4 @@ std::vector<FewShotExample> PromptBuilder::GetExamplesForCategory(
} // namespace cli } // namespace cli
} // namespace yaze } // namespace yaze

View File

@@ -89,9 +89,11 @@ add_executable(
cli/service/testing/test_suite_writer.cc cli/service/testing/test_suite_writer.cc
) )
# ============================================================================ target_compile_definitions(z3ed PRIVATE YAZE_ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets")
# ============================================================================
# AI Agent Support (Consolidated via Z3ED_AI flag) # AI Agent Support (Consolidated via Z3ED_AI flag)
# ============================================================================ # ============================================================================
if(Z3ED_AI OR YAZE_WITH_JSON) if(Z3ED_AI OR YAZE_WITH_JSON)
target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON) target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON)
message(STATUS "✓ z3ed AI agent enabled (Ollama + Gemini support)") message(STATUS "✓ z3ed AI agent enabled (Ollama + Gemini support)")
@@ -100,9 +102,9 @@ if(Z3ED_AI OR YAZE_WITH_JSON)
target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json)
endif() endif()
# ============================================================================ # ============================================================================
# SSL/HTTPS Support (Optional - Required for Gemini API) # SSL/HTTPS Support (Optional - Required for Gemini API)
# ============================================================================ # ============================================================================
# SSL is only enabled when AI features are active # SSL is only enabled when AI features are active
# Ollama (localhost) works without SSL, Gemini (HTTPS) requires it # Ollama (localhost) works without SSL, Gemini (HTTPS) requires it
if((Z3ED_AI OR YAZE_WITH_JSON) AND (YAZE_WITH_GRPC OR Z3ED_AI)) if((Z3ED_AI OR YAZE_WITH_JSON) AND (YAZE_WITH_GRPC OR Z3ED_AI))
@@ -158,9 +160,9 @@ else()
) )
endif() endif()
# ============================================================================ # ============================================================================
# Optional gRPC Support for CLI Agent Test Command # Optional gRPC Support for CLI Agent Test Command
# ============================================================================ # ============================================================================
if(YAZE_WITH_GRPC) if(YAZE_WITH_GRPC)
message(STATUS "Adding gRPC support to z3ed CLI") message(STATUS "Adding gRPC support to z3ed CLI")
@@ -183,4 +185,4 @@ if(YAZE_WITH_GRPC)
libprotobuf) libprotobuf)
message(STATUS "✓ gRPC CLI automation integrated") message(STATUS "✓ gRPC CLI automation integrated")
endif() endif()

View File

@@ -11,44 +11,63 @@
#else #else
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
#include <climits> // For PATH_MAX
#ifdef __APPLE__
#include <mach-o/dyld.h> // For _NSGetExecutablePath
#endif
#endif #endif
namespace yaze { namespace yaze {
namespace util { namespace util {
std::filesystem::path PlatformPaths::GetHomeDirectory() { std::filesystem::path PlatformPaths::GetHomeDirectory() {
try {
#ifdef _WIN32 #ifdef _WIN32
// Windows: Use USERPROFILE environment variable // Windows: Use USERPROFILE environment variable
const char* userprofile = std::getenv("USERPROFILE"); const char* userprofile = std::getenv("USERPROFILE");
if (userprofile && *userprofile) { if (userprofile && *userprofile) {
return std::filesystem::path(userprofile); return std::filesystem::path(userprofile);
} }
// Fallback to HOMEDRIVE + HOMEPATH // Fallback to HOMEDRIVE + HOMEPATH
const char* homedrive = std::getenv("HOMEDRIVE"); const char* homedrive = std::getenv("HOMEDRIVE");
const char* homepath = std::getenv("HOMEPATH"); const char* homepath = std::getenv("HOMEPATH");
if (homedrive && homepath) { if (homedrive && homepath) {
return std::filesystem::path(std::string(homedrive) + std::string(homepath)); return std::filesystem::path(std::string(homedrive) + std::string(homepath));
} }
// Last resort: use temp directory // Last resort: use temp directory
return std::filesystem::temp_directory_path(); std::error_code ec;
auto temp = std::filesystem::temp_directory_path(ec);
if (!ec) {
return temp;
}
return std::filesystem::path(".");
#else #else
// Unix/macOS: Use HOME environment variable // Unix/macOS: Use HOME environment variable
const char* home = std::getenv("HOME"); const char* home = std::getenv("HOME");
if (home && *home) { if (home && *home) {
return std::filesystem::path(home); return std::filesystem::path(home);
} }
// Fallback: try getpwuid // Fallback: try getpwuid
struct passwd* pw = getpwuid(getuid()); struct passwd* pw = getpwuid(getuid());
if (pw && pw->pw_dir) { if (pw && pw->pw_dir) {
return std::filesystem::path(pw->pw_dir); return std::filesystem::path(pw->pw_dir);
} }
// Last resort: current directory // Last resort: current directory (with error handling)
return std::filesystem::current_path(); std::error_code ec;
auto cwd = std::filesystem::current_path(ec);
if (!ec) {
return cwd;
}
return std::filesystem::path(".");
#endif #endif
} catch (...) {
// If everything fails, return current directory placeholder
return std::filesystem::path(".");
}
} }
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() { absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
@@ -70,23 +89,11 @@ absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
return status; return status;
} }
return app_data; return app_data;
#elif defined(__APPLE__) #else
// Unix/macOS: Use ~/.yaze for simplicity and consistency
// This is simpler than XDG or Application Support for a dev tool
std::filesystem::path home = GetHomeDirectory(); std::filesystem::path home = GetHomeDirectory();
std::filesystem::path app_data = home / "Library" / "Application Support" / "yaze"; std::filesystem::path app_data = home / ".yaze";
auto status = EnsureDirectoryExists(app_data);
if (!status.ok()) {
return status;
}
return app_data;
#else // Linux and other Unix-like systems
const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
std::filesystem::path config_dir;
if (xdg_config_home && *xdg_config_home) {
config_dir = std::filesystem::path(xdg_config_home);
} else {
config_dir = GetHomeDirectory() / ".config";
}
std::filesystem::path app_data = config_dir / "yaze";
auto status = EnsureDirectoryExists(app_data); auto status = EnsureDirectoryExists(app_data);
if (!status.ok()) { if (!status.ok()) {
return status; return status;
@@ -174,5 +181,163 @@ std::string PlatformPaths::ToNativePath(const std::filesystem::path& path) {
return path.string(); return path.string();
} }
absl::StatusOr<std::filesystem::path> PlatformPaths::FindAsset(
const std::string& relative_path) {
std::vector<std::filesystem::path> search_paths;
try {
// 1. Check compile-time YAZE_ASSETS_PATH if defined
#ifdef YAZE_ASSETS_PATH
try {
search_paths.push_back(std::filesystem::path(YAZE_ASSETS_PATH) / relative_path);
} catch (...) {
// Skip if path construction fails
}
#endif
// 2. Current working directory + assets/ (cached to avoid repeated calls)
static std::filesystem::path cached_cwd;
static bool cwd_cached = false;
if (!cwd_cached) {
std::error_code ec;
cached_cwd = std::filesystem::current_path(ec);
cwd_cached = true; // Only try once to avoid repeated slow calls
}
if (!cached_cwd.empty()) {
try {
search_paths.push_back(cached_cwd / "assets" / relative_path);
} catch (...) {
// Skip if path construction fails
}
}
// 3. Executable directory + assets/ (cached to avoid repeated OS calls)
static std::filesystem::path cached_exe_dir;
static bool exe_dir_cached = false;
if (!exe_dir_cached) {
try {
#ifdef __APPLE__
char exe_path[PATH_MAX];
uint32_t size = sizeof(exe_path);
if (_NSGetExecutablePath(exe_path, &size) == 0) {
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
}
#elif defined(__linux__)
char exe_path[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (len != -1) {
exe_path[len] = '\0';
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
}
#elif defined(_WIN32)
wchar_t exe_path[MAX_PATH];
if (GetModuleFileNameW(NULL, exe_path, MAX_PATH) != 0) {
cached_exe_dir = std::filesystem::path(exe_path).parent_path();
}
#endif
} catch (...) {
// Skip if exe path detection fails
}
exe_dir_cached = true; // Only try once
}
if (!cached_exe_dir.empty()) {
try {
search_paths.push_back(cached_exe_dir / "assets" / relative_path);
// Also check parent (for build/bin/yaze case)
search_paths.push_back(cached_exe_dir.parent_path() / "assets" / relative_path);
} catch (...) {
// Skip if path construction fails
}
}
// 4. Parent directories (for running from build subdirectories)
if (!cached_cwd.empty()) {
try {
auto parent = cached_cwd.parent_path();
if (!parent.empty() && parent != cached_cwd) {
search_paths.push_back(parent / "assets" / relative_path);
auto grandparent = parent.parent_path();
if (!grandparent.empty() && grandparent != parent) {
search_paths.push_back(grandparent / "assets" / relative_path);
}
}
} catch (...) {
// Skip if path operations fail
}
}
// 5. User directory ~/.yaze/assets/ (cached home directory)
static std::filesystem::path cached_home;
static bool home_cached = false;
if (!home_cached) {
try {
cached_home = GetHomeDirectory();
} catch (...) {
// Skip if home lookup fails
}
home_cached = true; // Only try once
}
if (!cached_home.empty() && cached_home != ".") {
try {
search_paths.push_back(cached_home / ".yaze" / "assets" / relative_path);
} catch (...) {
// Skip if path construction fails
}
}
// 6. System-wide installation (Unix only)
#ifndef _WIN32
try {
search_paths.push_back(std::filesystem::path("/usr/local/share/yaze/assets") / relative_path);
search_paths.push_back(std::filesystem::path("/usr/share/yaze/assets") / relative_path);
} catch (...) {
// Skip if path construction fails
}
#endif
// Search all paths and return the first one that exists
// Limit search to prevent infinite loops on weird filesystems
const size_t max_paths_to_check = 20;
size_t checked = 0;
for (const auto& candidate : search_paths) {
if (++checked > max_paths_to_check) {
break; // Safety limit
}
try {
// Use std::filesystem::exists with error code to avoid exceptions
std::error_code exists_ec;
if (std::filesystem::exists(candidate, exists_ec) && !exists_ec) {
// Double-check it's a regular file or directory, not a broken symlink
auto status = std::filesystem::status(candidate, exists_ec);
if (!exists_ec && status.type() != std::filesystem::file_type::not_found) {
return candidate;
}
}
} catch (...) {
// Skip this candidate if checking fails
continue;
}
}
// If not found, return a simple error
return absl::NotFoundError(
absl::StrCat("Asset not found: ", relative_path));
} catch (const std::exception& e) {
return absl::InternalError(
absl::StrCat("Exception while searching for asset: ", e.what()));
} catch (...) {
return absl::InternalError("Unknown exception while searching for asset");
}
}
} // namespace util } // namespace util
} // namespace yaze } // namespace yaze

View File

@@ -35,8 +35,7 @@ class PlatformPaths {
* and cache files. The directory is created if it doesn't exist. * and cache files. The directory is created if it doesn't exist.
* *
* - Windows: `%APPDATA%\yaze` (e.g., C:\Users\user\AppData\Roaming\yaze) * - Windows: `%APPDATA%\yaze` (e.g., C:\Users\user\AppData\Roaming\yaze)
* - macOS: `~/Library/Application Support/yaze` * - macOS/Unix: `~/.yaze`
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
* *
* @return StatusOr with path to the application data directory. * @return StatusOr with path to the application data directory.
*/ */
@@ -46,13 +45,10 @@ class PlatformPaths {
* @brief Get the user-specific configuration directory for YAZE. * @brief Get the user-specific configuration directory for YAZE.
* *
* This is the standard location for storing user configuration files. * This is the standard location for storing user configuration files.
* It often maps to the same location as GetAppDataDirectory but provides
* a more semantically correct API for config files.
* The directory is created if it doesn't exist. * The directory is created if it doesn't exist.
* *
* - Windows: `%APPDATA%\yaze` * - Windows: `%APPDATA%\yaze`
* - macOS: `~/Library/Application Support/yaze` * - macOS/Unix: `~/.yaze`
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
* *
* @return StatusOr with path to the configuration directory. * @return StatusOr with path to the configuration directory.
*/ */
@@ -117,6 +113,23 @@ class PlatformPaths {
* @return Native path string * @return Native path string
*/ */
static std::string ToNativePath(const std::filesystem::path& path); static std::string ToNativePath(const std::filesystem::path& path);
/**
* @brief Find an asset file in multiple standard locations
*
* Searches for an asset file in the following order:
* 1. YAZE_ASSETS_PATH (if defined at compile time) + relative_path
* 2. Current working directory + assets/ + relative_path
* 3. Executable directory + assets/ + relative_path
* 4. Parent directory + assets/ + relative_path
* 5. ~/.yaze/assets/ + relative_path (user-installed assets)
* 6. /usr/local/share/yaze/assets/ + relative_path (system-wide on Unix)
*
* @param relative_path Path relative to assets directory (e.g., "agent/prompt_catalogue.yaml")
* @return StatusOr with absolute path to found asset file
*/
static absl::StatusOr<std::filesystem::path> FindAsset(
const std::string& relative_path);
}; };
} // namespace util } // namespace util