mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
207 lines • 8.61 kB
JavaScript
/**
* Cross-Platform Path Utilities
* =============================
*
* Centralized utilities for handling paths in a cross-platform manner.
* This module provides the single source of truth for all path operations
* to ensure MIRA works correctly on Windows, macOS, Linux, and containers.
*
* Key Features:
* - Intelligent Claude conversation path discovery
* - Cross-platform temp directory handling
* - Safe path joining and normalization
* - Platform-specific path detection and conversion
*/
import * as path from 'path';
import * as os from 'os';
import fs from 'fs-extra';
/**
* Get Claude conversation paths to search in order of likelihood
* This matches the Python claude_path_discovery.py logic
*/
export function getClaudeConversationSearchPaths() {
const homeDir = os.homedir();
const currentDir = process.cwd();
const searchPaths = [
// TIER 1: Standard user locations (90% of installations)
path.join(homeDir, '.claude', 'projects'),
path.join(homeDir, '.claude-code', 'projects'),
// TIER 2: Development environments (common in professional setups)
// Use path.join instead of hardcoded paths for cross-platform compatibility
...(process.platform !== 'win32' ? [
path.join(path.sep, 'home', 'codespace', '.claude', 'projects'),
path.join(path.sep, 'home', 'codespace', '.claude-code', 'projects'),
path.join(path.sep, 'home', 'vscode', '.claude', 'projects'),
path.join(path.sep, 'home', 'gitpod', '.claude', 'projects'),
] : []),
// TIER 3: Current workspace variations (local development)
path.join(currentDir, '.claude', 'projects'),
path.join(currentDir, '..', '.claude', 'projects'),
path.join(currentDir, 'claude', 'projects'),
// TIER 4: Platform-specific standard locations
// Windows
...(process.platform === 'win32' ? [
path.join(homeDir, 'AppData', 'Local', 'Claude', 'projects'),
path.join(homeDir, 'AppData', 'Local', 'claude-code', 'projects'),
path.join(homeDir, 'AppData', 'Roaming', 'Claude', 'projects'),
] : []),
// macOS
...(process.platform === 'darwin' ? [
path.join(homeDir, 'Library', 'Application Support', 'Claude', 'projects'),
path.join(homeDir, 'Library', 'Application Support', 'claude-code', 'projects'),
path.join(homeDir, 'Library', 'Caches', 'Claude', 'projects'),
] : []),
// Linux standard directories
...(process.platform === 'linux' ? [
path.join(homeDir, '.local', 'share', 'claude', 'projects'),
path.join(homeDir, '.config', 'claude', 'projects'),
path.join(homeDir, '.cache', 'claude', 'projects'),
path.join(homeDir, 'snap', 'claude', 'common', 'projects'),
] : []),
// TIER 5: Container and virtualization environments
...(process.platform !== 'win32' ? [
path.join(path.sep, 'workspace', '.claude', 'projects'),
path.join(path.sep, 'app', '.claude', 'projects'),
path.join(path.sep, 'opt', '.claude', 'projects'),
path.join(path.sep, 'usr', 'local', 'share', 'claude', 'projects'),
path.join(path.sep, 'var', 'lib', 'claude', 'projects'),
] : []),
// TIER 6: WSL (Windows Subsystem for Linux) paths
...(process.platform === 'win32' || fs.existsSync('/mnt/c') ? [
path.join(path.sep, 'mnt', 'c', 'Users', process.env.USERNAME || 'user', '.claude', 'projects'),
path.join(path.sep, 'mnt', 'c', 'Users', process.env.USER || 'user', '.claude', 'projects'),
] : []),
// TIER 7: Alternative environment variables and dynamic paths
...(process.env.CLAUDE_HOME ? [path.join(process.env.CLAUDE_HOME, 'projects')] : []),
...(process.env.CLAUDE_DATA_DIR ? [path.join(process.env.CLAUDE_DATA_DIR, 'projects')] : []),
...(process.env.XDG_DATA_HOME ? [path.join(process.env.XDG_DATA_HOME, 'claude', 'projects')] : []),
...(process.env.XDG_CONFIG_HOME ? [path.join(process.env.XDG_CONFIG_HOME, 'claude', 'projects')] : []),
];
// Use temp directory as last resort (cross-platform)
searchPaths.push(path.join(os.tmpdir(), '.claude', 'projects'));
// Remove duplicates and return
return [...new Set(searchPaths)];
}
/**
* Validate that a path contains Claude conversation files
*/
export async function validateClaudeConversationPath(searchPath) {
try {
if (!await fs.pathExists(searchPath)) {
return false;
}
const stats = await fs.stat(searchPath);
if (!stats.isDirectory()) {
return false;
}
// Check for project directories containing .jsonl files
const entries = await fs.readdir(searchPath);
for (const entry of entries) {
const projectPath = path.join(searchPath, entry);
const projectStats = await fs.stat(projectPath);
if (projectStats.isDirectory()) {
const projectFiles = await fs.readdir(projectPath);
const hasJsonl = projectFiles.some(file => file.endsWith('.jsonl'));
if (hasJsonl) {
// Try to validate at least one conversation file
const jsonlFile = projectFiles.find(f => f.endsWith('.jsonl'));
if (jsonlFile) {
const filePath = path.join(projectPath, jsonlFile);
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n').filter(l => l.trim());
// Check first few lines for Claude conversation format
for (let i = 0; i < Math.min(5, lines.length); i++) {
try {
const data = JSON.parse(lines[i]);
if (data.message &&
(data.message.role === 'user' || data.message.role === 'assistant')) {
return true; // Valid Claude conversation found
}
}
catch {
// Continue checking other lines
}
}
}
}
}
}
return false;
}
catch (error) {
return false;
}
}
/**
* Get cross-platform temp directory
* Handles Windows vs Unix differences
*/
export function getCrossPlatformTempDir() {
return os.tmpdir();
}
/**
* Convert Windows-style paths to cross-platform paths
*/
export function normalizePath(inputPath) {
// Convert backslashes to forward slashes
let normalized = inputPath.replace(/\\/g, path.sep);
// Handle Windows drive letters
if (process.platform !== 'win32' && /^[A-Za-z]:/.test(normalized)) {
// Convert C:\path to /mnt/c/path for WSL
const driveLetter = normalized[0].toLowerCase();
normalized = `/mnt/${driveLetter}${normalized.substring(2)}`;
}
return path.normalize(normalized);
}
/**
* Check if a path is absolute in a cross-platform way
*/
export function isAbsolutePath(inputPath) {
return path.isAbsolute(inputPath) ||
/^[A-Za-z]:/.test(inputPath) || // Windows drive letter
inputPath.startsWith('/'); // Unix absolute
}
/**
* Join paths safely across platforms
*/
export function safejoin(...paths) {
return path.join(...paths);
}
/**
* Get platform-specific null device
*/
export function getNullDevice() {
return process.platform === 'win32' ? 'nul' : '/dev/null';
}
/**
* Get home directory with fallback
*/
export function getHomeDirectory() {
return os.homedir() || process.env.HOME || process.env.USERPROFILE || '.';
}
/**
* Check if running in WSL
*/
export function isWSL() {
if (process.platform !== 'linux')
return false;
try {
return fs.existsSync('/proc/version') &&
fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
}
catch {
return false;
}
}
/**
* Get platform name for logging
*/
export function getPlatformName() {
switch (process.platform) {
case 'win32': return 'Windows';
case 'darwin': return 'macOS';
case 'linux': return isWSL() ? 'WSL' : 'Linux';
default: return process.platform;
}
}
//# sourceMappingURL=crossPlatformPaths.js.map