UNPKG

@civic/hub-bridge

Version:

Stdio <-> HTTP/SSE MCP bridge with Civic auth handling

169 lines 5.06 kB
/** * ConfigManager.ts * * Unified configuration management for hub-bridge. * Handles OAuth tokens, profile selection, and other persistent configuration. */ import { promises as fs } from 'node:fs'; import { dirname } from 'node:path'; import { homedir } from 'node:os'; import { logger } from '../utils/logger.js'; import { CONFIG_FILE } from "../config/index.js"; /** * Centralized configuration manager for hub-bridge * Also implements TokenPersistence interface for OAuth token storage */ export class ConfigManager { static instance = null; configPath; config = null; constructor(configPath) { this.configPath = this.expandTildePath(configPath); this.loadConfig().then((config) => { this.config = config; }); } /** * Get singleton instance of ConfigManager */ static getInstance(configPath = CONFIG_FILE) { if (!ConfigManager.instance) { if (!configPath) { throw new Error('ConfigManager not initialized. Provide configPath on first call.'); } ConfigManager.instance = new ConfigManager(configPath); } return ConfigManager.instance; } /** * Reset singleton instance (primarily for testing) */ static resetInstance() { ConfigManager.instance = null; } /** * Expands tilde (~) in file paths to the user's home directory */ expandTildePath(filePath) { if (filePath.startsWith('~/')) { return filePath.replace('~', homedir()); } return filePath; } /** * Load configuration from disk */ async loadConfig() { if (this.config) { return this.config; } try { const data = await fs.readFile(this.configPath, 'utf8'); this.config = JSON.parse(data); return this.config; } catch { // Config doesn't exist or can't be read - return empty config logger.debug('Config file not found or unreadable, using empty config'); this.config = {}; return this.config; } } /** * Save configuration to disk */ async saveConfig() { if (!this.config) { throw new Error('No configuration loaded'); } try { // Ensure directory exists await fs.mkdir(dirname(this.configPath), { recursive: true }); // Write config atomically const tempPath = `${this.configPath}.tmp`; await fs.writeFile(tempPath, JSON.stringify(this.config, null, 2), 'utf8'); await fs.rename(tempPath, this.configPath); logger.info('Configuration saved successfully'); } catch (error) { logger.error('Failed to save configuration:', error); throw error; } } /** * Get OAuth tokens (alias for TokenPersistence interface) */ async getTokens() { const config = await this.loadConfig(); return config.tokens; } /** * Load OAuth tokens (TokenPersistence interface method) */ async loadTokens() { return this.getTokens(); } /** * Save OAuth tokens */ async saveTokens(tokens) { const config = await this.loadConfig(); config.tokens = tokens; await this.saveConfig(); logger.info('OAuth tokens saved'); } /** * Clear OAuth tokens */ async clearTokens() { const config = await this.loadConfig(); delete config.tokens; await this.saveConfig(); logger.info('OAuth tokens cleared'); } /** * Get current profile alias synchronously from cached config * Returns undefined if config not yet loaded */ getCurrentProfileAliasSync() { return this.config?.currentProfileAlias; } /** * Get current profile alias */ async getCurrentProfileAlias() { const config = await this.loadConfig(); return config.currentProfileAlias; } /** * Set current profile by alias */ async setCurrentProfile(alias) { this.config = await this.loadConfig(); this.config.currentProfileAlias = alias; await this.saveConfig(); logger.info(`Profile switched to: ${alias}`); } /** * Clear current profile selection */ async clearCurrentProfile() { this.config = await this.loadConfig(); delete this.config.currentProfileAlias; await this.saveConfig(); logger.info('Profile selection cleared'); } /** * Clear all configuration */ async clearAll() { try { await fs.unlink(this.configPath); this.config = null; logger.info('All configuration cleared'); } catch { // File doesn't exist - ignore error logger.debug('Config file does not exist, nothing to clear'); } } } //# sourceMappingURL=ConfigManager.js.map