From bb194ef97834b9e36ac2604e8af1184855f5d31e Mon Sep 17 00:00:00 2001 From: zhi Date: Thu, 5 Mar 2026 12:26:52 +0000 Subject: [PATCH] refactor: install to dist/padded-cell/ like Dirigent - Plugin name: padded-cell (matches dist subdirectory) - Install copies files to dist/padded-cell/ - Plugin path set to dist/padded-cell/ directory - Follows Dirigent pattern: dist// --- install.mjs | 231 ++++++++++++++++--------------------------- openclaw.plugin.json | 10 +- 2 files changed, 88 insertions(+), 153 deletions(-) diff --git a/install.mjs b/install.mjs index 7e8e4db..0a7a6fd 100755 --- a/install.mjs +++ b/install.mjs @@ -9,20 +9,21 @@ * node install.mjs --build-only * node install.mjs --skip-check * 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 { 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 = resolve(dirname(__filename)); -// Plugin configuration -const PLUGIN_NAME = 'PaddedCell'; +// Plugin configuration - matches directory name in dist/ +const PLUGIN_NAME = 'padded-cell'; +const DIST_DIR = join(__dirname, 'dist', PLUGIN_NAME); // Parse arguments const args = process.argv.slice(2); @@ -79,19 +80,6 @@ function exec(command, options = {}) { return execSync(command, { ...defaultOptions, ...options }); } -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()); - }); - }); -} - // OpenClaw config helpers function getOpenclawConfig(pathKey, defaultValue = undefined) { try { @@ -107,7 +95,7 @@ function getOpenclawConfig(pathKey, defaultValue = undefined) { } function setOpenclawConfig(pathKey, value) { - execSync(`openclaw config set ${pathKey} --json '${JSON.stringify(value)}'`, { + execSync(`openclaw config set ${pathKey} '${JSON.stringify(value)}' --json`, { cwd: __dirname }); } @@ -120,6 +108,23 @@ function unsetOpenclawConfig(pathKey) { } } +// 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 // ============================================================================ @@ -129,28 +134,23 @@ function detectEnvironment() { const env = { platform: platform(), - isLinux: platform() === 'linux', - isMacOS: platform() === 'darwin', nodeVersion: null, goVersion: null, - openclawPath: null, - openclawDir: null, + openclawDir: join(homedir(), '.openclaw'), }; // Check Node.js try { - const version = exec('node --version', { silent: true }).trim(); - env.nodeVersion = version; - logSuccess(`Node.js ${version}`); + env.nodeVersion = exec('node --version', { silent: true }).trim(); + logSuccess(`Node.js ${env.nodeVersion}`); } 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 = exec('go version', { silent: true }).trim(); + logSuccess(`Go ${env.goVersion}`); } catch { logError('Go not found'); } @@ -158,7 +158,6 @@ function detectEnvironment() { // Check openclaw try { const path = exec('which openclaw', { silent: true }).trim(); - env.openclawPath = path; logSuccess(`openclaw at ${path}`); // Try to find openclaw config dir @@ -166,7 +165,6 @@ function detectEnvironment() { const possibleDirs = [ join(home, '.openclaw'), join(home, '.config', 'openclaw'), - '/etc/openclaw', ]; for (const dir of possibleDirs) { @@ -176,14 +174,8 @@ function detectEnvironment() { 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; @@ -238,21 +230,13 @@ async function buildComponents(env) { 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 - }); + exec('go build -o dist/pass_mgr src/main.go', { cwd: passMgrDir, silent: !options.verbose }); 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) { @@ -264,13 +248,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 }); - logSuccess('pcexec built successfully'); } catch (err) { logError(`Failed to build pcexec: ${err.message}`); @@ -281,13 +260,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 }); - logSuccess('safe-restart built successfully'); } catch (err) { logError(`Failed to build safe-restart: ${err.message}`); @@ -312,73 +286,46 @@ async function installComponents(env) { log(` Install directory: ${installDir}`, 'blue'); log(` Binary directory: ${binDir}`, 'blue'); - log(` Plugin directory: ${__dirname}`, 'blue'); + log(` Dist directory: ${DIST_DIR}`, 'blue'); - // Create bin directory - mkdirSync(binDir, { recursive: true }); + // Create dist/padded-cell directory and copy plugin files + log(' Copying plugin files to dist/padded-cell...', 'blue'); + mkdirSync(DIST_DIR, { recursive: true }); - // Create openclaw.plugin.json if not exists - const manifestPath = join(__dirname, 'openclaw.plugin.json'); - if (!existsSync(manifestPath)) { - const manifest = { - id: PLUGIN_NAME, - name: 'PaddedCell', - version: '0.1.0', - description: 'Secure password management, safe execution, and coordinated restart', - entry: './index.js', - configSchema: { - type: 'object', - properties: { - enabled: { type: 'boolean', default: true } - } + // Copy pcexec + copyDir(join(__dirname, 'pcexec'), join(DIST_DIR, 'pcexec')); + logSuccess('Copied pcexec to dist/padded-cell/'); + + // Copy safe-restart + copyDir(join(__dirname, 'safe-restart'), join(DIST_DIR, 'safe-restart')); + logSuccess('Copied safe-restart to dist/padded-cell/'); + + // Create openclaw.plugin.json + const manifest = { + id: PLUGIN_NAME, + name: 'PaddedCell', + version: '0.1.0', + description: 'Secure password management, safe execution, and coordinated restart', + entry: './safe-restart/dist/index.js', + tools: [ + { + name: 'pcexec', + entry: './pcexec/dist/index.js', + description: 'Safe exec with password sanitization' }, - 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' - } - ] - }; - writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); - logSuccess('Created openclaw.plugin.json'); - } + { + name: 'safe_restart', + entry: './safe-restart/dist/index.js', + description: 'Safe coordinated restart' + } + ] + }; + writeFileSync(join(DIST_DIR, 'openclaw.plugin.json'), JSON.stringify(manifest, null, 2)); + logSuccess('Created openclaw.plugin.json in dist/padded-cell/'); - // Create index.js if not exists - const indexPath = join(__dirname, 'index.js'); - if (!existsSync(indexPath)) { - const indexContent = `// PaddedCell Plugin Entry Point -export const id = '${PLUGIN_NAME}'; -export const name = 'PaddedCell'; -export const version = '0.1.0'; - -// Export tools (will be loaded by OpenClaw) -export { pcexec } from './pcexec/dist/index.js'; -export { - safeRestart, - createSafeRestartTool, - StatusManager, - createApiServer, - startApiServer -} from './safe-restart/dist/index.js'; - -// Default export -export default { - id, - name, - version, -}; -`; - writeFileSync(indexPath, indexContent); - logSuccess('Created index.js'); - } - - // Install pass_mgr binary + // Create bin directory and install pass_mgr binary + mkdirSync(binDir, { recursive: true }); + log(' Installing pass_mgr binary...', 'blue'); const passMgrSource = join(__dirname, 'pass_mgr', 'dist', 'pass_mgr'); const passMgrDest = join(binDir, 'pass_mgr'); @@ -415,7 +362,7 @@ async function configure(env) { log(` Run "${passMgrPath} admin init" manually after installation.`, 'cyan'); } - // Configure OpenClaw using openclaw config commands + // Configure OpenClaw log('\n Configuring OpenClaw plugin...', 'blue'); try { @@ -430,33 +377,24 @@ async function configure(env) { } // 2. Add plugin entry - const existingEntry = getOpenclawConfig(`plugins.entries.${PLUGIN_NAME}`); - const pluginEntry = { + const plugins = getOpenclawConfig('plugins', {}); + plugins.entries = plugins.entries || {}; + plugins.entries[PLUGIN_NAME] = { enabled: true, config: { enabled: true, passMgrPath: passMgrPath, - pluginDir: __dirname, }, }; - - const plugins = getOpenclawConfig('plugins', {}); - plugins.entries = plugins.entries || {}; - plugins.entries[PLUGIN_NAME] = pluginEntry; setOpenclawConfig('plugins', plugins); - - if (existingEntry) { - logSuccess(`Updated ${PLUGIN_NAME} plugin entry`); - } else { - logSuccess(`Added ${PLUGIN_NAME} plugin entry`); - } + logSuccess(`Configured ${PLUGIN_NAME} plugin entry`); // 3. Add plugin path to plugins.load.paths const currentPaths = getOpenclawConfig('plugins.load.paths', []); - if (!currentPaths.includes(__dirname)) { - currentPaths.push(__dirname); + if (!currentPaths.includes(DIST_DIR)) { + currentPaths.push(DIST_DIR); setOpenclawConfig('plugins.load.paths', currentPaths); - logSuccess(`Added plugin path to plugins.load.paths`); + logSuccess(`Added ${DIST_DIR} to plugins.load.paths`); } else { log(' Plugin path already in plugins.load.paths', 'green'); } @@ -464,8 +402,7 @@ async function configure(env) { logWarning(`Failed to configure OpenClaw: ${err.message}`); log(' Please manually configure:', 'yellow'); log(` openclaw config set plugins.allow --json '[..., "${PLUGIN_NAME}"]'`, 'cyan'); - log(` openclaw config set plugins.entries.${PLUGIN_NAME}.enabled --json 'true'`, 'cyan'); - log(` openclaw config set plugins.load.paths --json '[..., "${__dirname}"]'`, 'cyan'); + log(` openclaw config set plugins.load.paths --json '[..., "${DIST_DIR}"]'`, 'cyan'); } } @@ -492,7 +429,7 @@ function printSummary(env, passMgrPath) { } else { log('Installed components:', 'blue'); log(` • pass_mgr binary: ${passMgrPath}`, 'reset'); - log(` • Plugin location: ${__dirname}`, 'reset'); + log(` • Plugin files: ${DIST_DIR}`, 'reset'); console.log(''); log('Next steps:', 'blue'); @@ -509,9 +446,6 @@ function printSummary(env, passMgrPath) { } console.log(''); - log('For more information:', 'blue'); - log(' • Documentation: https://git.hangman-lab.top/nav/PaddedCell', 'cyan'); - console.log(''); } // ============================================================================ @@ -534,6 +468,16 @@ async function uninstall(env) { } } + // Remove dist/padded-cell directory + if (existsSync(DIST_DIR)) { + try { + execSync(`rm -rf "${DIST_DIR}"`, { silent: true }); + logSuccess(`Removed ${DIST_DIR}`); + } catch (err) { + logError(`Failed to remove ${DIST_DIR}`); + } + } + // Remove OpenClaw configuration log('\n Removing OpenClaw configuration...', 'blue'); @@ -553,7 +497,7 @@ async function uninstall(env) { // Remove from plugins.load.paths const currentPaths = getOpenclawConfig('plugins.load.paths', []); - const pathIdx = currentPaths.indexOf(__dirname); + const pathIdx = currentPaths.indexOf(DIST_DIR); if (pathIdx !== -1) { currentPaths.splice(pathIdx, 1); setOpenclawConfig('plugins.load.paths', currentPaths); @@ -613,9 +557,6 @@ async function main() { log('╚════════════════════════════════════════════════════════╝', 'red'); console.log(''); log(`Error: ${err.message}`, 'red'); - if (options.verbose) { - console.log(err.stack); - } process.exit(1); } } diff --git a/openclaw.plugin.json b/openclaw.plugin.json index b7047d4..162f222 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,15 +1,9 @@ { - "id": "PaddedCell", + "id": "padded-cell", "name": "PaddedCell", "version": "0.1.0", "description": "Secure password management, safe execution, and coordinated restart", - "entry": "./index.js", - "configSchema": { - "type": "object", - "properties": { - "enabled": { "type": "boolean", "default": true } - } - }, + "entry": "./safe-restart/dist/index.js", "tools": [ { "name": "pcexec",