/ extensions / rewind / install.js
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  });