// --------------------------------------------------------------------------- // Event System // --------------------------------------------------------------------------- export type EventCategory = | 'file' | 'content' | 'navigation' | 'editor' | 'vault' | 'plugin' | 'system'; export type EventType = // File events | 'file:create' | 'file:delete' | 'file:rename' | 'file:move' | 'file:modify' // Content events (semantic diffs) | 'content:words-changed' | 'content:link-added' | 'content:link-removed' | 'content:tag-added' | 'content:tag-removed' | 'content:frontmatter-changed' | 'content:heading-changed' | 'content:embed-added' | 'content:embed-removed' // Navigation events | 'nav:file-open' | 'nav:file-close' | 'nav:active-leaf-change' // Editor events | 'editor:change' | 'editor:selection' | 'editor:paste' // Vault structure events | 'vault:folder-create' | 'vault:folder-delete' | 'vault:folder-rename' // System events | 'system:session-start' | 'system:session-end' | 'system:baseline-scan' | 'system:rescan' | 'system:maintenance' // Plugin events | 'plugin:command-executed' // Custom events (from other plugins via API) | `custom:${string}`; export interface LogfireEvent { id: string; timestamp: number; type: EventType; category: EventCategory; source: string; target?: string; payload: Record; session: string; } // --------------------------------------------------------------------------- // Category mapping // --------------------------------------------------------------------------- const CATEGORY_MAP: Record = { file: 'file', content: 'content', nav: 'navigation', editor: 'editor', vault: 'vault', plugin: 'plugin', system: 'system', custom: 'plugin', }; export function categoryForType(type: EventType): EventCategory { const prefix = type.split(':')[0]; return CATEGORY_MAP[prefix] ?? 'system'; } // --------------------------------------------------------------------------- // Content Analysis // --------------------------------------------------------------------------- export interface ContentSnapshot { path: string; wordCount: number; charCount: number; links: string[]; tags: string[]; headings: { level: number; text: string }[]; frontmatter: Record; embeds: string[]; } export interface ContentDelta { wordsAdded: number; wordsRemoved: number; linksAdded: string[]; linksRemoved: string[]; tagsAdded: string[]; tagsRemoved: string[]; headingsAdded: { level: number; text: string }[]; headingsRemoved: { level: number; text: string }[]; embedsAdded: string[]; embedsRemoved: string[]; frontmatterChanged: boolean; frontmatterChanges: Record; } // --------------------------------------------------------------------------- // Query System // --------------------------------------------------------------------------- export type TimeRange = | { type: 'relative'; value: 'today' | 'yesterday' | 'this-week' | 'this-month' | 'last-7-days' | 'last-30-days' } | { type: 'absolute'; from: number; to: number } | { type: 'session'; sessionId?: string }; export interface QueryConfig { timeRange: TimeRange; eventTypes?: EventType[]; categories?: EventCategory[]; filePaths?: string[]; groupBy?: 'file' | 'type' | 'hour' | 'day' | 'session'; orderBy?: 'timestamp' | 'count' | 'words'; orderDirection?: 'asc' | 'desc'; limit?: number; } // --------------------------------------------------------------------------- // Projections // --------------------------------------------------------------------------- export interface ProjectionTemplate { id: string; name: string; description: string; enabled: boolean; trigger: ProjectionTrigger; output: OutputConfig; sections: SectionConfig[]; } export interface ProjectionTrigger { type: 'interval' | 'on-session-end' | 'manual' | 'cron'; interval?: number; cron?: string; } export interface OutputConfig { folder: string; filePattern: string; mode: 'overwrite' | 'append'; frontmatter: Record; } export interface SectionConfig { id: string; heading: string; type: SectionType; query: QueryConfig; format: FormatConfig; enabled: boolean; } export type SectionType = 'timeline' | 'table' | 'summary' | 'chart-data' | 'raw-events' | 'custom'; export interface FormatConfig { type: 'builtin' | 'custom'; builtin?: BuiltinFormat; customTemplate?: string; } export type BuiltinFormat = | { name: 'timeline'; showTimestamp: boolean; showPayload: boolean } | { name: 'table'; columns: ColumnDef[] } | { name: 'summary'; metrics: MetricDef[] } | { name: 'metric'; aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max'; field?: string }; export interface ColumnDef { header: string; value: string; align?: 'left' | 'right' | 'center'; } export interface MetricDef { label: string; aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max'; field?: string; filter?: Partial; } // --------------------------------------------------------------------------- // Settings // --------------------------------------------------------------------------- export interface LogfireSettings { settingsVersion: number; general: { logFolder: string; enabled: boolean; pauseOnStartup: boolean; }; tracking: { fileEvents: boolean; contentAnalysis: boolean; navigation: boolean; editorChanges: boolean; commandTracking: boolean; excludedFolders: string[]; excludedPatterns: string[]; }; projections: { templates: ProjectionTemplate[]; }; advanced: { debounceMs: number; flushIntervalMs: number; flushThreshold: number; retention: RetentionSettings; database: DatabaseSettings; }; } export interface RetentionSettings { rawEventsDays: number; dailyStatsDays: number; monthlyStatsForever: boolean; neverDeleteTypes: EventType[]; maintenanceOnStartup: boolean; } export interface DatabaseSettings { cacheSizeMb: number; walMode: boolean; mmapSizeMb: number; } export const DEFAULT_SETTINGS: LogfireSettings = { settingsVersion: 1, general: { logFolder: 'Logfire', enabled: true, pauseOnStartup: false, }, tracking: { fileEvents: true, contentAnalysis: true, navigation: true, editorChanges: true, commandTracking: true, excludedFolders: ['.obsidian'], excludedPatterns: [], }, projections: { templates: [], }, advanced: { debounceMs: 3000, flushIntervalMs: 5000, flushThreshold: 100, retention: { rawEventsDays: 30, dailyStatsDays: 365, monthlyStatsForever: true, neverDeleteTypes: ['file:create', 'file:delete', 'file:rename'], maintenanceOnStartup: true, }, database: { cacheSizeMb: 64, walMode: true, mmapSizeMb: 256, }, }, }; // --------------------------------------------------------------------------- // API types // --------------------------------------------------------------------------- export interface VaultStats { eventsTotal: number; wordsAdded: number; wordsRemoved: number; filesCreated: number; filesModified: number; filesDeleted: number; sessionCount: number; activeTimeMs: number; } export interface FileStats { path: string; eventsTotal: number; wordsAdded: number; wordsRemoved: number; activeTimeMs: number; lastModified: number; editSessions: number; } // --------------------------------------------------------------------------- // Utilities // --------------------------------------------------------------------------- export function deepMerge(defaults: T, overrides: Partial): T { if ( typeof defaults !== 'object' || defaults === null || typeof overrides !== 'object' || overrides === null ) { return (overrides ?? defaults) as T; } const result = { ...defaults } as Record; const src = overrides as Record; const def = defaults as Record; for (const key of Object.keys(src)) { const defaultVal = def[key]; const overrideVal = src[key]; if ( defaultVal !== null && defaultVal !== undefined && overrideVal !== null && overrideVal !== undefined && typeof defaultVal === 'object' && !Array.isArray(defaultVal) && typeof overrideVal === 'object' && !Array.isArray(overrideVal) ) { result[key] = deepMerge(defaultVal, overrideVal); } else if (overrideVal !== undefined) { result[key] = overrideVal; } } return result as T; }