UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

202 lines 6.37 kB
/** * Environment-based configuration for Pino logger */ import { homedir, platform } from 'os'; import { join } from 'path'; /** * Get the default log directory based on platform * Follows OS conventions: * - macOS: ~/Library/Logs/nanocoder * - Linux: ~/.local/state/nanocoder/logs (XDG_STATE_HOME) * - Windows: %LOCALAPPDATA%/nanocoder/logs */ export function getDefaultLogDirectory() { if (process.env.NANOCODER_LOG_DIR) { return process.env.NANOCODER_LOG_DIR; } switch (platform()) { case 'win32': return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'nanocoder', 'logs'); case 'darwin': return join(homedir(), 'Library', 'Logs', 'nanocoder'); default: // linux return join(process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state'), 'nanocoder', 'logs'); } } /** * Create development configuration */ export function createDevelopmentConfig() { return { level: process.env.NANOCODER_LOG_LEVEL || 'debug', destination: String(process.stdout.fd), pretty: true, redact: ['apiKey', 'token', 'password', 'secret'], correlation: true, serialize: false, target: 'pino-pretty', options: { translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', messageFormat: undefined, customPrettifiers: {}, levelFirst: false, singleLine: false, }, }; } /** * Create production configuration * * In production, we want: * - File logging enabled at 'info' level by default (for diagnostics) * - Console/UI output silent (to avoid polluting the user interface) * * The log level here controls what gets written to files. * Console output is suppressed by using file-only transport. */ export function createProductionConfig() { // Check if file logging is explicitly disabled const disableFileLogging = process.env.NANOCODER_LOG_DISABLE_FILE === 'true'; // File log level defaults to 'info' for useful diagnostics // Can be overridden with NANOCODER_LOG_LEVEL env var const fileLogLevel = process.env.NANOCODER_LOG_LEVEL || (disableFileLogging ? 'silent' : 'info'); const baseConfig = { level: fileLogLevel, pretty: false, redact: ['apiKey', 'token', 'password', 'email', 'userId', 'secret'], correlation: true, serialize: true, }; // If file logging is disabled, only use stdout (for UI) if (disableFileLogging) { return { ...baseConfig, destination: String(process.stdout.fd), target: 'pino-pretty', options: { colorize: false, // No colors in production translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', levelFirst: true, messageFormat: '{level} - {msg}', singleLine: true, // Compact for UI }, }; } // Otherwise use stdout for UI with optional file logging // This ensures UI works while still allowing file persistence when needed return { ...baseConfig, // Always output to stdout for UI compatibility destination: String(process.stdout.fd), target: 'pino-pretty', options: { colorize: false, // No colors in production translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', // Reduce UI clutter levelFirst: false, messageFormat: '{msg}', singleLine: true, // Compact for UI }, // Note: File logging will be handled by the multi-transport system in transports.ts // when NANOCODER_LOG_TO_FILE=true is set }; } /** * Create test configuration */ export function createTestConfig() { return { level: process.env.LOG_LEVEL || 'debug', // Changed from 'silent' to 'debug' pretty: false, redact: ['apiKey', 'token', 'password'], correlation: false, serialize: false, target: 'pino/file', options: { destination: '/dev/null', }, }; } /** * Get configuration based on current environment * * For CLI tools, we default to production (silent) behavior when NODE_ENV is not set. * This gives users a clean experience. Developers working on nanocoder itself should * explicitly set NODE_ENV=development to see debug logs. */ export function getEnvironmentConfig() { const env = process.env.NODE_ENV; switch (env) { case 'development': return createDevelopmentConfig(); case 'test': return createTestConfig(); default: // Default to production (silent) for normal CLI usage return createProductionConfig(); } } /** * Validate log level */ export function validateLogLevel(level) { const validLevels = [ 'fatal', 'error', 'warn', 'info', 'http', 'debug', 'trace', 'silent', ]; return validLevels.includes(level.toLowerCase()); } /** * Normalize log level string */ export function normalizeLogLevel(level) { const normalized = level.toLowerCase().trim(); // Map common aliases const aliases = { warning: 'warn', err: 'error', information: 'info', http: 'http', }; return aliases[normalized] || normalized; } /** * Create configuration with overrides */ export function createConfig(overrides = {}) { const baseConfig = getEnvironmentConfig(); // Apply overrides with validation if (overrides.level) { const normalizedLevel = normalizeLogLevel(overrides.level); if (!validateLogLevel(normalizedLevel)) { console.warn(`[WARNING] Invalid log level "${overrides.level}", using default`); } else { baseConfig.level = normalizedLevel; } } if (overrides.redact) { baseConfig.redact = [ ...new Set([...baseConfig.redact, ...overrides.redact]), ]; } // Merge other properties return { ...baseConfig, ...overrides, options: { ...baseConfig.options, ...overrides?.options, }, }; } //# sourceMappingURL=config.js.map