Refactor settings management to use ~/.claude-mem/settings.json

- Updated paths in troubleshooting documentation to reflect new settings file location.
- Modified diagnostics and reference files to read from ~/.claude-mem/settings.json.
- Introduced getWorkerPort utility for cleaner worker port retrieval.
- Enhanced ChromaSync and SDKAgent to load Python version and Claude path from settings.
- Updated SettingsRoutes to validate new settings: CLAUDE_MEM_LOG_LEVEL and CLAUDE_MEM_PYTHON_VERSION.
- Added early-settings module to load settings for logger and other early-stage modules.
- Adjusted logger to use early-loaded log level setting.
- Refactored paths to utilize early-loaded data directory setting.
This commit is contained in:
Alex Newman
2025-12-09 12:23:33 -05:00
parent b22adcca05
commit fc5c2d5e07
23 changed files with 342 additions and 166 deletions
+2 -1
View File
@@ -15,11 +15,12 @@ import {
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { silentDebug } from '../utils/silent-debug.js';
import { getWorkerPort } from '../shared/worker-utils.js';
/**
* Worker HTTP API configuration
*/
const WORKER_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
const WORKER_PORT = getWorkerPort();
const WORKER_BASE_URL = `http://localhost:${WORKER_PORT}`;
/**
+4 -1
View File
@@ -13,6 +13,8 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import { ParsedObservation, ParsedSummary } from '../../sdk/parser.js';
import { SessionStore } from '../sqlite/SessionStore.js';
import { logger } from '../../utils/logger.js';
import { SettingsDefaultsManager } from '../worker/settings/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
import path from 'path';
import os from 'os';
@@ -96,7 +98,8 @@ export class ChromaSync {
try {
// Use Python 3.13 by default to avoid onnxruntime compatibility issues with Python 3.14+
// See: https://github.com/thedotmack/claude-mem/issues/170 (Python 3.14 incompatibility)
const pythonVersion = process.env.CLAUDE_MEM_PYTHON_VERSION || '3.13';
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
const pythonVersion = settings.CLAUDE_MEM_PYTHON_VERSION;
const transport = new StdioClientTransport({
command: 'uvx',
args: [
+3 -1
View File
@@ -18,6 +18,7 @@ import { silentDebug } from '../../utils/silent-debug.js';
import { parseObservations, parseSummary } from '../../sdk/parser.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import { SettingsDefaultsManager } from './settings/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
import type { ActiveSession, SDKUserMessage, PendingMessage } from '../worker-types.js';
// Import Agent SDK (assumes it's installed)
@@ -410,7 +411,8 @@ export class SDKAgent {
* Find Claude executable (inline, called once per session)
*/
private findClaudeExecutable(): string {
const claudePath = process.env.CLAUDE_CODE_PATH ||
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
const claudePath = settings.CLAUDE_CODE_PATH ||
execSync(process.platform === 'win32' ? 'where claude' : 'which claude', { encoding: 'utf8', windowsHide: true })
.trim().split('\n')[0].trim();
@@ -45,7 +45,7 @@ export class SettingsRoutes extends BaseRouteHandler {
}
/**
* Get environment settings (from ~/.claude/settings.json)
* Get environment settings (from ~/.claude-mem/settings.json)
*/
private handleGetSettings = this.wrapHandler((req: Request, res: Response): void => {
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
@@ -54,7 +54,7 @@ export class SettingsRoutes extends BaseRouteHandler {
});
/**
* Update environment settings (in ~/.claude/settings.json) with validation
* Update environment settings (in ~/.claude-mem/settings.json) with validation
*/
private handleUpdateSettings = this.wrapHandler((req: Request, res: Response): void => {
// Validate CLAUDE_MEM_CONTEXT_OBSERVATIONS
@@ -81,6 +81,30 @@ export class SettingsRoutes extends BaseRouteHandler {
}
}
// Validate CLAUDE_MEM_LOG_LEVEL
if (req.body.CLAUDE_MEM_LOG_LEVEL) {
const validLevels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'SILENT'];
if (!validLevels.includes(req.body.CLAUDE_MEM_LOG_LEVEL.toUpperCase())) {
res.status(400).json({
success: false,
error: 'CLAUDE_MEM_LOG_LEVEL must be one of: DEBUG, INFO, WARN, ERROR, SILENT'
});
return;
}
}
// Validate CLAUDE_MEM_PYTHON_VERSION (must be valid Python version format)
if (req.body.CLAUDE_MEM_PYTHON_VERSION) {
const pythonVersionRegex = /^3\.\d+$/;
if (!pythonVersionRegex.test(req.body.CLAUDE_MEM_PYTHON_VERSION)) {
res.status(400).json({
success: false,
error: 'CLAUDE_MEM_PYTHON_VERSION must be in format "3.X" (e.g., "3.13")'
});
return;
}
}
// Validate context settings
const validation = this.validateContextSettings(req.body);
if (!validation.valid) {
@@ -108,15 +132,24 @@ export class SettingsRoutes extends BaseRouteHandler {
'CLAUDE_MEM_MODEL',
'CLAUDE_MEM_CONTEXT_OBSERVATIONS',
'CLAUDE_MEM_WORKER_PORT',
// System Configuration
'CLAUDE_MEM_DATA_DIR',
'CLAUDE_MEM_LOG_LEVEL',
'CLAUDE_MEM_PYTHON_VERSION',
'CLAUDE_CODE_PATH',
// Token Economics
'CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS',
'CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS',
'CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT',
'CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT',
// Observation Filtering
'CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES',
'CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS',
// Display Configuration
'CLAUDE_MEM_CONTEXT_FULL_COUNT',
'CLAUDE_MEM_CONTEXT_FULL_FIELD',
'CLAUDE_MEM_CONTEXT_SESSION_COUNT',
// Feature Toggles
'CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY',
'CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE',
];
@@ -6,12 +6,19 @@
*/
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { DEFAULT_OBSERVATION_TYPES_STRING, DEFAULT_OBSERVATION_CONCEPTS_STRING } from '../../../constants/observation-metadata.js';
export interface SettingsDefaults {
CLAUDE_MEM_MODEL: string;
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
CLAUDE_MEM_WORKER_PORT: string;
// System Configuration
CLAUDE_MEM_DATA_DIR: string;
CLAUDE_MEM_LOG_LEVEL: string;
CLAUDE_MEM_PYTHON_VERSION: string;
CLAUDE_CODE_PATH: string;
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: string;
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: string;
@@ -37,6 +44,11 @@ export class SettingsDefaultsManager {
CLAUDE_MEM_MODEL: 'claude-haiku-4-5',
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
CLAUDE_MEM_WORKER_PORT: '37777',
// 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'
// Token Economics
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'true',
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: 'true',
+31
View File
@@ -0,0 +1,31 @@
import { join } from 'path';
import { homedir } from 'os';
import { existsSync, readFileSync } from 'fs';
const SETTINGS_PATH = join(homedir(), '.claude-mem', 'settings.json');
interface EarlySettings {
CLAUDE_MEM_DATA_DIR?: string;
CLAUDE_MEM_LOG_LEVEL?: string;
CLAUDE_MEM_PYTHON_VERSION?: string;
CLAUDE_CODE_PATH?: string;
}
/**
* Load settings for early-stage modules (paths, logger)
* Falls back to env vars, then defaults
*/
export function loadEarlySetting(key: keyof EarlySettings, defaultValue: string): string {
// Priority: settings.json > env var > default
try {
if (existsSync(SETTINGS_PATH)) {
const data = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
const fileValue = data.env?.[key];
if (fileValue !== undefined) return fileValue;
}
} catch {
// Fail silently - fall through to env var
}
return process.env[key] || defaultValue;
}
+3 -1
View File
@@ -3,6 +3,7 @@ import { homedir } from 'os';
import { existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import { loadEarlySetting } from './early-settings.js';
// Get __dirname that works in both ESM (hooks) and CJS (worker) contexts
function getDirname(): string {
@@ -22,7 +23,8 @@ const _dirname = getDirname();
*/
// Base directories
export const DATA_DIR = process.env.CLAUDE_MEM_DATA_DIR || join(homedir(), '.claude-mem');
export const DATA_DIR = loadEarlySetting('CLAUDE_MEM_DATA_DIR', join(homedir(), '.claude-mem'));
// Note: CLAUDE_CONFIG_DIR is a Claude Code setting, not claude-mem, so leave as env var
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
// Data subdirectories
+3 -1
View File
@@ -3,6 +3,8 @@
* Provides readable, traceable logging with correlation IDs and data flow tracking
*/
import { loadEarlySetting } from '../shared/early-settings.js';
export enum LogLevel {
DEBUG = 0,
INFO = 1,
@@ -26,7 +28,7 @@ class Logger {
constructor() {
// Parse log level from environment
const envLevel = process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase() || 'INFO';
const envLevel = loadEarlySetting('CLAUDE_MEM_LOG_LEVEL', 'INFO').toUpperCase();
this.level = LogLevel[envLevel as keyof typeof LogLevel] ?? LogLevel.INFO;
// Disable colors when output is not a TTY (e.g., PM2 logs)