@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
897 lines • 125 kB
JavaScript
/**
* ConfigManager - Centralized configuration management for DollhouseMCP
*
* Features:
* - YAML-based configuration file
* - Default values with user overrides
* - Migration from environment variables
* - Validation and type safety
* - Atomic updates with backup
* - Privacy-first defaults
* - OAuth client ID storage for MCP client integration
*/
import * as path from 'path';
import * as os from 'os';
import * as yaml from 'js-yaml';
import { logger } from '../utils/logger.js';
import { SecureYamlParser } from '../security/secureYamlParser.js';
import { validatePropertyPath, safeSetProperty, createSafeObject, safeHasOwnProperty } from '../utils/securityUtils.js';
import { env } from './env.js';
/** Validate and brand a port number. Returns undefined if invalid. */
export function validatePort(value) {
const num = typeof value === 'string' ? Number(value) : value;
if (typeof num !== 'number' || !Number.isFinite(num))
return undefined;
const port = Math.floor(num);
if (port < 1024 || port > 65535)
return undefined;
return port;
}
export class ConfigManager {
configDir;
configPath;
backupPath;
config = null;
fileOperations;
os;
/**
* Extract console.port from raw YAML config content without full
* ConfigManager initialization. Uses FAILSAFE_SCHEMA for security
* (no code execution). Returns undefined if not found or invalid.
*/
static readPortFromYaml(yamlContent) {
try {
const parsed = yaml.load(yamlContent, { schema: yaml.FAILSAFE_SCHEMA });
const raw = parsed?.console?.port;
if (raw === undefined || raw === null)
return undefined;
const port = Number(raw);
return Number.isFinite(port) ? port : undefined;
}
catch {
return undefined;
}
}
constructor(fileOperations, osModule) {
this.fileOperations = fileOperations;
this.os = osModule;
// Initialize paths - use test directory if in test environment
// NOTE: Using process.env directly here (not cached env.TEST_CONFIG_DIR)
// to allow tests to dynamically override the config directory
if (env.NODE_ENV === 'test' && process.env.TEST_CONFIG_DIR) {
this.configDir = process.env.TEST_CONFIG_DIR;
}
else {
this.configDir = path.join(this.os.homedir(), '.dollhouse');
}
this.configPath = path.join(this.configDir, 'config.yml');
this.backupPath = path.join(this.configDir, 'config.yml.backup');
}
/**
* Get default configuration
*/
getDefaultConfig() {
return {
version: '1.0.0',
user: {
username: null,
email: null,
display_name: null
},
github: {
portfolio: {
repository_url: null,
repository_name: env.GITHUB_REPOSITORY || 'dollhouse-portfolio',
default_branch: 'main',
auto_create: true
},
auth: {
use_oauth: true,
token_source: 'environment'
}
},
sync: {
enabled: false, // Privacy first - off by default
individual: {
require_confirmation: true,
show_diff_before_sync: true,
track_versions: true,
keep_history: 10
},
bulk: {
upload_enabled: false, // Requires explicit enablement
download_enabled: false,
require_preview: true,
respect_local_only: true
},
privacy: {
scan_for_secrets: true,
scan_for_pii: true,
warn_on_sensitive: true,
excluded_patterns: [
'*.secret',
'*-private.*',
'credentials/**',
'personal/**'
]
}
},
collection: {
auto_submit: false, // Never auto-submit
require_review: true,
add_attribution: true
},
autoLoad: {
enabled: true, // Auto-load baseline memories by default
maxTokenBudget: 5000,
maxSingleMemoryTokens: undefined,
suppressLargeMemoryWarnings: false,
memories: [] // Configured via memory files with autoLoad flag
},
elements: {
auto_activate: {},
default_element_dir: path.join(os.homedir(), '.dollhouse', 'portfolio'),
enhanced_index: {
enabled: true,
limits: {
maxTriggersPerElement: 50,
maxTriggerLength: 50,
maxKeywordsToCheck: 100
},
telemetry: {
enabled: false, // Opt-in only
sampleRate: 0.1,
metricsInterval: 60000
},
resources: {
advertise_resources: false, // Default: safe, disabled
variants: {
summary: false, // ~1,254 tokens - Opt-in required
full: false, // ~48,306 tokens - Opt-in required
stats: true // ~50 tokens - Safe by default
}
}
}
},
display: {
persona_indicators: {
enabled: true,
style: 'minimal',
include_emoji: true
},
verbose_logging: false,
show_progress: true
},
wizard: {
completed: false,
dismissed: false
},
license: {
tier: 'agpl'
},
console: {
port: 41715
},
/**
* Retention Policy Configuration (Issue #51)
* IMPORTANT: Disabled by default - nothing is auto-deleted without explicit consent.
* Users must explicitly enable retention enforcement if they want automatic cleanup.
*/
retentionPolicy: {
enabled: false, // DISABLED by default - no auto-deletion
enforcement_mode: 'disabled', // Only manual enforcement allowed
safety: {
require_confirmation: true, // Always require confirmation
dry_run_first: true, // Always preview before deleting
warn_on_expiring: true, // Warn when entries approaching expiration
warning_threshold_days: 7 // Warn 7 days before expiration
},
audit: {
log_deletions: true, // Log all deletions
backup_before_delete: true, // Backup deleted entries
backup_retention_days: 30 // Keep backups for 30 days
},
defaults: {
ttl_days: 30, // Default 30-day TTL (if enabled)
max_entries: 1000 // Default max entries per memory
}
}
};
}
/**
* Initialize configuration
*/
async initialize() {
// Always reload config from disk if it exists, even if we have defaults in memory
// This ensures we pick up any manual edits or saved settings
try {
// Ensure config directory exists with proper permissions (0o700 = owner only)
await this.fileOperations.createDirectory(this.configDir);
// Set permissions on the directory
await this.fileOperations.chmod(this.configDir, 0o700, {
source: 'ConfigManager.initialize'
});
// Load or create config
if (await this.configExists()) {
await this.loadConfig();
}
else {
// Create default config
this.config = this.getDefaultConfig();
// Try to migrate from environment variables
await this.migrateFromEnvironment();
// Save the config
await this.saveConfig();
logger.info('Created new configuration file', {
path: this.configPath
});
}
}
catch (error) {
logger.error('Failed to initialize configuration', {
error: error instanceof Error ? error.message : String(error)
});
// Use defaults in memory
this.config = this.getDefaultConfig();
}
}
/**
* Load configuration from file
*/
async loadConfig() {
try {
const content = await this.fileOperations.readFile(this.configPath, {
source: 'ConfigManager.loadConfig'
});
/**
* IMPORTANT: Parser Selection for Different File Types
*
* We use DIFFERENT parsers for different file types:
*
* 1. js-yaml (used here) - For PURE YAML files:
* - Configuration files (config.yml)
* - Data files without markdown content
* - Any .yml or .yaml file that's just YAML
* Example format:
* ```yaml
* version: 1.0.0
* user:
* username: johndoe
* email: john@example.com
* ```
*
* 2. SecureYamlParser - For MARKDOWN files with YAML frontmatter:
* - Persona files (*.md in personas/)
* - Skill files (*.md in skills/)
* - Template files (*.md in templates/)
* - Any .md file with frontmatter between --- markers
* Example format:
* ```markdown
* ---
* name: Creative Writer
* description: A creative assistant
* ---
* # Instructions
* You are a creative writer...
* ```
*
* The config file is PURE YAML, so we use js-yaml directly with FAILSAFE_SCHEMA
* for security (prevents code execution via YAML tags).
* SECURITY: This is NOT a vulnerability - FAILSAFE_SCHEMA prevents code execution
*/
let loadedData;
try {
// Using yaml with FAILSAFE_SCHEMA is secure - prevents code execution
loadedData = yaml.load(content, {
schema: yaml.FAILSAFE_SCHEMA // Safe schema prevents code execution
});
}
catch (yamlError) {
throw new Error(`Invalid YAML in configuration file: ${yamlError instanceof Error ? yamlError.message : String(yamlError)}`);
}
if (!loadedData || typeof loadedData !== 'object') {
throw new Error('Invalid configuration format');
}
logger.debug('Loaded config from file', {
username: loadedData.user?.username,
email: loadedData.user?.email,
syncEnabled: loadedData.sync?.enabled
});
this.config = this.mergeWithDefaults(loadedData);
// Fix any string booleans that might have been saved incorrectly
this.fixConfigTypes();
logger.debug('Configuration loaded successfully', {
username: this.config.user.username,
syncEnabled: this.config.sync.enabled
});
}
catch (error) {
logger.error('Failed to load configuration', {
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Check if config file exists
*/
async configExists() {
return this.fileOperations.exists(this.configPath);
}
/**
* Get GitHub OAuth client ID
* Environment variable takes precedence over config file
*/
getGitHubClientId() {
// NOTE: DOLLHOUSE_GITHUB_CLIENT_ID is not in centralized env config
// as it's an optional feature flag, so we still use process.env here
const envClientId = process.env.DOLLHOUSE_GITHUB_CLIENT_ID;
if (envClientId) {
return envClientId;
}
// Fall back to config file
return this.config?.github?.auth?.client_id || null;
}
/**
* Set GitHub OAuth client ID in config file
*/
async setGitHubClientId(clientId) {
if (!ConfigManager.validateClientId(clientId)) {
throw new Error(`Invalid GitHub client ID format. Expected format: Ov23li followed by at least 14 alphanumeric characters (e.g., Ov23liABCDEFGHIJKLMN)`);
}
if (!this.config) {
this.config = this.getDefaultConfig();
}
// Ensure github.auth object exists
if (!this.config.github) {
this.config.github = this.getDefaultConfig().github;
}
if (!this.config.github.auth) {
this.config.github.auth = this.getDefaultConfig().github.auth;
}
this.config.github.auth.client_id = clientId;
await this.saveConfig();
}
/**
* Get the current configuration
*/
getConfig() {
if (!this.config) {
throw new Error('Configuration not initialized');
}
return this.config;
}
/**
* Get a specific setting using dot notation
*/
getSetting(path, defaultValue) {
if (!this.config) {
return defaultValue;
}
const keys = path.split('.');
let value = this.config;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
}
else {
return defaultValue;
}
}
return value;
}
/**
* Update a specific setting using dot notation
* SECURITY FIX (PR #895): Added prototype pollution protection
* Previously: Direct property assignment allowed __proto__ injection
* Now: Validates keys against forbidden properties before assignment
*/
async updateSetting(path, value) {
if (!this.config) {
await this.initialize();
}
// SECURITY: Validate path to prevent prototype pollution
validatePropertyPath(path, 'path');
// Runtime validation for known typed settings (#1840)
if (path === 'console.port') {
const validated = validatePort(value);
if (!validated) {
return {
success: false,
message: `Invalid port: ${value}. Must be an integer between 1024 and 65535.`,
};
}
value = validated;
}
const keys = path.split('.');
let current = this.config;
const previousValue = this.getSetting(path);
// Navigate to the parent object using security utilities
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
// SECURITY: Use safe property check and create prototype-less objects
if (!safeHasOwnProperty(current, key)) {
current[key] = createSafeObject();
}
current = current[key];
}
// Set the value using secure property setter
const lastKey = keys[keys.length - 1];
safeSetProperty(current, lastKey, value);
// Save the configuration
await this.saveConfig();
logger.info('Configuration setting updated', {
path,
previousValue,
newValue: value
});
return {
success: true,
message: `Setting '${path}' updated successfully`,
previousValue,
newValue: value
};
}
/**
* Validate GitHub OAuth client ID format
* Client IDs start with "Ov23li" followed by at least 14 alphanumeric characters
*
* @param clientId - The client ID to validate
* @returns true if valid, false otherwise
*
* @example
* ConfigManager.validateClientId("Ov23liABCDEFGHIJKLMN123456") // true
* ConfigManager.validateClientId("invalid") // false
* ConfigManager.validateClientId("Ov23li") // false (too short)
* ConfigManager.validateClientId("Xv23liABCDEFGHIJKLMN") // false (wrong prefix)
*/
static validateClientId(clientId) {
if (typeof clientId !== 'string' || !clientId) {
return false;
}
// GitHub OAuth client IDs follow the pattern: Ov23li[A-Za-z0-9]{14,}
const clientIdPattern = /^Ov23li[A-Za-z0-9]{14,}$/;
return clientIdPattern.test(clientId);
}
/**
* Save configuration to file
*/
async saveConfig() {
if (!this.config) {
throw new Error('No configuration to save');
}
try {
// Create backup of existing config
if (await this.configExists()) {
await this.fileOperations.copyFile(this.configPath, this.backupPath, {
source: 'ConfigManager.saveConfig'
});
}
// Convert to YAML
// Note: We use js-yaml's dump() for pure YAML output (no frontmatter markers)
// This creates a standard YAML file, not a markdown file with frontmatter
const yamlContent = yaml.dump(this.config, {
indent: 2,
lineWidth: 120,
noRefs: true,
sortKeys: false
// Using default schema (not FAILSAFE) for dump to preserve types like booleans
});
// Write atomically with proper permissions (0o600 = owner read/write only)
const tempPath = `${this.configPath}.tmp`;
await this.fileOperations.writeFile(tempPath, yamlContent, {
source: 'ConfigManager.saveConfig'
});
await this.fileOperations.chmod(tempPath, 0o600, {
source: 'ConfigManager.saveConfig'
});
await this.fileOperations.renameFile(tempPath, this.configPath);
logger.debug('Configuration saved successfully');
// Log audit event for configuration update
logger.debug('Configuration update audit', {
event: 'CONFIG_UPDATED',
source: 'ConfigManager.saveConfig',
timestamp: new Date().toISOString()
});
}
catch (error) {
logger.error('Failed to save configuration', {
error: error instanceof Error ? error.message : String(error)
});
// Try to restore backup
if (await this.backupExists()) {
await this.fileOperations.copyFile(this.backupPath, this.configPath, {
source: 'ConfigManager.saveConfig'
});
logger.info('Restored configuration from backup');
}
throw error;
}
}
/**
* Check if backup exists
*/
async backupExists() {
return this.fileOperations.exists(this.backupPath);
}
/**
* Fix incorrect types in config (e.g., string booleans, string "null")
*/
fixConfigTypes() {
if (!this.config)
return;
// Helper to convert string "null" to actual null
const fixNull = (value) => {
if (value === 'null' || value === 'NULL')
return null;
return value;
};
// Helper to convert string booleans to actual booleans
const fixBoolean = (value) => {
if (typeof value === 'string') {
const lower = value.toLowerCase();
if (lower === 'true')
return true;
if (lower === 'false')
return false;
}
return value;
};
// Fix user fields - handle string "null" values
if (this.config.user) {
this.config.user.username = fixNull(this.config.user.username);
this.config.user.email = fixNull(this.config.user.email);
this.config.user.display_name = fixNull(this.config.user.display_name);
}
// Fix sync settings
if (this.config.sync) {
this.config.sync.enabled = fixBoolean(this.config.sync.enabled);
if (this.config.sync.individual) {
this.config.sync.individual.require_confirmation = fixBoolean(this.config.sync.individual.require_confirmation);
this.config.sync.individual.show_diff_before_sync = fixBoolean(this.config.sync.individual.show_diff_before_sync);
this.config.sync.individual.track_versions = fixBoolean(this.config.sync.individual.track_versions);
}
if (this.config.sync.bulk) {
this.config.sync.bulk.upload_enabled = fixBoolean(this.config.sync.bulk.upload_enabled);
this.config.sync.bulk.download_enabled = fixBoolean(this.config.sync.bulk.download_enabled);
this.config.sync.bulk.require_preview = fixBoolean(this.config.sync.bulk.require_preview);
this.config.sync.bulk.respect_local_only = fixBoolean(this.config.sync.bulk.respect_local_only);
}
if (this.config.sync.privacy) {
this.config.sync.privacy.scan_for_secrets = fixBoolean(this.config.sync.privacy.scan_for_secrets);
this.config.sync.privacy.scan_for_pii = fixBoolean(this.config.sync.privacy.scan_for_pii);
this.config.sync.privacy.warn_on_sensitive = fixBoolean(this.config.sync.privacy.warn_on_sensitive);
}
}
// Fix collection settings
if (this.config.collection) {
this.config.collection.auto_submit = fixBoolean(this.config.collection.auto_submit);
this.config.collection.require_review = fixBoolean(this.config.collection.require_review);
this.config.collection.add_attribution = fixBoolean(this.config.collection.add_attribution);
}
// Fix display settings
if (this.config.display) {
if (this.config.display.persona_indicators) {
this.config.display.persona_indicators.enabled = fixBoolean(this.config.display.persona_indicators.enabled);
this.config.display.persona_indicators.include_emoji = fixBoolean(this.config.display.persona_indicators.include_emoji);
}
this.config.display.verbose_logging = fixBoolean(this.config.display.verbose_logging);
this.config.display.show_progress = fixBoolean(this.config.display.show_progress);
}
// Fix github settings
if (this.config.github) {
if (this.config.github.portfolio) {
this.config.github.portfolio.repository_url = fixNull(this.config.github.portfolio.repository_url);
this.config.github.portfolio.auto_create = fixBoolean(this.config.github.portfolio.auto_create);
}
if (this.config.github.auth) {
this.config.github.auth.use_oauth = fixBoolean(this.config.github.auth.use_oauth);
// Fix client_id if it's a string "null"
if (this.config.github.auth.client_id) {
this.config.github.auth.client_id = fixNull(this.config.github.auth.client_id) || undefined;
}
}
}
// Fix wizard settings
if (this.config.wizard) {
this.config.wizard.completed = fixBoolean(this.config.wizard.completed);
this.config.wizard.dismissed = fixBoolean(this.config.wizard.dismissed);
}
// Fix retention policy settings (Issue #51)
if (this.config.retentionPolicy) {
this.config.retentionPolicy.enabled = fixBoolean(this.config.retentionPolicy.enabled);
if (this.config.retentionPolicy.safety) {
this.config.retentionPolicy.safety.require_confirmation = fixBoolean(this.config.retentionPolicy.safety.require_confirmation);
this.config.retentionPolicy.safety.dry_run_first = fixBoolean(this.config.retentionPolicy.safety.dry_run_first);
this.config.retentionPolicy.safety.warn_on_expiring = fixBoolean(this.config.retentionPolicy.safety.warn_on_expiring);
}
if (this.config.retentionPolicy.audit) {
this.config.retentionPolicy.audit.log_deletions = fixBoolean(this.config.retentionPolicy.audit.log_deletions);
this.config.retentionPolicy.audit.backup_before_delete = fixBoolean(this.config.retentionPolicy.audit.backup_before_delete);
}
}
}
/**
* Merge partial config with defaults
*
* IMPORTANT: This function preserves unknown fields for forward compatibility.
* If a future version adds new config fields, older versions won't lose them.
*/
mergeWithDefaults(partial) {
const defaults = this.getDefaultConfig();
// Start with a deep clone of partial to preserve all unknown fields
const result = JSON.parse(JSON.stringify(partial));
// Ensure all required fields exist with defaults
result.version = result.version || defaults.version;
// User section - preserve unknown fields while ensuring required fields
result.user = {
...result.user,
username: result.user?.username ?? defaults.user.username,
email: result.user?.email ?? defaults.user.email,
display_name: result.user?.display_name ?? defaults.user.display_name
};
// GitHub section - deep merge preserving unknown fields
if (!result.github)
result.github = {};
result.github.portfolio = {
...defaults.github.portfolio,
...result.github.portfolio
};
result.github.auth = {
...defaults.github.auth,
...result.github.auth
};
// Sync section - preserve unknown fields at all levels
if (!result.sync)
result.sync = {};
result.sync.enabled = result.sync.enabled ?? defaults.sync.enabled;
result.sync.individual = {
...defaults.sync.individual,
...result.sync.individual
};
result.sync.bulk = {
...defaults.sync.bulk,
...result.sync.bulk
};
result.sync.privacy = {
...defaults.sync.privacy,
...result.sync.privacy,
// Special handling for arrays - use provided or default
excluded_patterns: result.sync.privacy?.excluded_patterns || defaults.sync.privacy.excluded_patterns
};
// Collection section
result.collection = {
...defaults.collection,
...result.collection
};
// Elements section
if (!result.elements)
result.elements = {};
result.elements = {
...result.elements,
auto_activate: result.elements.auto_activate || defaults.elements.auto_activate,
default_element_dir: result.elements.default_element_dir || defaults.elements.default_element_dir
};
// Display section
if (!result.display)
result.display = {};
result.display.persona_indicators = {
...defaults.display.persona_indicators,
...result.display.persona_indicators
};
result.display.verbose_logging = result.display.verbose_logging ?? defaults.display.verbose_logging;
result.display.show_progress = result.display.show_progress ?? defaults.display.show_progress;
// Wizard section
result.wizard = {
...defaults.wizard,
...result.wizard
};
// AutoLoad section (Issue: regression from current-server)
// This was missing in mcp-server refactor, causing auto-load to fail for existing configs
result.autoLoad = {
...defaults.autoLoad,
...result.autoLoad
};
// Retention Policy section (Issue #51)
// IMPORTANT: Defaults are disabled - nothing auto-deleted without explicit consent
if (!result.retentionPolicy)
result.retentionPolicy = {};
result.retentionPolicy = {
enabled: result.retentionPolicy.enabled ?? defaults.retentionPolicy.enabled,
enforcement_mode: result.retentionPolicy.enforcement_mode ?? defaults.retentionPolicy.enforcement_mode,
safety: {
...defaults.retentionPolicy.safety,
...result.retentionPolicy.safety
},
audit: {
...defaults.retentionPolicy.audit,
...result.retentionPolicy.audit
},
defaults: {
...defaults.retentionPolicy.defaults,
...result.retentionPolicy.defaults
}
};
return result;
}
/**
* Migrate settings from environment variables
*/
async migrateFromEnvironment() {
let migrated = false;
// NOTE: These are optional custom env vars not in centralized config
// They're used for backwards compatibility migration only
// Migrate user settings
if (process.env.DOLLHOUSE_USER && !this.config?.user.username) {
if (!this.config)
this.config = this.getDefaultConfig();
this.config.user.username = process.env.DOLLHOUSE_USER;
migrated = true;
}
if (process.env.DOLLHOUSE_EMAIL && !this.config?.user.email) {
if (!this.config)
this.config = this.getDefaultConfig();
this.config.user.email = process.env.DOLLHOUSE_EMAIL;
migrated = true;
}
// Migrate portfolio URL
if (process.env.DOLLHOUSE_PORTFOLIO_URL && !this.config?.github.portfolio.repository_url) {
if (!this.config)
this.config = this.getDefaultConfig();
this.config.github.portfolio.repository_url = process.env.DOLLHOUSE_PORTFOLIO_URL;
migrated = true;
}
// Migrate collection auto-submit - use centralized env config
if (env.DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION !== false) {
if (!this.config)
this.config = this.getDefaultConfig();
this.config.collection.auto_submit = env.DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION;
migrated = true;
}
if (migrated) {
logger.info('Migrated settings from environment variables');
}
}
/**
* Reset configuration to defaults
* SECURITY FIX (PR #895): Added prototype pollution protection
* Previously: Direct property assignment allowed __proto__ injection
* Now: Validates keys against forbidden properties before assignment
*/
async resetConfig(section) {
const defaults = this.getDefaultConfig();
if (section) {
// Reset specific section
if (!this.config) {
this.config = defaults;
}
else {
// SECURITY: Validate section path to prevent prototype pollution
validatePropertyPath(section, 'section');
const sectionKeys = section.split('.');
let current = this.config;
let defaultSection = defaults;
for (let i = 0; i < sectionKeys.length - 1; i++) {
current = current[sectionKeys[i]];
defaultSection = defaultSection[sectionKeys[i]];
}
const lastKey = sectionKeys[sectionKeys.length - 1];
// SECURITY: Use secure property setter to avoid prototype chain pollution
safeSetProperty(current, lastKey, defaultSection[lastKey]);
}
await this.saveConfig();
return {
success: true,
message: `Section '${section}' reset to defaults`
};
}
else {
// Reset entire config
this.config = defaults;
await this.saveConfig();
return {
success: true,
message: 'Configuration reset to defaults'
};
}
}
/**
* Export configuration to file
*/
async exportConfig(filePath) {
if (!this.config) {
return {
success: false,
message: 'No configuration to export'
};
}
try {
const yamlContent = yaml.dump(this.config, {
indent: 2,
lineWidth: 120,
noRefs: true,
sortKeys: false
});
await this.fileOperations.writeFile(filePath, yamlContent, {
source: 'ConfigManager.exportConfig'
});
await this.fileOperations.chmod(filePath, 0o600, {
source: 'ConfigManager.exportConfig'
});
return {
success: true,
message: `Configuration exported to ${filePath}`
};
}
catch (error) {
return {
success: false,
message: `Failed to export configuration: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Import configuration from file
*/
async importConfig(filePath) {
try {
const content = await this.fileOperations.readFile(filePath, {
source: 'ConfigManager.importConfig'
});
// Parse and validate
const parsed = SecureYamlParser.parse(content, {
maxYamlSize: 64 * 1024,
validateContent: false,
validateFields: false
});
if (!parsed.data || typeof parsed.data !== 'object') {
return {
success: false,
message: 'Invalid configuration format in import file'
};
}
// Merge with defaults
this.config = this.mergeWithDefaults(parsed.data);
// Save the imported config
await this.saveConfig();
return {
success: true,
message: `Configuration imported from ${filePath}`
};
}
catch (error) {
return {
success: false,
message: `Failed to import configuration: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Get formatted config for display
*/
getFormattedConfig(section) {
if (!this.config) {
return 'Configuration not initialized';
}
let configToShow = this.config;
if (section) {
configToShow = this.getSetting(section);
if (!configToShow) {
return `Section '${section}' not found`;
}
}
// Remove sensitive data for display
const sanitized = JSON.parse(JSON.stringify(configToShow));
// Don't show tokens if they exist
if (sanitized.github?.auth?.token) {
sanitized.github.auth.token = '***REDACTED***';
}
return yaml.dump(sanitized, {
indent: 2,
lineWidth: 120,
noRefs: true,
sortKeys: false
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29uZmlnTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25maWcvQ29uZmlnTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7R0FXRztBQUVILE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLGVBQWUsRUFDZixnQkFBZ0IsRUFDaEIsa0JBQWtCLEVBQ25CLE1BQU0sMkJBQTJCLENBQUM7QUFDbkMsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQXlOL0Isc0VBQXNFO0FBQ3RFLE1BQU0sVUFBVSxZQUFZLENBQUMsS0FBYztJQUN6QyxNQUFNLEdBQUcsR0FBRyxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0lBQzlELElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUN2RSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzdCLElBQUksSUFBSSxHQUFHLElBQUksSUFBSSxJQUFJLEdBQUcsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2xELE9BQU8sSUFBa0IsQ0FBQztBQUM1QixDQUFDO0FBaURELE1BQU0sT0FBTyxhQUFhO0lBQ2hCLFNBQVMsQ0FBUztJQUNsQixV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