import { homedir } from 'os';
import path from 'path';
import fs from 'fs-extra';
import { fileURLToPath } from 'url';
import { DEFAULT_CONFIG } from './defaults.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Get the default config file path using XDG standard
* ~/.config/mcp-obsidian/config.json on Unix-like systems
* %APPDATA%/mcp-obsidian/config.json on Windows
*/
export function getDefaultConfigPath(): string {
const homeDir = homedir();
if (process.platform === 'win32') {
return path.join(process.env.APPDATA || homeDir, 'mcp-obsidian', 'config.json');
}
return path.join(homeDir, '.config', 'mcp-obsidian', 'config.json');
}
/**
* Check if a directory is a valid Obsidian vault
*/
async function isValidVault(vaultPath: string): Promise<boolean> {
try {
const obsidianDir = path.join(vaultPath, '.obsidian');
const stats = await fs.stat(obsidianDir);
return stats.isDirectory();
} catch {
return false;
}
}
/**
* Discover Obsidian vaults in common locations
*/
async function discoverVaults(): Promise<{ vaults: string[]; basePaths: string[] }> {
const homeDir = homedir();
const discoveredVaults: string[] = [];
const basePaths = new Set<string>();
// Common locations to search for Obsidian vaults
const searchLocations: string[] = [];
if (process.platform === 'darwin') {
// macOS locations
searchLocations.push(
path.join(homeDir, 'Documents', 'Obsidian'),
path.join(homeDir, 'Dropbox'),
path.join(homeDir, 'Library', 'CloudStorage', 'Dropbox'),
path.join(homeDir, 'Library', 'Mobile Documents', 'iCloud~md~obsidian'),
path.join(homeDir, 'Library', 'CloudStorage', 'Dropbox', 'Aplicaciones', 'Obsidian'),
path.join(homeDir, 'Library', 'CloudStorage', 'Dropbox', 'Obsidian'),
);
} else if (process.platform === 'win32') {
// Windows locations
const documents = process.env.USERPROFILE
? path.join(process.env.USERPROFILE, 'Documents')
: path.join(homeDir, 'Documents');
searchLocations.push(
path.join(documents, 'Obsidian'),
path.join(homeDir, 'Dropbox'),
path.join(homeDir, 'OneDrive', 'Documents', 'Obsidian'),
);
} else {
// Linux locations
searchLocations.push(
path.join(homeDir, 'Documents', 'Obsidian'),
path.join(homeDir, 'Dropbox'),
path.join(homeDir, '.local', 'share', 'Obsidian'),
);
}
// Search each location
for (const location of searchLocations) {
try {
if (!(await fs.pathExists(location))) {
continue;
}
const stats = await fs.stat(location);
if (!stats.isDirectory()) {
continue;
}
// Check if the location itself is a vault
if (await isValidVault(location)) {
discoveredVaults.push(location);
basePaths.add(path.dirname(location));
continue;
}
// Check subdirectories for vaults
const entries = await fs.readdir(location, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const vaultPath = path.join(location, entry.name);
if (await isValidVault(vaultPath)) {
discoveredVaults.push(vaultPath);
basePaths.add(location);
}
}
}
} catch (error) {
// Silently skip locations that don't exist or can't be accessed
}
}
return {
vaults: discoveredVaults,
basePaths: Array.from(basePaths),
};
}
/**
* Auto-detect and update config with discovered vault paths
*/
async function autoDetectAndUpdateConfig(config: any): Promise<any> {
const discovered = await discoverVaults();
if (discovered.vaults.length === 0) {
return config; // No vaults found, return as-is
}
// Update defaultVaultPath with the first discovered base path
if (discovered.basePaths.length > 0) {
config.defaultVaultPath = discovered.basePaths[0];
}
// Update allowedVaultPaths with all discovered base paths
// Remove placeholder paths and add real ones
const realPaths = discovered.basePaths.map(p => path.resolve(p));
config.security = config.security || {};
config.security.allowedVaultPaths = realPaths;
// Log what was discovered
console.error(`[AUTO-DETECT] Found ${discovered.vaults.length} vault(s) in ${discovered.basePaths.length} location(s)`);
discovered.basePaths.forEach(p => {
console.error(`[AUTO-DETECT] Added to allowed paths: ${p}`);
});
return config;
}
/**
* Check if config has placeholder paths that need to be replaced
*/
function hasPlaceholderPaths(config: any): boolean {
if (!config) return false;
const defaultPath = config.defaultVaultPath || '';
const allowedPaths = config.security?.allowedVaultPaths || [];
return (
defaultPath.includes('/yourname/') ||
allowedPaths.some((p: string) => typeof p === 'string' && p.includes('/yourname/'))
);
}
/**
* Ensure config file exists, creating it from example if available, otherwise from defaults
* Auto-detects Obsidian vaults and updates config with real paths
*/
export async function ensureConfigExists(configPath: string): Promise<void> {
// If config already exists, check if it needs updating
let configToWrite: any;
let needsUpdate = false;
if (await fs.pathExists(configPath)) {
try {
const existing = JSON.parse(await fs.readFile(configPath, 'utf-8'));
// Check if config has placeholder paths that need to be replaced
if (hasPlaceholderPaths(existing)) {
configToWrite = existing;
needsUpdate = true;
} else {
// Config exists and looks good, nothing to do
return;
}
} catch (error) {
// Config file is corrupted, recreate it
needsUpdate = true;
}
}
// Ensure directory exists
await fs.ensureDir(path.dirname(configPath));
// If we don't have a config yet, load from example or defaults
if (!configToWrite) {
// Try to find config.example.json relative to the package root
const possibleExamplePaths = [
// If running from source
path.join(__dirname, '../../config.example.json'),
// If installed via npm, try to find it in node_modules
path.join(__dirname, '../../../config.example.json'),
// Try current working directory (for development)
path.join(process.cwd(), 'config.example.json'),
];
let exampleConfig: any = null;
for (const examplePath of possibleExamplePaths) {
if (await fs.pathExists(examplePath)) {
try {
const content = await fs.readFile(examplePath, 'utf-8');
exampleConfig = JSON.parse(content);
break;
} catch (error) {
// Continue to next path
}
}
}
// Use example config if found, otherwise use defaults
configToWrite = exampleConfig || { ...DEFAULT_CONFIG };
}
// Auto-detect vaults and update config
configToWrite = await autoDetectAndUpdateConfig(configToWrite);
// Write config file
await fs.writeFile(
configPath,
JSON.stringify(configToWrite, null, 2) + '\n',
'utf-8'
);
if (needsUpdate) {
console.error(`Updated config file at ${configPath} with auto-detected vault paths`);
} else {
console.error(`Created config file at ${configPath} with auto-detected vault paths`);
}
}