import { existsSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { homedir } from "node:os"; import { run, cliEntry, type AutostartManager, type AutostartInstallOpts } from "./index.js"; import { userConfigPath } from "../config.js"; const LABEL = "dev.kuns.claude-mailbox"; function plistPath(): string { return join(homedir(), "Library", "LaunchAgents", `${LABEL}.plist`); } function logDir(): string { return join(homedir(), "Library", "Logs", "ClaudeMailbox"); } function ensureConfigSeeded(opts: AutostartInstallOpts): string { const path = userConfigPath(); if (!existsSync(path)) { mkdirSync(dirname(path), { recursive: true }); const seed: Record = {}; if (opts.port !== undefined) seed.port = opts.port; if (opts.bind !== undefined) seed.bind = opts.bind; if (opts.dbPath !== undefined) seed.dbPath = opts.dbPath; writeFileSync(path, JSON.stringify(seed, null, 2) + "\n", "utf8"); } return path; } function escapeXml(s: string): string { return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function buildPlist(node: string, script: string, configPath: string): string { mkdirSync(logDir(), { recursive: true }); const argv = [node, script, "serve", "--config", configPath]; const argsXml = argv .map((a) => ` ${escapeXml(a)}`) .join("\n"); const stdout = join(logDir(), "stdout.log"); const stderr = join(logDir(), "stderr.log"); return ` Label ${LABEL} ProgramArguments ${argsXml} RunAtLoad KeepAlive StandardOutPath ${escapeXml(stdout)} StandardErrorPath ${escapeXml(stderr)} `; } export function darwinManager(): AutostartManager { return { mode: "default", async install(opts) { const configPath = ensureConfigSeeded(opts); const { node, script } = cliEntry(); const plist = buildPlist(node, script, configPath); const path = plistPath(); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, plist, "utf8"); run("launchctl", ["unload", path]); const r = run("launchctl", ["load", "-w", path]); if (r.status !== 0) { throw new Error(`launchctl load failed: ${r.stderr || r.stdout}`); } }, async uninstall(purge) { const path = plistPath(); if (existsSync(path)) { run("launchctl", ["unload", path]); unlinkSync(path); } if (purge) { const cfg = userConfigPath(); if (existsSync(cfg)) { try { const parsed = JSON.parse(readFileSync(cfg, "utf8")) as { dbPath?: string }; if (parsed.dbPath && existsSync(parsed.dbPath)) { rmSync(parsed.dbPath, { force: true }); } } catch { // ignore } unlinkSync(cfg); } } }, async start() { const r = run("launchctl", ["start", LABEL]); if (r.status !== 0) throw new Error(`launchctl start failed: ${r.stderr || r.stdout}`); }, async stop() { run("launchctl", ["stop", LABEL]); }, async status() { if (!existsSync(plistPath())) return "NotInstalled"; const r = run("launchctl", ["list", LABEL]); if (r.status !== 0) return "Stopped"; const pidMatch = r.stdout.match(/"PID"\s*=\s*(\d+)/); return pidMatch ? "Running" : "Stopped"; }, }; }