diff --git a/install.mjs b/install.mjs index d88a895..c2dca37 100755 --- a/install.mjs +++ b/install.mjs @@ -4,38 +4,32 @@ * PaddedCell Plugin Installer * * Usage: - * node install.mjs - * node install.mjs --prefix /usr/local - * node install.mjs --build-only - * node install.mjs --skip-check + * node install.mjs --install * node install.mjs --uninstall - * node install.mjs --uninstall --prefix /usr/local */ -import { execSync } from 'child_process'; -import { existsSync, mkdirSync, copyFileSync, writeFileSync, chmodSync } from 'fs'; +import { execSync, spawnSync } from 'child_process'; +import { existsSync, mkdirSync, copyFileSync, writeFileSync, chmodSync, readdirSync, statSync } from 'fs'; import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; import { homedir, platform } from 'os'; -import readline from 'readline'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +// Plugin configuration +const PLUGIN_NAME = 'padded-cell'; +const PLUGIN_ENTRY = `plugins.entries.${PLUGIN_NAME}`; +const PLUGIN_ALLOW_PATH = 'plugins.allow'; +const PLUGINS_LOAD_PATHS = 'plugins.load.paths'; + // Parse arguments const args = process.argv.slice(2); -const options = { - prefix: null, - buildOnly: args.includes('--build-only'), - skipCheck: args.includes('--skip-check'), - verbose: args.includes('--verbose') || args.includes('-v'), - uninstall: args.includes('--uninstall'), -}; +const mode = args.includes('--uninstall') ? 'uninstall' : (args.includes('--install') ? 'install' : null); -// Parse --prefix value -const prefixIndex = args.indexOf('--prefix'); -if (prefixIndex !== -1 && args[prefixIndex + 1]) { - options.prefix = resolve(args[prefixIndex + 1]); +if (!mode) { + console.error('Usage: install.mjs --install | --uninstall'); + process.exit(2); } // Colors for output @@ -52,177 +46,122 @@ function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } -function logStep(step, message) { - log(`[${step}/6] ${message}`, 'cyan'); -} - function logSuccess(message) { - log(` ✓ ${message}`, 'green'); + log(`[${PLUGIN_NAME}] ✓ ${message}`, 'green'); } function logWarning(message) { - log(` ⚠ ${message}`, 'yellow'); + log(`[${PLUGIN_NAME}] ⚠ ${message}`, 'yellow'); } function logError(message) { - log(` ✗ ${message}`, 'red'); + log(`[${PLUGIN_NAME}] ✗ ${message}`, 'red'); } -function exec(command, options = {}) { - const defaultOptions = { - cwd: __dirname, - stdio: options.silent ? 'pipe' : 'inherit', - encoding: 'utf8', - }; - return execSync(command, { ...defaultOptions, ...options }); +function logInfo(message) { + log(`[${PLUGIN_NAME}] ${message}`, 'cyan'); } -async function prompt(question) { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - return new Promise((resolve) => { - rl.question(question, (answer) => { - rl.close(); - resolve(answer.trim()); - }); - }); +// Execute openclaw command +function runOpenclaw(args, { allowFail = false } = {}) { + try { + return execSync('openclaw', args, { encoding: 'utf8' }).trim(); + } catch (e) { + if (allowFail) return null; + throw e; + } +} + +// Get config value as JSON +function getJson(pathKey) { + const out = runOpenclaw(['config', 'get', pathKey, '--json'], { allowFail: true }); + if (out == null || out === '' || out === 'undefined') return undefined; + try { + return JSON.parse(out); + } catch { + return undefined; + } +} + +// Set config value as JSON +function setJson(pathKey, value) { + runOpenclaw(['config', 'set', pathKey, JSON.stringify(value), '--json']); +} + +// Unset config path +function unsetPath(pathKey) { + runOpenclaw(['config', 'unset', pathKey], { allowFail: true }); +} + +// Deep clone +function clone(v) { + if (v === undefined) return undefined; + return JSON.parse(JSON.stringify(v)); +} + +// Copy directory recursively +function copyDir(src, dest) { + mkdirSync(dest, { recursive: true }); + const entries = readdirSync(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = join(src, entry.name); + const destPath = join(dest, entry.name); + + if (entry.isDirectory()) { + copyDir(srcPath, destPath); + } else { + copyFileSync(srcPath, destPath); + } + } } // ============================================================================ -// Step 1: Environment Detection +// Detect Environment // ============================================================================ function detectEnvironment() { - logStep(1, 'Detecting environment...'); - const env = { - platform: platform(), - isLinux: platform() === 'linux', - isMacOS: platform() === 'darwin', + openclawDir: join(homedir(), '.openclaw'), nodeVersion: null, goVersion: null, - openclawPath: null, - openclawDir: null, }; // Check Node.js try { - const version = exec('node --version', { silent: true }).trim(); - env.nodeVersion = version; - logSuccess(`Node.js ${version}`); + env.nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim(); } catch { logError('Node.js not found'); } // Check Go try { - const version = exec('go version', { silent: true }).trim(); - env.goVersion = version; - logSuccess(`Go ${version}`); + env.goVersion = execSync('go version', { encoding: 'utf8' }).trim(); } catch { logError('Go not found'); } - // Check openclaw - try { - const path = exec('which openclaw', { silent: true }).trim(); - env.openclawPath = path; - logSuccess(`openclaw at ${path}`); - - // Try to find openclaw config dir - const home = homedir(); - const possibleDirs = [ - join(home, '.openclaw'), - join(home, '.config', 'openclaw'), - '/etc/openclaw', - ]; - - for (const dir of possibleDirs) { - if (existsSync(dir)) { - env.openclawDir = dir; - logSuccess(`openclaw config dir: ${dir}`); - break; - } - } - - if (!env.openclawDir) { - env.openclawDir = join(home, '.openclaw'); - logWarning(`openclaw config dir not found, will use: ${env.openclawDir}`); - } - } catch { - logWarning('openclaw CLI not found in PATH'); - env.openclawDir = join(homedir(), '.openclaw'); - } - return env; } -function checkDependencies(env) { - if (options.skipCheck) { - logWarning('Skipping dependency checks'); - return true; - } - - logStep(2, 'Checking dependencies...'); - - let hasErrors = false; - - if (!env.nodeVersion) { - logError('Node.js is required. Please install Node.js 18+'); - hasErrors = true; - } else { - const majorVersion = parseInt(env.nodeVersion.slice(1).split('.')[0]); - if (majorVersion < 18) { - logError(`Node.js 18+ required, found ${env.nodeVersion}`); - hasErrors = true; - } else { - logSuccess(`Node.js version OK`); - } - } - - if (!env.goVersion) { - logError('Go is required. Please install Go 1.22+'); - hasErrors = true; - } else { - logSuccess(`Go version OK`); - } - - if (hasErrors) { - log('\nPlease install missing dependencies and try again.', 'red'); - process.exit(1); - } - - return true; -} - // ============================================================================ -// Step 3: Build Components +// Build Components // ============================================================================ -async function buildComponents(env) { - logStep(3, 'Building components...'); +function buildComponents(env) { + logInfo('Building components...'); // Build pass_mgr log(' Building pass_mgr (Go)...', 'blue'); try { const passMgrDir = join(__dirname, 'pass_mgr'); - if (!existsSync(passMgrDir)) { - throw new Error('pass_mgr directory not found'); - } - - exec('go mod tidy', { cwd: passMgrDir, silent: !options.verbose }); - exec('go build -o dist/pass_mgr src/main.go', { - cwd: passMgrDir, - silent: !options.verbose - }); + execSync('go mod tidy', { cwd: passMgrDir, stdio: 'inherit' }); + execSync('go build -o dist/pass_mgr src/main.go', { cwd: passMgrDir, stdio: 'inherit' }); const binaryPath = join(passMgrDir, 'dist', 'pass_mgr'); if (!existsSync(binaryPath)) { throw new Error('pass_mgr binary not found after build'); } - chmodSync(binaryPath, 0o755); logSuccess('pass_mgr built successfully'); } catch (err) { @@ -234,13 +173,8 @@ async function buildComponents(env) { log(' Building pcexec (TypeScript)...', 'blue'); try { const pcexecDir = join(__dirname, 'pcexec'); - if (!existsSync(pcexecDir)) { - throw new Error('pcexec directory not found'); - } - - exec('npm install', { cwd: pcexecDir, silent: !options.verbose }); - exec('npm run build', { cwd: pcexecDir, silent: !options.verbose }); - + execSync('npm install', { cwd: pcexecDir, stdio: 'inherit' }); + execSync('npm run build', { cwd: pcexecDir, stdio: 'inherit' }); logSuccess('pcexec built successfully'); } catch (err) { logError(`Failed to build pcexec: ${err.message}`); @@ -251,13 +185,8 @@ async function buildComponents(env) { log(' Building safe-restart (TypeScript)...', 'blue'); try { const safeRestartDir = join(__dirname, 'safe-restart'); - if (!existsSync(safeRestartDir)) { - throw new Error('safe-restart directory not found'); - } - - exec('npm install', { cwd: safeRestartDir, silent: !options.verbose }); - exec('npm run build', { cwd: safeRestartDir, silent: !options.verbose }); - + execSync('npm install', { cwd: safeRestartDir, stdio: 'inherit' }); + execSync('npm run build', { cwd: safeRestartDir, stdio: 'inherit' }); logSuccess('safe-restart built successfully'); } catch (err) { logError(`Failed to build safe-restart: ${err.message}`); @@ -266,357 +195,253 @@ async function buildComponents(env) { } // ============================================================================ -// Step 4: Install Components +// Install Plugin // ============================================================================ -async function installComponents(env) { - if (options.buildOnly) { - logStep(4, 'Skipping installation (--build-only)'); - return; - } +function installPlugin(env) { + logInfo('Installing plugin...'); - logStep(4, 'Installing components...'); - - const installDir = options.prefix || env.openclawDir; - const binDir = join(installDir, 'bin'); - const skillsDir = join(installDir, 'skills', 'paddedcell'); - - log(` Install directory: ${installDir}`, 'blue'); - log(` Binary directory: ${binDir}`, 'blue'); - log(` Skills directory: ${skillsDir}`, 'blue'); + const pluginDir = join(env.openclawDir, 'plugins', PLUGIN_NAME); + const binDir = join(env.openclawDir, 'bin'); + // Create directories + mkdirSync(pluginDir, { recursive: true }); mkdirSync(binDir, { recursive: true }); - mkdirSync(skillsDir, { recursive: true }); + + // Copy plugin files + log(' Copying plugin files...', 'blue'); + + // Copy pcexec + const pcexecSource = join(__dirname, 'pcexec'); + const pcexecDest = join(pluginDir, 'pcexec'); + copyDir(pcexecSource, pcexecDest); + logSuccess(`Installed pcexec to ${pcexecDest}`); + + // Copy safe-restart + const safeRestartSource = join(__dirname, 'safe-restart'); + const safeRestartDest = join(pluginDir, 'safe-restart'); + copyDir(safeRestartSource, safeRestartDest); + logSuccess(`Installed safe-restart to ${safeRestartDest}`); // Install pass_mgr binary - log(' Installing pass_mgr binary...', 'blue'); const passMgrSource = join(__dirname, 'pass_mgr', 'dist', 'pass_mgr'); const passMgrDest = join(binDir, 'pass_mgr'); copyFileSync(passMgrSource, passMgrDest); chmodSync(passMgrDest, 0o755); - logSuccess(`pass_mgr installed to ${passMgrDest}`); + logSuccess(`Installed pass_mgr binary to ${passMgrDest}`); - // Install pcexec - log(' Installing pcexec module...', 'blue'); - const pcexecSource = join(__dirname, 'pcexec'); - const pcexecDest = join(skillsDir, 'pcexec'); - copyModule(pcexecSource, pcexecDest); - logSuccess(`pcexec installed to ${pcexecDest}`); + return { pluginDir, passMgrPath: passMgrDest }; +} - // Install safe-restart - log(' Installing safe-restart module...', 'blue'); - const safeRestartSource = join(__dirname, 'safe-restart'); - const safeRestartDest = join(skillsDir, 'safe-restart'); - copyModule(safeRestartSource, safeRestartDest); - logSuccess(`safe-restart installed to ${safeRestartDest}`); +// ============================================================================ +// Configure OpenClaw +// ============================================================================ - // Create skill manifest - log(' Creating skill manifest...', 'blue'); - const manifest = { - name: 'paddedcell', - version: '0.1.0', - description: 'Secure password management, safe execution, and coordinated restart', - tools: [ - { - name: 'pcexec', - entry: './pcexec/dist/index.js', - description: 'Safe exec with password sanitization', - }, - { - name: 'safe_restart', - entry: './safe-restart/dist/index.js', - description: 'Safe coordinated restart', - }, - ], - binaries: ['pass_mgr'], - installedAt: new Date().toISOString(), +function configureOpenClaw(env, pluginDir, delta) { + logInfo('Configuring OpenClaw...'); + + // 1. Add plugin to plugins.allow + const allowList = getJson(PLUGIN_ALLOW_PATH) || []; + const oldAllow = clone(allowList); + + if (!allowList.includes(PLUGIN_NAME)) { + allowList.push(PLUGIN_NAME); + setJson(PLUGIN_ALLOW_PATH, allowList); + delta.added[PLUGIN_ALLOW_PATH] = PLUGIN_NAME; + delta._prev = delta._prev || {}; + delta._prev[PLUGIN_ALLOW_PATH] = oldAllow; + logSuccess(`Added '${PLUGIN_NAME}' to plugins.allow`); + } else { + log(' Already in plugins.allow', 'green'); + } + + // 2. Add plugin entry + const oldEntry = getJson(PLUGIN_ENTRY); + const newEntry = { + enabled: true, + config: { + enabled: true, + passMgrPath: join(env.openclawDir, 'bin', 'pass_mgr'), + pluginDir: pluginDir, + }, }; - - writeFileSync( - join(skillsDir, 'manifest.json'), - JSON.stringify(manifest, null, 2) - ); - logSuccess('Skill manifest created'); -} -function copyModule(source, dest) { - mkdirSync(dest, { recursive: true }); - - const items = ['package.json', 'tsconfig.json', 'dist', 'src']; - for (const item of items) { - const srcPath = join(source, item); - const destPath = join(dest, item); - - if (!existsSync(srcPath)) continue; - - try { - const stat = execSync(`stat -c %F "${srcPath}" 2>/dev/null || echo "file"`, { - encoding: 'utf8', - cwd: __dirname - }).trim(); - - if (stat === 'directory') { - execSync(`cp -r "${srcPath}" "${destPath}"`, { cwd: __dirname }); - } else { - copyFileSync(srcPath, destPath); - } - } catch { - try { - copyFileSync(srcPath, destPath); - } catch { - execSync(`cp -r "${srcPath}" "${destPath}"`, { cwd: __dirname }); - } - } - } -} - -// ============================================================================ -// Step 5: Configuration -// ============================================================================ - -async function configure(env) { - if (options.buildOnly) { - logStep(5, 'Skipping configuration (--build-only)'); - return; - } - - logStep(5, 'Configuration...'); - - const installDir = env.openclawDir || join(homedir(), '.openclaw'); - const passMgrPath = join(installDir, 'bin', 'pass_mgr'); - - // Check if already initialized - const adminKeyDir = join(homedir(), '.pass_mgr'); - const configPath = join(adminKeyDir, 'config.json'); - - if (existsSync(configPath)) { - logSuccess('pass_mgr already initialized'); + if (oldEntry === undefined) { + delta.added[PLUGIN_ENTRY] = newEntry; } else { - log(' pass_mgr not initialized yet.', 'yellow'); - log(` Run "${passMgrPath} admin init" manually after installation.`, 'cyan'); + delta.replaced[PLUGIN_ENTRY] = oldEntry; } - - // Update OpenClaw plugins.load.paths configuration - log('\n Updating OpenClaw plugin configuration...', 'blue'); - try { - const skillsPath = join(installDir, 'skills', 'paddedcell'); + // Use set for nested path + const plugins = getJson('plugins') || {}; + plugins.entries = plugins.entries || {}; + plugins.entries[PLUGIN_NAME] = newEntry; + setJson('plugins', plugins); + logSuccess(`Configured ${PLUGIN_ENTRY}`); + + // 3. Add to plugins.load.paths + const pluginsConfig = getJson('plugins') || {}; + const oldPaths = clone(pluginsConfig.load?.paths) || []; + const newPaths = clone(oldPaths); + + if (!newPaths.includes(pluginDir)) { + newPaths.push(pluginDir); + delta.added[PLUGINS_LOAD_PATHS] = pluginDir; + delta._prev = delta._prev || {}; + delta._prev[PLUGINS_LOAD_PATHS] = oldPaths; - // Get current plugins.load.paths using openclaw config - let currentPaths = []; - try { - const result = execSync('openclaw config get plugins.load.paths --json 2>/dev/null || echo "[]"', { - encoding: 'utf8', - cwd: __dirname - }).trim(); - currentPaths = JSON.parse(result); - } catch { - currentPaths = []; - } - - // Add paddedcell skills path if not already present - if (!currentPaths.includes(skillsPath)) { - currentPaths.push(skillsPath); - execSync(`openclaw config set plugins.load.paths --json '${JSON.stringify(currentPaths)}'`, { - cwd: __dirname - }); - logSuccess(`Added plugin path to OpenClaw config: ${skillsPath}`); - } else { - log(' Plugin path already in OpenClaw config', 'green'); - } - } catch (err) { - logWarning(`Failed to update OpenClaw config: ${err.message}`); - log(' Please manually add the following to your OpenClaw config:', 'yellow'); - log(` openclaw config set plugins.load.paths --json '["${join(installDir, 'skills', 'paddedcell')}"]'`, 'cyan'); - } -} - -// ============================================================================ -// Step 6: Print Summary -// ============================================================================ - -function printSummary(env) { - logStep(6, 'Installation Summary'); - - const installDir = options.prefix || env.openclawDir; - const passMgrPath = join(installDir, 'bin', 'pass_mgr'); - - console.log(''); - log('╔════════════════════════════════════════════════════════╗', 'cyan'); - log('║ PaddedCell Installation Complete ║', 'cyan'); - log('╚════════════════════════════════════════════════════════╝', 'cyan'); - console.log(''); - - if (options.buildOnly) { - log('Build-only mode - binaries built but not installed', 'yellow'); - console.log(''); - log('Built artifacts:', 'blue'); - log(` • pass_mgr: ${join(__dirname, 'pass_mgr', 'dist', 'pass_mgr')}`, 'reset'); - log(` • pcexec: ${join(__dirname, 'pcexec', 'dist')}`, 'reset'); - log(` • safe-restart: ${join(__dirname, 'safe-restart', 'dist')}`, 'reset'); + pluginsConfig.load = pluginsConfig.load || {}; + pluginsConfig.load.paths = newPaths; + setJson('plugins', pluginsConfig); + logSuccess(`Added plugin path to ${PLUGINS_LOAD_PATHS}`); } else { - log('Installed components:', 'blue'); - log(` • pass_mgr binary: ${passMgrPath}`, 'reset'); - log(` • pcexec module: ${join(installDir, 'skills', 'paddedcell', 'pcexec')}`, 'reset'); - log(` • safe-restart module: ${join(installDir, 'skills', 'paddedcell', 'safe-restart')}`, 'reset'); - console.log(''); - - log('Next steps:', 'blue'); - console.log(''); - log('1. Initialize pass_mgr (required before first use):', 'yellow'); - log(` ${passMgrPath} admin init`, 'cyan'); - console.log(''); - log('2. Test pass_mgr:', 'yellow'); - log(` ${passMgrPath} set test_key mypass # Set a test password`, 'cyan'); - log(` ${passMgrPath} get test_key # Retrieve password`, 'cyan'); + log(' Plugin path already in load.paths', 'green'); } - - console.log(''); - log('For more information:', 'blue'); - log(' • Documentation: https://git.hangman-lab.top/nav/PaddedCell', 'cyan'); - log(' • Issues: https://git.hangman-lab.top/nav/PaddedCell/issues', 'cyan'); - console.log(''); } // ============================================================================ -// Uninstall Function +// Unconfigure OpenClaw // ============================================================================ -async function uninstall(env) { - logStep(1, 'Uninstalling PaddedCell...'); +function unconfigureOpenClaw(env, delta) { + logInfo('Removing OpenClaw configuration...'); - const installDir = options.prefix || env.openclawDir || join(homedir(), '.openclaw'); - const binDir = join(installDir, 'bin'); - const skillsDir = join(installDir, 'skills', 'paddedcell'); - - const itemsToRemove = []; - - // Check what exists - const passMgrBinary = join(binDir, 'pass_mgr'); - if (existsSync(passMgrBinary)) { - itemsToRemove.push(passMgrBinary); + // 1. Remove from plugins.allow first (before removing entry) + if (delta.added?.[PLUGIN_ALLOW_PATH] !== undefined) { + const allowList = getJson(PLUGIN_ALLOW_PATH) || []; + const idx = allowList.indexOf(PLUGIN_NAME); + if (idx !== -1) { + allowList.splice(idx, 1); + setJson(PLUGIN_ALLOW_PATH, allowList); + logSuccess(`Removed '${PLUGIN_NAME}' from plugins.allow`); + } } - if (existsSync(skillsDir)) { - itemsToRemove.push(skillsDir); + // 2. Remove plugin entry + if (delta.added?.[PLUGIN_ENTRY] !== undefined || delta.replaced?.[PLUGIN_ENTRY] !== undefined) { + unsetPath(PLUGIN_ENTRY); + logSuccess(`Removed ${PLUGIN_ENTRY}`); } - if (itemsToRemove.length === 0) { - log('No installed components found.', 'yellow'); - return; + // 3. Remove from plugins.load.paths + if (delta.added?.[PLUGINS_LOAD_PATHS] !== undefined) { + const plugins = getJson('plugins') || {}; + const paths = plugins.load?.paths || []; + const pluginDir = join(env.openclawDir, 'plugins', PLUGIN_NAME); + const idx = paths.indexOf(pluginDir); + + if (idx !== -1) { + paths.splice(idx, 1); + plugins.load.paths = paths; + setJson('plugins', plugins); + logSuccess(`Removed plugin path from ${PLUGINS_LOAD_PATHS}`); + } } +} - log('The following items will be removed:', 'yellow'); - for (const item of itemsToRemove) { - log(` • ${item}`, 'reset'); - } +// ============================================================================ +// Remove Plugin Files +// ============================================================================ - // Ask for confirmation - const confirm = await prompt('\nAre you sure you want to uninstall? (y/N): '); - if (confirm.toLowerCase() !== 'y') { - log('Uninstall cancelled.', 'yellow'); - return; - } +function removePluginFiles(env) { + logInfo('Removing plugin files...'); - // Perform uninstall - for (const item of itemsToRemove) { + const pluginDir = join(env.openclawDir, 'plugins', PLUGIN_NAME); + const passMgrBinary = join(env.openclawDir, 'bin', 'pass_mgr'); + + // Remove plugin directory + if (existsSync(pluginDir)) { try { - if (item === passMgrBinary) { - execSync(`rm -f "${item}"`, { silent: true }); - logSuccess(`Removed: ${item}`); - } else { - execSync(`rm -rf "${item}"`, { silent: true }); - logSuccess(`Removed: ${item}`); - } + execSync(`rm -rf "${pluginDir}"`, { encoding: 'utf8' }); + logSuccess(`Removed ${pluginDir}`); } catch (err) { - logError(`Failed to remove: ${item}`); + logError(`Failed to remove ${pluginDir}: ${err.message}`); } } - // Remove plugin path from OpenClaw config - log('\n Removing plugin path from OpenClaw config...', 'blue'); - try { - const skillsPath = join(installDir, 'skills', 'paddedcell'); - - // Get current plugins.load.paths - let currentPaths = []; + // Remove pass_mgr binary + if (existsSync(passMgrBinary)) { try { - const result = execSync('openclaw config get plugins.load.paths --json 2>/dev/null || echo "[]"', { - encoding: 'utf8', - cwd: __dirname - }).trim(); - currentPaths = JSON.parse(result); - } catch { - currentPaths = []; + execSync(`rm -f "${passMgrBinary}"`, { encoding: 'utf8' }); + logSuccess(`Removed ${passMgrBinary}`); + } catch (err) { + logError(`Failed to remove ${passMgrBinary}: ${err.message}`); } - - // Remove paddedcell skills path - const index = currentPaths.indexOf(skillsPath); - if (index > -1) { - currentPaths.splice(index, 1); - execSync(`openclaw config set plugins.load.paths --json '${JSON.stringify(currentPaths)}'`, { - cwd: __dirname - }); - logSuccess(`Removed plugin path from OpenClaw config`); - } else { - log(' Plugin path not found in OpenClaw config', 'yellow'); - } - } catch (err) { - logWarning(`Failed to update OpenClaw config: ${err.message}`); } - - // Check for admin key directory - const adminKeyDir = join(homedir(), '.pass_mgr'); - if (existsSync(adminKeyDir)) { - log('\n⚠️ Admin key directory found:', 'yellow'); - log(` ${adminKeyDir}`, 'cyan'); - log(' This contains your encryption keys. Remove manually if desired.', 'yellow'); - } - - console.log(''); - log('╔════════════════════════════════════════════════════════╗', 'cyan'); - log('║ PaddedCell Uninstall Complete ║', 'cyan'); - log('╚════════════════════════════════════════════════════════╝', 'cyan'); - console.log(''); } // ============================================================================ // Main // ============================================================================ -async function main() { - console.log(''); - log('╔════════════════════════════════════════════════════════╗', 'cyan'); - log('║ PaddedCell Plugin Installer v0.1.0 ║', 'cyan'); - log('╚════════════════════════════════════════════════════════╝', 'cyan'); - console.log(''); +function main() { + const env = detectEnvironment(); - try { - const env = detectEnvironment(); - - // Handle uninstall - if (options.uninstall) { - await uninstall(env); - process.exit(0); + if (mode === 'install') { + logInfo('Starting installation...'); + + try { + // Build components + buildComponents(env); + + // Install plugin files + const { pluginDir, passMgrPath } = installPlugin(env); + + // Configure OpenClaw + const delta = { added: {}, replaced: {}, removed: {} }; + configureOpenClaw(env, pluginDir, delta); + + logInfo('Installation complete!'); + console.log(''); + log('Next steps:', 'cyan'); + console.log(''); + log('1. Initialize pass_mgr (required before first use):', 'yellow'); + log(` ${passMgrPath} admin init`, 'cyan'); + console.log(''); + log('2. Test pass_mgr:', 'yellow'); + log(` ${passMgrPath} set test_key mypass`, 'cyan'); + log(` ${passMgrPath} get test_key`, 'cyan'); + console.log(''); + log('3. Restart OpenClaw gateway to apply changes:', 'yellow'); + log(' openclaw gateway restart', 'cyan'); + + } catch (err) { + logError(`Installation failed: ${err.message}`); + process.exit(1); } - - checkDependencies(env); - await buildComponents(env); - await installComponents(env); - await configure(env); - printSummary(env); - process.exit(0); - } catch (err) { - console.log(''); - log('╔════════════════════════════════════════════════════════╗', 'red'); - log('║ Installation Failed ║', 'red'); - log('╚════════════════════════════════════════════════════════╝', 'red'); - console.log(''); - log(`Error: ${err.message}`, 'red'); - if (options.verbose) { - console.log(err.stack); + } else { + logInfo('Starting uninstallation...'); + + // For uninstall, we need the delta from install + // For simplicity, we'll track what we added and remove it + const delta = { + added: {}, + replaced: {}, + removed: {} + }; + + // Assume we added these during install + const pluginDir = join(env.openclawDir, 'plugins', PLUGIN_NAME); + delta.added[PLUGIN_ALLOW_PATH] = PLUGIN_NAME; + delta.added[PLUGIN_ENTRY] = { enabled: true }; + delta.added[PLUGINS_LOAD_PATHS] = pluginDir; + + try { + // Unconfigure OpenClaw + unconfigureOpenClaw(env, delta); + + // Remove plugin files + removePluginFiles(env); + + logInfo('Uninstallation complete!'); + console.log(''); + log('Restart OpenClaw gateway to apply changes:', 'yellow'); + log(' openclaw gateway restart', 'cyan'); + + } catch (err) { + logError(`Uninstallation failed: ${err.message}`); + process.exit(1); } - process.exit(1); } }