/** * @file `agenthub statusline install|uninstall`: writes or removes the * `statusLine` entry in Claude Code's user settings (`/settings.json`) * — only when the user runs the command, never at bootstrap. A statusLine that * is not AgentHub's is left alone unless `force` is set, and the file is backed * up beside itself (`settings.json.agenthub-backup-`) before any write. * @author Nguyễn Ngọc Trí Vĩ */ const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const REPO_ROOT = path.join(__dirname, ".."); /** Our own entries: `node ".../statusline/statusline.js"` or `".../agenthub" statusline`. */ const OURS = /(statusline[\\/]statusline\.js"?|agenthub"? statusline)\s*$/; const isOurs = (entry) => typeof entry?.command === "string" && OURS.test(entry.command); /** * The command to register. A plugin install runs from a cache directory that * every update replaces, so it goes through the stable `~/.local/bin/agenthub` * launcher the bootstrap keeps pointed at the current build; a checkout runs * the script in place. */ function statuslineCommand(root = REPO_ROOT, home = os.homedir()) { const launcher = path.join(home, ".local", "bin", "agenthub"); const inCache = root.includes(`${path.sep}plugins${path.sep}cache${path.sep}`); if (inCache && fs.existsSync(launcher)) return `"${launcher}" statusline`; return `node "${path.join(root, "statusline", "statusline.js")}"`; } function readSettings(file) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch (err) { if (err.code === "ENOENT") return {}; throw new Error(`cannot read ${file}: ${err.message}`); } } function writeSettings(file, settings) { if (fs.existsSync(file)) { const stamp = new Date().toISOString().replace(/[:.]/g, "-"); fs.copyFileSync(file, `${file}.agenthub-backup-${stamp}`); } else { fs.mkdirSync(path.dirname(file), { recursive: true }); } fs.writeFileSync(file, JSON.stringify(settings, null, 2) + "\n", "utf8"); } /** @returns {{status: "installed"|"unchanged"|"foreign", command: string, previous?: object}} */ function install(file, { force = false, command = statuslineCommand() } = {}) { const settings = readSettings(file); const current = settings.statusLine; if (current && !isOurs(current) && !force) return { status: "foreign", command, previous: current }; if (current?.type === "command" && current.command === command) return { status: "unchanged", command }; settings.statusLine = { type: "command", command, padding: 0 }; writeSettings(file, settings); return { status: "installed", command, previous: current }; } /** @returns {{status: "removed"|"absent"|"foreign", previous?: object}} */ function uninstall(file, { force = false } = {}) { const settings = readSettings(file); const current = settings.statusLine; if (!current) return { status: "absent" }; if (!isOurs(current) && !force) return { status: "foreign", previous: current }; delete settings.statusLine; writeSettings(file, settings); return { status: "removed", previous: current }; } module.exports = { install, uninstall, isOurs, statuslineCommand };