install.js
1 #!/usr/bin/env node 2 3 const fs = require("fs"); 4 const path = require("path"); 5 const https = require("https"); 6 const os = require("os"); 7 8 const REPO_URL = "https://raw.githubusercontent.com/nicobailon/pi-rewind-hook/main"; 9 const EXT_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "rewind"); 10 const OLD_HOOK_DIR = path.join(os.homedir(), ".pi", "agent", "hooks", "rewind"); 11 const SETTINGS_FILE = path.join(os.homedir(), ".pi", "agent", "settings.json"); 12 const EXT_PATH = "~/.pi/agent/extensions/rewind/index.ts"; 13 14 function download(url) { 15 return new Promise((resolve, reject) => { 16 https.get(url, (res) => { 17 if (res.statusCode === 301 || res.statusCode === 302) { 18 return download(res.headers.location).then(resolve).catch(reject); 19 } 20 if (res.statusCode !== 200) { 21 return reject(new Error(`Failed to download ${url}: ${res.statusCode}`)); 22 } 23 let data = ""; 24 res.on("data", (chunk) => (data += chunk)); 25 res.on("end", () => resolve(data)); 26 res.on("error", reject); 27 }).on("error", reject); 28 }); 29 } 30 31 async function main() { 32 console.log("Installing pi-rewind-hook (Rewind Extension)...\n"); 33 34 fs.mkdirSync(EXT_DIR, { recursive: true }); 35 console.log(`Created directory: ${EXT_DIR}`); 36 37 console.log("Downloading index.ts..."); 38 const extContent = await download(`${REPO_URL}/index.ts`); 39 fs.writeFileSync(path.join(EXT_DIR, "index.ts"), extContent); 40 41 console.log("Downloading README.md..."); 42 const readmeContent = await download(`${REPO_URL}/README.md`); 43 fs.writeFileSync(path.join(EXT_DIR, "README.md"), readmeContent); 44 45 console.log(`\nUpdating settings: ${SETTINGS_FILE}`); 46 47 let settings = {}; 48 if (fs.existsSync(SETTINGS_FILE)) { 49 try { 50 settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8")); 51 } catch (err) { 52 console.error(`Warning: Could not parse existing settings.json: ${err.message}`); 53 console.error("Creating new settings file..."); 54 } 55 } 56 57 if (settings.hooks && Array.isArray(settings.hooks) && settings.hooks.length > 0) { 58 console.log("\nMigrating hooks to extensions..."); 59 if (!Array.isArray(settings.extensions)) { 60 settings.extensions = []; 61 } 62 for (const entry of settings.hooks) { 63 if (entry.includes("/hooks/rewind")) { 64 continue; 65 } 66 const newPath = entry.replace("/hooks/", "/extensions/"); 67 if (!settings.extensions.includes(newPath)) { 68 settings.extensions.push(newPath); 69 console.log(` Migrated: ${entry} -> ${newPath}`); 70 } 71 } 72 delete settings.hooks; 73 console.log("Removed old 'hooks' key from settings"); 74 } 75 76 if (!Array.isArray(settings.extensions)) { 77 settings.extensions = []; 78 } 79 80 const EXT_PATH_ALT = "~/.pi/agent/extensions/rewind"; 81 const hasRewindExt = settings.extensions.some(p => 82 p === EXT_PATH || p === EXT_PATH_ALT || 83 p.includes("/extensions/rewind/index.ts") || 84 p.endsWith("/extensions/rewind") 85 ); 86 87 if (!hasRewindExt) { 88 settings.extensions.push(EXT_PATH); 89 console.log(`Added "${EXT_PATH}" to extensions array`); 90 } else { 91 console.log("Extension already configured in settings.json"); 92 } 93 94 fs.mkdirSync(path.dirname(SETTINGS_FILE), { recursive: true }); 95 fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n"); 96 97 if (fs.existsSync(OLD_HOOK_DIR)) { 98 console.log(`\nCleaning up old hooks directory: ${OLD_HOOK_DIR}`); 99 fs.rmSync(OLD_HOOK_DIR, { recursive: true, force: true }); 100 console.log("Removed old hooks/rewind directory"); 101 } 102 103 console.log("\nInstallation complete!"); 104 console.log("\nThe rewind extension will load automatically when you start pi."); 105 console.log("Use /branch to rewind to a previous checkpoint."); 106 } 107 108 main().catch((err) => { 109 console.error(`\nInstallation failed: ${err.message}`); 110 process.exit(1); 111 });