/** Config loader + TypeScript interfaces mirroring the Python config. */ import { readFileSync } from "fs"; import { resolve } from "path"; export interface EmbeddingConfig { provider: string; model: string; base_url: string; dimensions: number; batch_size: number; } export interface VectorStoreConfig { type: string; path: string; } export interface IndexingConfig { chunk_size: number; chunk_overlap: number; file_patterns: string[]; deny_dirs: string[]; allow_dirs: string[]; } export interface SecurityConfig { require_confirmation_for: string[]; sensitive_sections: string[]; local_only: boolean; } export interface MemoryPatterns { financial: string[]; health: string[]; commitments: string[]; } export interface MemoryConfig { auto_suggest: boolean; patterns: MemoryPatterns; } export interface ObsidianRagConfig { vault_path: string; embedding: EmbeddingConfig; vector_store: VectorStoreConfig; indexing: IndexingConfig; security: SecurityConfig; memory: MemoryConfig; } function defaults(): ObsidianRagConfig { return { vault_path: "./KnowledgeVault/Default", embedding: { provider: "ollama", model: "mxbai-embed-large", base_url: "http://localhost:11434", dimensions: 1024, batch_size: 64, }, vector_store: { type: "lancedb", path: "./obsidian-rag/vectors.lance", }, indexing: { chunk_size: 500, chunk_overlap: 100, file_patterns: ["*.md"], deny_dirs: [".obsidian", ".trash", "zzz-Archive", ".git", ".logseq"], allow_dirs: [], }, security: { require_confirmation_for: ["health", "financial_debt"], sensitive_sections: ["#mentalhealth", "#physicalhealth", "#Relations"], local_only: true, }, memory: { auto_suggest: true, patterns: { financial: ["owe", "owed", "debt", "paid", "$", "spent", "spend"], health: ["#mentalhealth", "#physicalhealth", "medication", "therapy"], commitments: ["shopping list", "costco", "amazon", "grocery"], }, }, }; } export function loadConfig(configPath?: string): ObsidianRagConfig { const defaultPath = resolve(process.cwd(), "obsidian-rag", "config.json"); const path = configPath ?? defaultPath; try { const raw = JSON.parse(readFileSync(path, "utf-8")); return deepMerge(defaults(), raw) as ObsidianRagConfig; } catch { return defaults(); } } function deepMerge(target: T, source: Partial): T { const out = { ...target }; for (const [key, val] of Object.entries(source)) { if (val && typeof val === "object" && !Array.isArray(val)) { (out as any)[key] = deepMerge((target as any)[key] ?? {}, val); } else if (val !== undefined) { (out as any)[key] = val; } } return out; }