/** * Configuration Path Utilities * * Centralized utilities for resolving Wave configuration file paths. * Supports both regular settings.json and local settings.local.json with proper priority. * * Priority system: * - User configs: ~/.wave/settings.json * - Local configs: {workdir}/.wave/settings.local.json > {workdir}/.wave/settings.json * - Project configs override user configs (existing behavior) */ import { join, dirname } from "path"; import { homedir } from "os"; import { existsSync } from "fs"; import { fileURLToPath } from "url"; import { findUpSync } from "find-up"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Resolve the package root directory by finding the nearest package.json. * Works correctly even when bundled (e.g. esbuild into a VS Code extension), * as long as vendor/ and builtin/ remain alongside package.json on disk. */ let _packageRoot: string | undefined; export function getPackageRoot(): string { if (_packageRoot) return _packageRoot; const pkgPath = findUpSync("package.json", { cwd: __dirname }); if (pkgPath) { _packageRoot = dirname(pkgPath); return _packageRoot; } // Fallback: relative to this file (works during development) _packageRoot = join(__dirname, "..", ".."); return _packageRoot; } /** * Get the builtin skills directory path */ export function getBuiltinSkillsDir(): string { return join(getPackageRoot(), "builtin", "skills"); } /** * Get the builtin subagents directory path */ export function getBuiltinSubagentsDir(): string { return join(getPackageRoot(), "builtin", "subagents"); } /** * Get the user-specific configuration file path (legacy function) * @deprecated Use getUserConfigPaths() for better priority support */ export function getUserConfigPath(): string { return join(homedir(), ".wave", "settings.json"); } /** * Get the project-specific configuration file path (legacy function) * @deprecated Use getProjectConfigPaths() for better priority support */ export function getProjectConfigPath(workdir: string): string { return join(workdir, ".wave", "settings.json"); } /** * Get the user-specific configuration file paths in priority order * Returns array with .json only */ export function getUserConfigPaths(): string[] { const baseDir = join(homedir(), ".wave"); return [join(baseDir, "settings.json")]; } /** * Get the plugins directory path */ export function getPluginsDir(): string { return join(homedir(), ".wave", "plugins"); } /** * Get the local configuration file paths in priority order * Returns array with .local.json first, then .json */ export function getProjectConfigPaths(workdir: string): string[] { const baseDir = join(workdir, ".wave"); return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")]; } /** * Get all configuration file paths (user and project) in priority order * Useful for comprehensive configuration detection */ export function getAllConfigPaths(workdir: string): { userPaths: string[]; projectPaths: string[]; builtinPaths: string[]; allPaths: string[]; } { const userPaths = getUserConfigPaths(); const projectPaths = getProjectConfigPaths(workdir); const builtinPaths = [join(getBuiltinSkillsDir(), "settings", "SKILL.md")]; return { userPaths, projectPaths, builtinPaths, allPaths: [...userPaths, ...projectPaths, ...builtinPaths], }; } /** * Get existing configuration file paths * Returns only the paths that actually exist on the filesystem */ export function getExistingConfigPaths(workdir: string): { userPaths: string[]; projectPaths: string[]; builtinPaths: string[]; existingPaths: string[]; } { const allPaths = getAllConfigPaths(workdir); const existingUserPaths = allPaths.userPaths.filter(existsSync); const existingProjectPaths = allPaths.projectPaths.filter(existsSync); const existingBuiltinPaths = allPaths.builtinPaths.filter(existsSync); const allExistingPaths = allPaths.allPaths.filter(existsSync); return { userPaths: existingUserPaths, projectPaths: existingProjectPaths, builtinPaths: existingBuiltinPaths, existingPaths: allExistingPaths, }; } /** * Get the first existing configuration file path with the specified priority * @param paths Array of paths in priority order * @returns The first path that exists, or undefined if none exist */ export function getFirstExistingPath(paths: string[]): string | undefined { return paths.find((path) => existsSync(path)); } /** * Get effective configuration paths (the ones that would actually be used) * Returns the highest priority existing path for each category */ export function getEffectiveConfigPaths(workdir: string): { userPath?: string; projectPath?: string; effectivePath?: string; // The path that takes final precedence } { const userPaths = getUserConfigPaths(); const projectPaths = getProjectConfigPaths(workdir); const userPath = getFirstExistingPath(userPaths); const projectPath = getFirstExistingPath(projectPaths); // Project path takes precedence over user path if both exist const effectivePath = projectPath || userPath; return { userPath, projectPath, effectivePath, }; } /** * Check if any configuration files exist */ export function hasAnyConfig(workdir: string): boolean { const { existingPaths } = getExistingConfigPaths(workdir); return existingPaths.length > 0; } /** * Get configuration information for debugging and monitoring */ export function getConfigurationInfo(workdir: string): { hasUser: boolean; hasProject: boolean; paths: string[]; userPaths: string[]; projectPaths: string[]; existingPaths: string[]; effectivePaths: { userPath?: string; projectPath?: string; effectivePath?: string; }; } { const allPaths = getAllConfigPaths(workdir); const existingPaths = getExistingConfigPaths(workdir); const effectivePaths = getEffectiveConfigPaths(workdir); return { hasUser: existingPaths.userPaths.length > 0, hasProject: existingPaths.projectPaths.length > 0, paths: allPaths.allPaths, userPaths: allPaths.userPaths, projectPaths: allPaths.projectPaths, existingPaths: existingPaths.existingPaths, effectivePaths, }; }