@endlessblink/like-i-said-v2
Version:
Task Management & Memory for Claude - Track tasks, remember context, and maintain continuity across sessions with 27 powerful tools. Works with Claude Desktop and Claude Code.
416 lines (358 loc) • 13.9 kB
JavaScript
/**
* Dashboard Configuration System
* Manages persistent settings for the Like-I-Said dashboard launcher
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
class DashboardConfig {
constructor() {
this.configFile = path.join(process.cwd(), 'dashboard-config.json');
this.defaultConfig = {
memoryDirectory: path.join(process.cwd(), 'memories'),
taskDirectory: path.join(process.cwd(), 'tasks'),
autoOpenBrowser: true,
preferredPort: 3001,
logLevel: 'info',
showStartupBanner: true,
createDirectories: true,
backupOnStartup: true,
version: '2.4.3'
};
this.config = this.loadConfig();
}
/**
* Load configuration from file or create with defaults
*/
loadConfig() {
try {
if (fs.existsSync(this.configFile)) {
const configData = JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
// Merge with defaults to ensure all required fields exist
return { ...this.defaultConfig, ...configData };
}
} catch (error) {
console.warn(`Warning: Could not load config file: ${error.message}`);
}
// Return defaults if file doesn't exist or is corrupted
return { ...this.defaultConfig };
}
/**
* Save configuration to file
*/
saveConfig() {
try {
const configDir = path.dirname(this.configFile);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 2), 'utf8');
return true;
} catch (error) {
console.error(`Error saving config: ${error.message}`);
return false;
}
}
/**
* Get configuration value
*/
get(key) {
return this.config[key];
}
/**
* Set configuration value with validation
*/
set(key, value) {
// Validate specific keys
if (key === 'preferredPort') {
const portNum = parseInt(value);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
console.warn(`Invalid port number: ${value}. Keeping current value.`);
return false;
}
value = portNum;
} else if (['autoOpenBrowser', 'showStartupBanner', 'createDirectories', 'backupOnStartup'].includes(key)) {
if (typeof value === 'string') {
value = value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === 'yes';
}
} else if (['memoryDirectory', 'taskDirectory'].includes(key)) {
if (typeof value === 'string') {
value = path.resolve(value);
}
} else if (key === 'logLevel') {
const validLevels = ['debug', 'info', 'warn', 'error'];
if (!validLevels.includes(value)) {
console.warn(`Invalid log level: ${value}. Valid levels: ${validLevels.join(', ')}`);
return false;
}
}
this.config[key] = value;
return this.saveConfig();
}
/**
* Get all configuration
*/
getAll() {
return { ...this.config };
}
/**
* Reset configuration to defaults
*/
reset() {
this.config = { ...this.defaultConfig };
return this.saveConfig();
}
/**
* Validate and create directories
*/
validateDirectories() {
const results = {
memoryDirectory: this.validateDirectory(this.config.memoryDirectory),
taskDirectory: this.validateDirectory(this.config.taskDirectory)
};
return results;
}
/**
* Validate a single directory path
*/
validateDirectory(dirPath) {
const result = {
path: dirPath,
exists: false,
canCreate: false,
created: false,
writable: false,
error: null
};
try {
// Check if directory exists
if (fs.existsSync(dirPath)) {
result.exists = true;
// Check if writable
try {
fs.accessSync(dirPath, fs.constants.W_OK);
result.writable = true;
} catch (writeError) {
result.error = `Directory exists but is not writable: ${writeError.message}`;
}
} else {
// Try to create directory
if (this.config.createDirectories) {
try {
fs.mkdirSync(dirPath, { recursive: true });
result.created = true;
result.exists = true;
result.writable = true;
result.canCreate = true;
} catch (createError) {
result.error = `Cannot create directory: ${createError.message}`;
}
} else {
result.canCreate = this.canCreateDirectory(dirPath);
result.error = 'Directory does not exist and auto-creation is disabled';
}
}
} catch (error) {
result.error = `Path validation error: ${error.message}`;
}
return result;
}
/**
* Check if directory can be created
*/
canCreateDirectory(dirPath) {
try {
const parentDir = path.dirname(dirPath);
return fs.existsSync(parentDir) && fs.accessSync(parentDir, fs.constants.W_OK) === undefined;
} catch (error) {
return false;
}
}
/**
* Get config file path
*/
getConfigPath() {
return this.configFile;
}
/**
* Check if configuration file exists
*/
configExists() {
return fs.existsSync(this.configFile);
}
/**
* Display current configuration
*/
displayConfig() {
console.log('\n╔══════════════════════════════════════════╗');
console.log('║ Current Configuration ║');
console.log('╚══════════════════════════════════════════╝');
console.log(`\nMemory Directory: ${this.config.memoryDirectory}`);
console.log(`Task Directory: ${this.config.taskDirectory}`);
console.log(`Auto-open Browser: ${this.config.autoOpenBrowser ? 'Yes' : 'No'}`);
console.log(`Preferred Port: ${this.config.preferredPort}`);
console.log(`Log Level: ${this.config.logLevel}`);
console.log(`Show Startup Banner: ${this.config.showStartupBanner ? 'Yes' : 'No'}`);
console.log(`Create Directories: ${this.config.createDirectories ? 'Yes' : 'No'}`);
console.log(`Backup on Startup: ${this.config.backupOnStartup ? 'Yes' : 'No'}`);
console.log(`Config File: ${this.configFile}`);
// Validate directories and show status
const validation = this.validateDirectories();
console.log('\nDirectory Status:');
for (const [key, result] of Object.entries(validation)) {
const label = key === 'memoryDirectory' ? 'Memory' : 'Task';
const status = result.exists ?
(result.writable ? '✓ OK' : '⚠ Not writable') :
(result.canCreate ? '⚠ Will create' : '✗ Cannot create');
console.log(` ${label}: ${status}`);
if (result.error) {
console.log(` Error: ${result.error}`);
}
}
console.log('');
}
/**
* Interactive configuration CLI
*/
async runConfigWizard() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (prompt) => new Promise((resolve) => {
rl.question(prompt, resolve);
});
try {
console.log('\n╔══════════════════════════════════════════╗');
console.log('║ Configuration Wizard ║');
console.log('╚══════════════════════════════════════════╝');
this.displayConfig();
const shouldConfigure = await question('Do you want to change these settings? (y/N): ');
if (shouldConfigure.toLowerCase() === 'y' || shouldConfigure.toLowerCase() === 'yes') {
// Memory directory
const memoryDir = await question(`Memory directory [${this.config.memoryDirectory}]: `);
if (memoryDir.trim()) {
this.config.memoryDirectory = path.resolve(memoryDir.trim());
}
// Task directory
const taskDir = await question(`Task directory [${this.config.taskDirectory}]: `);
if (taskDir.trim()) {
this.config.taskDirectory = path.resolve(taskDir.trim());
}
// Auto-open browser
const autoOpen = await question(`Auto-open browser? (Y/n) [${this.config.autoOpenBrowser ? 'Y' : 'n'}]: `);
if (autoOpen.trim()) {
this.config.autoOpenBrowser = autoOpen.toLowerCase() !== 'n' && autoOpen.toLowerCase() !== 'no';
}
// Preferred port
const port = await question(`Preferred port [${this.config.preferredPort}]: `);
if (port.trim()) {
const portNum = parseInt(port.trim());
if (!isNaN(portNum) && portNum > 0 && portNum < 65536) {
this.config.preferredPort = portNum;
} else {
console.log('Invalid port number, keeping current value.');
}
}
// Create directories
const createDirs = await question(`Auto-create directories? (Y/n) [${this.config.createDirectories ? 'Y' : 'n'}]: `);
if (createDirs.trim()) {
this.config.createDirectories = createDirs.toLowerCase() !== 'n' && createDirs.toLowerCase() !== 'no';
}
// Log level
const logLevel = await question(`Log level (debug/info/warn/error) [${this.config.logLevel}]: `);
if (logLevel.trim() && ['debug', 'info', 'warn', 'error'].includes(logLevel.trim().toLowerCase())) {
this.config.logLevel = logLevel.trim().toLowerCase();
}
// Save configuration
if (this.saveConfig()) {
console.log('\n✓ Configuration saved successfully!');
// Validate directories after configuration
console.log('\nValidating directories...');
const validation = this.validateDirectories();
let hasErrors = false;
for (const [key, result] of Object.entries(validation)) {
const label = key === 'memoryDirectory' ? 'Memory' : 'Task';
if (result.error) {
console.log(`✗ ${label}: ${result.error}`);
hasErrors = true;
} else if (result.created) {
console.log(`✓ ${label}: Directory created successfully`);
} else if (result.exists) {
console.log(`✓ ${label}: Directory exists and is writable`);
}
}
if (hasErrors) {
console.log('\n⚠ Some directories have issues. Please fix them before starting the dashboard.');
} else {
console.log('\n✓ All directories are ready!');
}
} else {
console.log('\n✗ Failed to save configuration.');
}
} else {
console.log('\nConfiguration unchanged.');
}
} catch (error) {
console.error(`Configuration error: ${error.message}`);
} finally {
rl.close();
}
}
/**
* Quick setup for first-time users
*/
async quickSetup() {
console.log('\n╔══════════════════════════════════════════╗');
console.log('║ Quick Setup ║');
console.log('╚══════════════════════════════════════════╝');
console.log('\nSetting up Like-I-Said dashboard with default configuration...');
// Validate and create directories
const validation = this.validateDirectories();
let setupSuccess = true;
for (const [key, result] of Object.entries(validation)) {
const label = key === 'memoryDirectory' ? 'Memory' : 'Task';
if (result.error) {
console.log(`✗ ${label}: ${result.error}`);
setupSuccess = false;
} else if (result.created) {
console.log(`✓ ${label}: Directory created at ${result.path}`);
} else if (result.exists) {
console.log(`✓ ${label}: Using existing directory at ${result.path}`);
}
}
if (setupSuccess) {
this.saveConfig();
console.log(`\n✓ Quick setup complete! Configuration saved to ${this.configFile}`);
console.log('\nYou can run the dashboard with: node dashboard-launcher-windows.cjs');
console.log('Or reconfigure anytime with: node dashboard-launcher-windows.cjs --config');
} else {
console.log('\n✗ Setup failed. Please fix the directory issues above.');
return false;
}
return true;
}
/**
* Get normalized paths for Windows compatibility
*/
getWindowsPaths() {
return {
memoryDirectory: this.config.memoryDirectory.replace(/\//g, '\\'),
taskDirectory: this.config.taskDirectory.replace(/\//g, '\\'),
configFile: this.configFile.replace(/\//g, '\\')
};
}
/**
* Export configuration for other modules
*/
exportConfig() {
return {
...this.config,
paths: this.getWindowsPaths(),
validation: this.validateDirectories()
};
}
}
module.exports = { DashboardConfig };