import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import yaml from 'js-yaml';
import { Logger } from '../utils/logger.js';
export interface LudusConfigOptions {
url?: string;
apiKey?: string;
skipCertVerification?: boolean;
proxyUrl?: string;
timeout?: number;
sshHost?: string;
sshUser?: string;
}
export class LudusConfig {
private logger: Logger;
private config: LudusConfigOptions;
constructor(logger: Logger) {
this.logger = logger;
this.config = this.loadConfig();
}
private loadConfig(): LudusConfigOptions {
const config: LudusConfigOptions = {};
// Load from config file first
const configPath = join(homedir(), '.config', 'ludus', 'config.yml');
if (existsSync(configPath)) {
try {
const fileContent = readFileSync(configPath, 'utf8');
const fileConfig = yaml.load(fileContent) as any;
// Map config file keys to our interface
if (fileConfig.url) config.url = fileConfig.url;
if (fileConfig.verify !== undefined) config.skipCertVerification = !fileConfig.verify;
if (fileConfig.proxy) config.proxyUrl = fileConfig.proxy;
if (fileConfig.ssh_host) config.sshHost = fileConfig.ssh_host;
if (fileConfig.ssh_user) config.sshUser = fileConfig.ssh_user;
this.logger.debug('Loaded config from file', { path: configPath });
} catch (error) {
this.logger.warn('Failed to load config file', {
path: configPath,
error: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : String(error)
});
}
}
// Override with environment variables
if (process.env.LUDUS_URL) {
config.url = process.env.LUDUS_URL;
}
if (process.env.LUDUS_API_KEY) {
config.apiKey = process.env.LUDUS_API_KEY;
}
if (process.env.LUDUS_VERIFY) {
config.skipCertVerification = process.env.LUDUS_VERIFY === 'false';
}
if (process.env.LUDUS_PROXY) {
config.proxyUrl = process.env.LUDUS_PROXY;
}
if (process.env.LUDUS_TIMEOUT) {
config.timeout = parseInt(process.env.LUDUS_TIMEOUT, 10);
}
if (process.env.LUDUS_SSH_HOST) {
config.sshHost = process.env.LUDUS_SSH_HOST;
}
if (process.env.LUDUS_SSH_USER) {
config.sshUser = process.env.LUDUS_SSH_USER;
}
// Set defaults
if (!config.skipCertVerification) {
config.skipCertVerification = false;
}
if (!config.timeout) {
config.timeout = 30000; // 30 seconds
}
return config;
}
public getUrl(): string {
if (!this.config.url) {
throw new Error('Ludus URL not configured. Set LUDUS_URL environment variable or configure in ~/.config/ludusMCP/config.yml');
}
return this.config.url;
}
public getApiKey(): string {
if (!this.config.apiKey) {
throw new Error('Ludus API key not configured. Set LUDUS_API_KEY environment variable or configure in ~/.config/ludusMCP/config.yml');
}
return this.config.apiKey;
}
public getSkipCertVerification(): boolean {
return this.config.skipCertVerification || false;
}
public getProxyUrl(): string | undefined {
return this.config.proxyUrl;
}
public getTimeout(): number {
return this.config.timeout || 30000;
}
public getSSHHost(): string | undefined {
return this.config.sshHost;
}
public getSSHUser(): string | undefined {
return this.config.sshUser;
}
public validateConfig(): void {
const url = this.getUrl();
const apiKey = this.getApiKey();
// Validate URL format
try {
new URL(url);
} catch (error) {
throw new Error(`Invalid Ludus URL format: ${url}`);
}
// Validate API key format (USERID.{40-char-key})
const apiKeyPattern = /^[^.]+\.[a-zA-Z0-9]{40}$/;
if (!apiKeyPattern.test(apiKey)) {
throw new Error('Invalid API key format. Expected format: USERID.{40-character-key}');
}
this.logger.info('Configuration validated successfully', {
url,
apiKeyUser: apiKey.split('.')[0],
skipCertVerification: this.getSkipCertVerification(),
timeout: this.getTimeout()
});
}
public getConfig(): LudusConfigOptions {
return { ...this.config };
}
}