Files
claude-mem/src/shared/SettingsDefaultsManager.ts
T
alan e0fec4bad7 feat: add terminal output control for SessionStart context (#1143)
* feat: add terminal output control for SessionStart context

Add CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT setting to control whether
context is displayed in the terminal at SessionStart.

When set to "false", the terminal remains clean at startup while
context is still injected into Claude's system prompt. This allows
users who find the context output verbose to disable it without
losing the automatic context injection.

Defaults to "true" for backward compatibility.

Changes:
- Add CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT to SettingsDefaultsManager
- Check setting in context handler before setting systemMessage
- Update settings file format to include new option

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: use USER_SETTINGS_PATH and skip color fetch when disabled

Address PR feedback from automated review:

1. Use shared USER_SETTINGS_PATH constant instead of hardcoded path
   - Respects custom CLAUDE_MEM_DATA_DIR override
   - Consistent with other handlers (session-init, observation)

2. Skip color fetch when terminal output disabled
   - Check setting before making HTTP requests
   - Saves network round-trip on every session start

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Alan Dong <adong@Alans-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-23 21:05:05 -05:00

245 lines
10 KiB
TypeScript

/**
* SettingsDefaultsManager
*
* Single source of truth for all default configuration values.
* Provides methods to get defaults with optional environment variable overrides.
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { DEFAULT_OBSERVATION_TYPES_STRING, DEFAULT_OBSERVATION_CONCEPTS_STRING } from '../constants/observation-metadata.js';
// NOTE: Do NOT import logger here - it creates a circular dependency
// logger.ts depends on SettingsDefaultsManager for its initialization
export interface SettingsDefaults {
CLAUDE_MEM_MODEL: string;
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
CLAUDE_MEM_WORKER_PORT: string;
CLAUDE_MEM_WORKER_HOST: string;
CLAUDE_MEM_SKIP_TOOLS: string;
// AI Provider Configuration
CLAUDE_MEM_PROVIDER: string; // 'claude' | 'gemini' | 'openrouter'
CLAUDE_MEM_CLAUDE_AUTH_METHOD: string; // 'cli' | 'api' - how Claude provider authenticates
CLAUDE_MEM_GEMINI_API_KEY: string;
CLAUDE_MEM_GEMINI_MODEL: string; // 'gemini-2.5-flash-lite' | 'gemini-2.5-flash' | 'gemini-3-flash-preview'
CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED: string; // 'true' | 'false' - enable rate limiting for free tier
CLAUDE_MEM_OPENROUTER_API_KEY: string;
CLAUDE_MEM_OPENROUTER_MODEL: string;
CLAUDE_MEM_OPENROUTER_SITE_URL: string;
CLAUDE_MEM_OPENROUTER_APP_NAME: string;
CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES: string;
CLAUDE_MEM_OPENROUTER_MAX_TOKENS: string;
// System Configuration
CLAUDE_MEM_DATA_DIR: string;
CLAUDE_MEM_LOG_LEVEL: string;
CLAUDE_MEM_PYTHON_VERSION: string;
CLAUDE_CODE_PATH: string;
CLAUDE_MEM_MODE: string;
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: string;
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: string;
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: string;
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: string;
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: string;
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: string;
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: string;
CLAUDE_MEM_CONTEXT_FULL_FIELD: string;
CLAUDE_MEM_CONTEXT_SESSION_COUNT: string;
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: string;
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: string;
CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT: string;
CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED: string;
// Process Management
CLAUDE_MEM_MAX_CONCURRENT_AGENTS: string; // Max concurrent Claude SDK agent subprocesses (default: 2)
// Exclusion Settings
CLAUDE_MEM_EXCLUDED_PROJECTS: string; // Comma-separated glob patterns for excluded project paths
CLAUDE_MEM_FOLDER_MD_EXCLUDE: string; // JSON array of folder paths to exclude from CLAUDE.md generation
// Chroma Vector Database Configuration
CLAUDE_MEM_CHROMA_ENABLED: string; // 'true' | 'false' - set to 'false' for SQLite-only mode
CLAUDE_MEM_CHROMA_MODE: string; // 'local' | 'remote'
CLAUDE_MEM_CHROMA_HOST: string;
CLAUDE_MEM_CHROMA_PORT: string;
CLAUDE_MEM_CHROMA_SSL: string;
// Future cloud support
CLAUDE_MEM_CHROMA_API_KEY: string;
CLAUDE_MEM_CHROMA_TENANT: string;
CLAUDE_MEM_CHROMA_DATABASE: string;
}
export class SettingsDefaultsManager {
/**
* Default values for all settings
*/
private static readonly DEFAULTS: SettingsDefaults = {
CLAUDE_MEM_MODEL: 'claude-sonnet-4-5',
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
CLAUDE_MEM_WORKER_PORT: '37777',
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion',
// AI Provider Configuration
CLAUDE_MEM_PROVIDER: 'claude', // Default to Claude
CLAUDE_MEM_CLAUDE_AUTH_METHOD: 'cli', // Default to CLI subscription billing (not API key)
CLAUDE_MEM_GEMINI_API_KEY: '', // Empty by default, can be set via UI or env
CLAUDE_MEM_GEMINI_MODEL: 'gemini-2.5-flash-lite', // Default Gemini model (highest free tier RPM)
CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED: 'true', // Rate limiting ON by default for free tier users
CLAUDE_MEM_OPENROUTER_API_KEY: '', // Empty by default, can be set via UI or env
CLAUDE_MEM_OPENROUTER_MODEL: 'xiaomi/mimo-v2-flash:free', // Default OpenRouter model (free tier)
CLAUDE_MEM_OPENROUTER_SITE_URL: '', // Optional: for OpenRouter analytics
CLAUDE_MEM_OPENROUTER_APP_NAME: 'claude-mem', // App name for OpenRouter analytics
CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES: '20', // Max messages in context window
CLAUDE_MEM_OPENROUTER_MAX_TOKENS: '100000', // Max estimated tokens (~100k safety limit)
// System Configuration
CLAUDE_MEM_DATA_DIR: join(homedir(), '.claude-mem'),
CLAUDE_MEM_LOG_LEVEL: 'INFO',
CLAUDE_MEM_PYTHON_VERSION: '3.13',
CLAUDE_CODE_PATH: '', // Empty means auto-detect via 'which claude'
CLAUDE_MEM_MODE: 'code', // Default mode profile
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'false',
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: 'false',
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: 'false',
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: 'true',
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: DEFAULT_OBSERVATION_TYPES_STRING,
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: DEFAULT_OBSERVATION_CONCEPTS_STRING,
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: '0',
CLAUDE_MEM_CONTEXT_FULL_FIELD: 'narrative',
CLAUDE_MEM_CONTEXT_SESSION_COUNT: '10',
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: 'true',
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: 'false',
CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT: 'true',
CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED: 'false',
// Process Management
CLAUDE_MEM_MAX_CONCURRENT_AGENTS: '2', // Max concurrent Claude SDK agent subprocesses
// Exclusion Settings
CLAUDE_MEM_EXCLUDED_PROJECTS: '', // Comma-separated glob patterns for excluded project paths
CLAUDE_MEM_FOLDER_MD_EXCLUDE: '[]', // JSON array of folder paths to exclude from CLAUDE.md generation
// Chroma Vector Database Configuration
CLAUDE_MEM_CHROMA_ENABLED: 'true', // Set to 'false' to disable Chroma and use SQLite-only search
CLAUDE_MEM_CHROMA_MODE: 'local', // 'local' uses persistent chroma-mcp via uvx, 'remote' connects to existing server
CLAUDE_MEM_CHROMA_HOST: '127.0.0.1',
CLAUDE_MEM_CHROMA_PORT: '8000',
CLAUDE_MEM_CHROMA_SSL: 'false',
// Future cloud support (claude-mem pro)
CLAUDE_MEM_CHROMA_API_KEY: '',
CLAUDE_MEM_CHROMA_TENANT: 'default_tenant',
CLAUDE_MEM_CHROMA_DATABASE: 'default_database',
};
/**
* Get all defaults as an object
*/
static getAllDefaults(): SettingsDefaults {
return { ...this.DEFAULTS };
}
/**
* Get a default value from defaults (no environment variable override)
*/
static get(key: keyof SettingsDefaults): string {
return this.DEFAULTS[key];
}
/**
* Get an integer default value
*/
static getInt(key: keyof SettingsDefaults): number {
const value = this.get(key);
return parseInt(value, 10);
}
/**
* Get a boolean default value
* Handles both string 'true' and boolean true from JSON
*/
static getBool(key: keyof SettingsDefaults): boolean {
const value = this.get(key);
return value === 'true' || value === true;
}
/**
* Apply environment variable overrides to settings
* Environment variables take highest priority over file and defaults
*/
private static applyEnvOverrides(settings: SettingsDefaults): SettingsDefaults {
const result = { ...settings };
for (const key of Object.keys(this.DEFAULTS) as Array<keyof SettingsDefaults>) {
if (process.env[key] !== undefined) {
result[key] = process.env[key]!;
}
}
return result;
}
/**
* Load settings from file with fallback to defaults
* Returns merged settings with proper priority: process.env > settings file > defaults
* Handles all errors (missing file, corrupted JSON, permissions) gracefully
*
* Configuration Priority:
* 1. Environment variables (highest priority)
* 2. Settings file (~/.claude-mem/settings.json)
* 3. Default values (lowest priority)
*/
static loadFromFile(settingsPath: string): SettingsDefaults {
try {
if (!existsSync(settingsPath)) {
const defaults = this.getAllDefaults();
try {
const dir = dirname(settingsPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(settingsPath, JSON.stringify(defaults, null, 2), 'utf-8');
// Use console instead of logger to avoid circular dependency
console.log('[SETTINGS] Created settings file with defaults:', settingsPath);
} catch (error) {
console.warn('[SETTINGS] Failed to create settings file, using in-memory defaults:', settingsPath, error);
}
// Still apply env var overrides even when file doesn't exist
return this.applyEnvOverrides(defaults);
}
const settingsData = readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(settingsData);
// MIGRATION: Handle old nested schema { env: {...} }
let flatSettings = settings;
if (settings.env && typeof settings.env === 'object') {
// Migrate from nested to flat schema
flatSettings = settings.env;
// Auto-migrate the file to flat schema
try {
writeFileSync(settingsPath, JSON.stringify(flatSettings, null, 2), 'utf-8');
console.log('[SETTINGS] Migrated settings file from nested to flat schema:', settingsPath);
} catch (error) {
console.warn('[SETTINGS] Failed to auto-migrate settings file:', settingsPath, error);
// Continue with in-memory migration even if write fails
}
}
// Merge file settings with defaults (flat schema)
const result: SettingsDefaults = { ...this.DEFAULTS };
for (const key of Object.keys(this.DEFAULTS) as Array<keyof SettingsDefaults>) {
if (flatSettings[key] !== undefined) {
result[key] = flatSettings[key];
}
}
// Apply environment variable overrides (highest priority)
return this.applyEnvOverrides(result);
} catch (error) {
console.warn('[SETTINGS] Failed to load settings, using defaults:', settingsPath, error);
// Still apply env var overrides even on error
return this.applyEnvOverrides(this.getAllDefaults());
}
}
}