#!/usr/bin/env node /** * Deploys the bundle-authoritative system prompt cover into the Pi agent directory. * * The cover is bundle-owned configuration: the repository copy under * `config/system-prompt-cover.md` is authoritative, and this helper synchronizes it to * `/SYSTEM_COVER.md`. There is no user-owned variant, so an existing file is * replaced atomically without keeping a backup. */ import { readFileSync, realpathSync } from "node:fs"; import { mkdir, open, rename, rm } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; export const COVER_FILE_NAME = "SYSTEM_COVER.md"; const scriptPath = fileURLToPath(import.meta.url); const bundleRoot = dirname(dirname(scriptPath)); export const BUNDLED_COVER_PATH = join(bundleRoot, "config", "system-prompt-cover.md"); /** * Resolve the Pi agent directory, mirroring Pi's own `getAgentDir()`. * * Implemented locally so this helper stays runnable from `install.sh` / `update.sh` * without loading the Pi runtime. * * @param {NodeJS.ProcessEnv} [env] * @param {string} homedir * @returns {string} */ export function resolveAgentDir(env = process.env, homedir) { const configured = env.PI_CODING_AGENT_DIR; if (configured) { return configured.startsWith("~/") ? join(homedir, configured.slice(2)) : configured; } return join(homedir, ".pi", "agent"); } /** * Compare the bundle cover against the deployed copy and replace it when they differ. * * Returns `{ status }`: * - `current` — deployed copy already matches the bundle. * - `updated` — deployed copy was replaced. * - `created` — no deployed copy existed. * * Read or write failures throw so callers can report a real failure instead of * silently leaving a stale cover in place. * * @param {{ agentDir?: string, bundledCoverPath?: string }} options * @returns {Promise<{ status: "current" | "updated" | "created", targetPath: string }>} */ export async function deploySystemPromptCover({ agentDir, bundledCoverPath = BUNDLED_COVER_PATH } = {}) { if (!agentDir) { throw new Error("agentDir is required"); } let bundledCover; try { bundledCover = readFileSync(bundledCoverPath, "utf8"); } catch (error) { throw new Error( `cannot read bundled system prompt cover at ${bundledCoverPath}: ${ error instanceof Error ? error.message : String(error) }`, ); } const targetPath = join(agentDir, COVER_FILE_NAME); let existing; let existed = false; try { existing = readFileSync(targetPath, "utf8"); existed = true; } catch (error) { if (error?.code !== "ENOENT") { throw new Error( `cannot read deployed system prompt cover at ${targetPath}: ${ error instanceof Error ? error.message : String(error) }`, ); } } if (existed && existing === bundledCover) { return { status: "current", targetPath }; } await mkdir(agentDir, { recursive: true }); const temporaryPath = join( agentDir, `.${COVER_FILE_NAME}.my-pi-${process.pid}-${Math.random().toString(16).slice(2)}.tmp`, ); try { const handle = await open(temporaryPath, "wx", 0o644); try { await handle.writeFile(bundledCover, "utf8"); await handle.sync(); } finally { await handle.close(); } await rename(temporaryPath, targetPath); } catch (error) { await rm(temporaryPath, { force: true }).catch(() => {}); throw error; } return { status: existed ? "updated" : "created", targetPath }; } function isMainModule() { if (!process.argv[1]) return false; try { return fileURLToPath(import.meta.url) === realpathSync(process.argv[1]); } catch { return false; } } async function main() { const agentDir = process.argv[2] || resolveAgentDir(process.env, process.env.HOME); const { status } = await deploySystemPromptCover({ agentDir }); process.stdout.write(`${status}\n`); } if (isMainModule()) { main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }); }