Release v3.6.3
Published from npm package build Source: https://github.com/thedotmack/claude-mem-source
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// <Block> 5.1 ====================================
|
||||
// Default values
|
||||
const DEFAULT_PACKAGE_NAME = 'claude-mem';
|
||||
// This MUST be replaced by build process with --define flag
|
||||
// @ts-ignore
|
||||
// For development, use fallback
|
||||
const DEFAULT_PACKAGE_VERSION = typeof __DEFAULT_PACKAGE_VERSION__ !== 'undefined'
|
||||
? __DEFAULT_PACKAGE_VERSION__
|
||||
: '3.5.6-dev';
|
||||
const DEFAULT_PACKAGE_DESCRIPTION = 'Memory compression system for Claude Code - persist context across sessions';
|
||||
|
||||
let packageName = DEFAULT_PACKAGE_NAME;
|
||||
let packageVersion = DEFAULT_PACKAGE_VERSION;
|
||||
let packageDescription = DEFAULT_PACKAGE_DESCRIPTION;
|
||||
// </Block> =======================================
|
||||
|
||||
// Try to read package.json if it exists (for development)
|
||||
// <Block> 5.2 ====================================
|
||||
try {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageJsonPath = join(__dirname, '..', '..', 'package.json');
|
||||
|
||||
// <Block> 5.2a ====================================
|
||||
if (existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
// <Block> 5.2b ====================================
|
||||
packageName = packageJson.name || DEFAULT_PACKAGE_NAME;
|
||||
packageVersion = packageJson.version || DEFAULT_PACKAGE_VERSION;
|
||||
packageDescription = packageJson.description || DEFAULT_PACKAGE_DESCRIPTION;
|
||||
// </Block> =======================================
|
||||
}
|
||||
// </Block> =======================================
|
||||
} catch {
|
||||
// Use defaults if package.json can't be read
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 5.3 ====================================
|
||||
// Export package configuration
|
||||
export const PACKAGE_NAME = packageName;
|
||||
export const PACKAGE_VERSION = packageVersion;
|
||||
export const PACKAGE_DESCRIPTION = packageDescription;
|
||||
|
||||
// Export commonly used names
|
||||
export const CLI_NAME = PACKAGE_NAME; // The CLI command name
|
||||
// </Block> =======================================
|
||||
@@ -0,0 +1,200 @@
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { HookError, CompressionError, Logger, FileLogger } from './types.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export class ErrorHandler {
|
||||
private logger: Logger;
|
||||
private logDir: string;
|
||||
|
||||
// <Block> 7.1 ====================================
|
||||
constructor(enableDebug = false) {
|
||||
this.logDir = join(__dirname, '..', 'logs');
|
||||
this.ensureLogDirectory();
|
||||
|
||||
const logFile = join(
|
||||
this.logDir,
|
||||
`claude-mem-${new Date().toISOString().slice(0, 10)}.log`
|
||||
);
|
||||
this.logger = new FileLogger(logFile, enableDebug);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.2 ====================================
|
||||
private ensureLogDirectory(): void {
|
||||
if (!existsSync(this.logDir)) {
|
||||
mkdirSync(this.logDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.3 ====================================
|
||||
handleHookError(error: Error, hookType: string, payload?: unknown): never {
|
||||
// <Block> 7.3a ====================================
|
||||
const hookError =
|
||||
error instanceof HookError
|
||||
? error
|
||||
: new HookError(
|
||||
error.message,
|
||||
hookType,
|
||||
payload as any,
|
||||
'HOOK_EXECUTION_ERROR'
|
||||
);
|
||||
// </Block> =======================================
|
||||
|
||||
this.logger.error(`Hook execution failed in ${hookType}`, hookError, {
|
||||
hookType,
|
||||
payload: payload ? JSON.stringify(payload) : undefined,
|
||||
});
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
continue: false,
|
||||
stopReason: `Hook error: ${hookError.message}`,
|
||||
error: {
|
||||
type: hookError.name,
|
||||
message: hookError.message,
|
||||
code: hookError.code,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.4 ====================================
|
||||
handleCompressionError(
|
||||
error: Error,
|
||||
transcriptPath: string,
|
||||
stage: string
|
||||
): never {
|
||||
// <Block> 7.4a ====================================
|
||||
const compressionError =
|
||||
error instanceof CompressionError
|
||||
? error
|
||||
: new CompressionError(error.message, transcriptPath, stage as any);
|
||||
// </Block> =======================================
|
||||
|
||||
this.logger.error(`Compression failed during ${stage}`, compressionError, {
|
||||
transcriptPath,
|
||||
stage,
|
||||
});
|
||||
|
||||
console.error(`Compression error: ${compressionError.message}`);
|
||||
console.error(`Stage: ${stage}`);
|
||||
console.error(`Transcript: ${transcriptPath}`);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.5 ====================================
|
||||
handleValidationError(
|
||||
message: string,
|
||||
context?: Record<string, unknown>
|
||||
): never {
|
||||
this.logger.error('Validation error', undefined, { message, context });
|
||||
|
||||
console.error(`Validation error: ${message}`);
|
||||
// <Block> 7.5a ====================================
|
||||
if (context) {
|
||||
console.error('Context:', JSON.stringify(context, null, 2));
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.6 ====================================
|
||||
logSuccess(operation: string, details?: Record<string, unknown>): void {
|
||||
this.logger.info(`Operation successful: ${operation}`, details);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.7 ====================================
|
||||
logWarning(message: string, details?: Record<string, unknown>): void {
|
||||
this.logger.warn(message, details);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.8 ====================================
|
||||
logDebug(message: string, details?: Record<string, unknown>): void {
|
||||
this.logger.debug(message, details);
|
||||
}
|
||||
// </Block> =======================================
|
||||
}
|
||||
|
||||
// <Block> 7.9 ====================================
|
||||
export function parseStdinJson<T = unknown>(input: string): T {
|
||||
try {
|
||||
return JSON.parse(input) as T;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse JSON input: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.10 ===================================
|
||||
export async function safeExecute<T>(
|
||||
operation: () => Promise<T>,
|
||||
errorHandler: ErrorHandler,
|
||||
context: string
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
const message = `Safe execution failed in ${context}: ${error instanceof Error ? error.message : String(error)}`;
|
||||
errorHandler.handleValidationError(message, { context, error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.11 ===================================
|
||||
export function validateFileExists(
|
||||
filePath: string,
|
||||
errorHandler: ErrorHandler
|
||||
): void {
|
||||
if (!existsSync(filePath)) {
|
||||
errorHandler.handleValidationError(`File not found: ${filePath}`, {
|
||||
filePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 7.12 ===================================
|
||||
/**
|
||||
* Creates a standardized hook response using HookTemplates
|
||||
* @deprecated Use HookTemplates.createHookSuccessResponse or createHookErrorResponse instead
|
||||
* This function is maintained for backward compatibility but should be replaced with HookTemplates.
|
||||
*/
|
||||
export function createHookResponse(
|
||||
success: boolean,
|
||||
data?: Record<string, unknown>
|
||||
): string {
|
||||
// Log deprecation warning in development mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('createHookResponse in error-handler.ts is deprecated. Use HookTemplates.createHookSuccessResponse or createHookErrorResponse instead.');
|
||||
}
|
||||
|
||||
const response = {
|
||||
continue: success,
|
||||
suppressOutput: true, // Add standard suppressOutput field for Claude Code compatibility
|
||||
...data,
|
||||
};
|
||||
|
||||
return JSON.stringify(response);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
export const globalErrorHandler = new ErrorHandler(
|
||||
process.env.DEBUG === 'true'
|
||||
);
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Simple logging utility for claude-mem
|
||||
*/
|
||||
|
||||
export interface LogLevel {
|
||||
DEBUG: number;
|
||||
INFO: number;
|
||||
WARN: number;
|
||||
ERROR: number;
|
||||
}
|
||||
|
||||
const LOG_LEVELS: LogLevel = {
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
};
|
||||
|
||||
class Logger {
|
||||
// <Block> 2.1 ====================================
|
||||
private level: number = LOG_LEVELS.INFO;
|
||||
|
||||
setLevel(level: keyof LogLevel): void {
|
||||
this.level = LOG_LEVELS[level];
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 2.2 ====================================
|
||||
debug(message: string, ...args: any[]): void {
|
||||
if (this.level <= LOG_LEVELS.DEBUG) {
|
||||
console.debug(`[DEBUG] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 2.3 ====================================
|
||||
info(message: string, ...args: any[]): void {
|
||||
if (this.level <= LOG_LEVELS.INFO) {
|
||||
console.info(`[INFO] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 2.4 ====================================
|
||||
warn(message: string, ...args: any[]): void {
|
||||
if (this.level <= LOG_LEVELS.WARN) {
|
||||
console.warn(`[WARN] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 2.5 ====================================
|
||||
error(message: string, error?: any, context?: any): void {
|
||||
if (this.level <= LOG_LEVELS.ERROR) {
|
||||
console.error(`[ERROR] ${message}`);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (context) {
|
||||
console.error('Context:', context);
|
||||
}
|
||||
}
|
||||
}
|
||||
// </Block> =======================================
|
||||
}
|
||||
|
||||
export const log = new Logger();
|
||||
@@ -0,0 +1,91 @@
|
||||
import { sep, basename } from 'path';
|
||||
import { PathDiscovery } from '../services/path-discovery.js';
|
||||
|
||||
/**
|
||||
* PathResolver utility for managing claude-mem file system paths
|
||||
* Now delegates to PathDiscovery service for centralized path management
|
||||
*/
|
||||
export class PathResolver {
|
||||
private pathDiscovery: PathDiscovery;
|
||||
|
||||
// <Block> 1.1 ====================================
|
||||
constructor() {
|
||||
this.pathDiscovery = PathDiscovery.getInstance();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.2 ====================================
|
||||
getConfigDir(): string {
|
||||
return this.pathDiscovery.getDataDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.3 ====================================
|
||||
getIndexDir(): string {
|
||||
return this.pathDiscovery.getIndexDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.4 ====================================
|
||||
getIndexPath(): string {
|
||||
return this.pathDiscovery.getIndexPath();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.5 ====================================
|
||||
getArchiveDir(): string {
|
||||
return this.pathDiscovery.getArchivesDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.6 ====================================
|
||||
getProjectArchiveDir(projectName: string): string {
|
||||
return this.pathDiscovery.getProjectArchiveDirectory(projectName);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.7 ====================================
|
||||
getLogsDir(): string {
|
||||
return this.pathDiscovery.getLogsDirectory();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.8 ====================================
|
||||
static ensureDirectory(dirPath: string): void {
|
||||
PathDiscovery.getInstance().ensureDirectory(dirPath);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.9 ====================================
|
||||
static ensureDirectories(dirPaths: string[]): void {
|
||||
PathDiscovery.getInstance().ensureDirectories(dirPaths);
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.10 ===================================
|
||||
static extractProjectName(transcriptPath: string): string {
|
||||
return PathDiscovery.extractProjectName(transcriptPath);
|
||||
}
|
||||
|
||||
// <Block> 1.11 ===================================
|
||||
/**
|
||||
* DRY utility function: Canonical source for getting the current project prefix
|
||||
* Replaces all instances of path.basename(process.cwd()) across the codebase
|
||||
* @returns The current project directory name, sanitized for use as a prefix
|
||||
*/
|
||||
static getCurrentProjectPrefix(): string {
|
||||
return PathDiscovery.getCurrentProjectName();
|
||||
}
|
||||
// </Block> =======================================
|
||||
|
||||
// <Block> 1.12 ===================================
|
||||
/**
|
||||
* DRY utility function: Gets raw project name without sanitization
|
||||
* For use in contexts where original directory name is needed (e.g., display)
|
||||
* @returns The current project directory name as-is
|
||||
*/
|
||||
static getCurrentProjectName(): string {
|
||||
return PathDiscovery.getCurrentProjectName();
|
||||
}
|
||||
// </Block> =======================================
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { PathResolver } from './paths.js';
|
||||
import type { Settings } from './types.js';
|
||||
|
||||
/**
|
||||
* Settings utilities for managing ~/.claude-mem/settings.json
|
||||
*/
|
||||
export class SettingsManager {
|
||||
private static settingsPath: string;
|
||||
private static cachedSettings: Settings | null = null;
|
||||
|
||||
static {
|
||||
const pathResolver = new PathResolver();
|
||||
this.settingsPath = join(pathResolver.getConfigDir(), 'settings.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely read settings.json with error handling
|
||||
* Returns empty object if file doesn't exist or is malformed
|
||||
*/
|
||||
static readSettings(): Settings {
|
||||
// Return cached settings if available
|
||||
if (this.cachedSettings !== null) {
|
||||
return this.cachedSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync(this.settingsPath)) {
|
||||
const content = readFileSync(this.settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content) as Settings;
|
||||
this.cachedSettings = settings;
|
||||
return settings;
|
||||
}
|
||||
} catch {
|
||||
// File is malformed or unreadable - return empty settings
|
||||
}
|
||||
|
||||
// File doesn't exist or failed to read
|
||||
const emptySettings: Settings = {};
|
||||
this.cachedSettings = emptySettings;
|
||||
return emptySettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific setting value with optional fallback
|
||||
*/
|
||||
static getSetting<K extends keyof Settings>(
|
||||
key: K,
|
||||
fallback?: Settings[K]
|
||||
): Settings[K] | undefined {
|
||||
const settings = this.readSettings();
|
||||
return settings[key] ?? fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Claude binary path from settings
|
||||
* Falls back to 'claude' if not found or settings don't exist
|
||||
*/
|
||||
static getClaudePath(): string {
|
||||
const claudePath = this.getSetting('claudePath', 'claude');
|
||||
return claudePath as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached settings (useful for testing or after settings changes)
|
||||
*/
|
||||
static clearCache(): void {
|
||||
this.cachedSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get Claude binary path
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function getClaudePath(): string {
|
||||
return SettingsManager.getClaudePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to read all settings
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function readSettings(): Settings {
|
||||
return SettingsManager.readSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get a specific setting
|
||||
* Can be imported directly for simple use cases
|
||||
*/
|
||||
export function getSetting<K extends keyof Settings>(
|
||||
key: K,
|
||||
fallback?: Settings[K]
|
||||
): Settings[K] | undefined {
|
||||
return SettingsManager.getSetting(key, fallback);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
export interface HookPayload {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
hook_event_name: string;
|
||||
}
|
||||
|
||||
export interface PreCompactPayload extends HookPayload {
|
||||
hook_event_name: 'PreCompact';
|
||||
trigger: 'manual' | 'auto';
|
||||
custom_instructions?: string;
|
||||
}
|
||||
|
||||
export interface SessionStartPayload extends HookPayload {
|
||||
hook_event_name: 'SessionStart';
|
||||
source: 'startup' | 'compact' | 'vscode' | 'web';
|
||||
}
|
||||
|
||||
export interface UserPromptSubmitPayload extends HookPayload {
|
||||
hook_event_name: 'UserPromptSubmit';
|
||||
prompt: string;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
export interface PreToolUsePayload extends HookPayload {
|
||||
hook_event_name: 'PreToolUse';
|
||||
tool_name: string;
|
||||
tool_input: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface PostToolUsePayload extends HookPayload {
|
||||
hook_event_name: 'PostToolUse';
|
||||
tool_name: string;
|
||||
tool_input: Record<string, unknown>;
|
||||
tool_response: Record<string, unknown> & {
|
||||
success?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotificationPayload extends HookPayload {
|
||||
hook_event_name: 'Notification';
|
||||
message: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface StopPayload extends HookPayload {
|
||||
hook_event_name: 'Stop';
|
||||
stop_hook_active: boolean;
|
||||
}
|
||||
|
||||
export interface BaseHookResponse {
|
||||
continue?: boolean;
|
||||
stopReason?: string;
|
||||
suppressOutput?: boolean;
|
||||
}
|
||||
|
||||
export interface PreCompactResponse extends BaseHookResponse {
|
||||
decision?: 'approve' | 'block';
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface SessionStartResponse extends BaseHookResponse {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'SessionStart';
|
||||
additionalContext?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PreToolUseResponse extends BaseHookResponse {
|
||||
permissionDecision?: 'allow' | 'deny' | 'ask';
|
||||
permissionDecisionReason?: string;
|
||||
}
|
||||
|
||||
export interface CompressionResult {
|
||||
compressedLines: string[];
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
compressionRatio: number;
|
||||
memoryNodes: string[];
|
||||
}
|
||||
|
||||
export interface MemoryNode {
|
||||
id: string;
|
||||
type: 'document';
|
||||
content: string;
|
||||
timestamp: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class HookError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public hookType: string,
|
||||
public payload?: HookPayload,
|
||||
public code?: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'HookError';
|
||||
}
|
||||
}
|
||||
|
||||
export class CompressionError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public transcriptPath: string,
|
||||
public stage: 'reading' | 'analyzing' | 'compressing' | 'writing'
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'CompressionError';
|
||||
}
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
info(message: string, meta?: Record<string, unknown>): void;
|
||||
warn(message: string, meta?: Record<string, unknown>): void;
|
||||
error(message: string, error?: Error, meta?: Record<string, unknown>): void;
|
||||
debug(message: string, meta?: Record<string, unknown>): void;
|
||||
}
|
||||
|
||||
export class FileLogger implements Logger {
|
||||
constructor(
|
||||
private logFile: string,
|
||||
private enableDebug = false
|
||||
) {}
|
||||
|
||||
info(message: string, meta?: Record<string, unknown>): void {
|
||||
this.log('INFO', message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: Record<string, unknown>): void {
|
||||
this.log('WARN', message, meta);
|
||||
}
|
||||
|
||||
error(message: string, error?: Error, meta?: Record<string, unknown>): void {
|
||||
const errorMeta = error ? { error: error.message, stack: error.stack } : {};
|
||||
this.log('ERROR', message, { ...meta, ...errorMeta });
|
||||
}
|
||||
|
||||
debug(message: string, meta?: Record<string, unknown>): void {
|
||||
if (this.enableDebug) {
|
||||
this.log('DEBUG', message, meta);
|
||||
}
|
||||
}
|
||||
|
||||
private log(
|
||||
level: string,
|
||||
message: string,
|
||||
meta?: Record<string, unknown>
|
||||
): void {
|
||||
const timestamp = new Date().toISOString();
|
||||
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
|
||||
const logLine = `[${timestamp}] ${level}: ${message}${metaStr}\n`;
|
||||
|
||||
console.error(logLine);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateHookPayload(
|
||||
payload: unknown,
|
||||
expectedType: string
|
||||
): HookPayload {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
throw new HookError(
|
||||
`Invalid payload: expected object, got ${typeof payload}`,
|
||||
expectedType
|
||||
);
|
||||
}
|
||||
|
||||
const hookPayload = payload as Record<string, unknown>;
|
||||
|
||||
if (!hookPayload.session_id || typeof hookPayload.session_id !== 'string') {
|
||||
throw new HookError(
|
||||
'Missing or invalid session_id',
|
||||
expectedType,
|
||||
hookPayload as unknown as HookPayload
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!hookPayload.transcript_path ||
|
||||
typeof hookPayload.transcript_path !== 'string'
|
||||
) {
|
||||
throw new HookError(
|
||||
'Missing or invalid transcript_path',
|
||||
expectedType,
|
||||
hookPayload as unknown as HookPayload
|
||||
);
|
||||
}
|
||||
|
||||
return hookPayload as unknown as HookPayload;
|
||||
}
|
||||
|
||||
export function createSuccessResponse(
|
||||
additionalData?: Record<string, unknown>
|
||||
): BaseHookResponse {
|
||||
return {
|
||||
continue: true,
|
||||
...additionalData,
|
||||
};
|
||||
}
|
||||
|
||||
export function createErrorResponse(
|
||||
reason: string,
|
||||
additionalData?: Record<string, unknown>
|
||||
): BaseHookResponse {
|
||||
return {
|
||||
continue: false,
|
||||
stopReason: reason,
|
||||
...additionalData,
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SETTINGS AND CONFIGURATION TYPES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Main settings interface for claude-mem configuration
|
||||
*/
|
||||
export interface Settings {
|
||||
autoCompress?: boolean;
|
||||
projectName?: string;
|
||||
installed?: boolean;
|
||||
backend?: string;
|
||||
embedded?: boolean;
|
||||
saveMemoriesOnClear?: boolean;
|
||||
claudePath?: string;
|
||||
[key: string]: unknown; // Allow additional properties
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MCP CLIENT INTERFACE TYPES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Document structure for MCP operations
|
||||
*/
|
||||
export interface MCPDocument {
|
||||
id: string;
|
||||
content: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search result structure from MCP operations
|
||||
*/
|
||||
export interface MCPSearchResult {
|
||||
documents?: MCPDocument[];
|
||||
ids?: string[];
|
||||
metadatas?: Record<string, unknown>[];
|
||||
distances?: number[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for MCP client implementations (Chroma-based)
|
||||
*/
|
||||
export interface IMCPClient {
|
||||
connect(): Promise<void>;
|
||||
disconnect(): Promise<void>;
|
||||
addDocuments(documents: MCPDocument[]): Promise<void>;
|
||||
queryDocuments(query: string, limit?: number): Promise<MCPSearchResult>;
|
||||
getDocuments(ids?: string[]): Promise<MCPSearchResult>;
|
||||
}
|
||||
Reference in New Issue
Block a user