import { mkdirSync, readdirSync, writeFileSync } from "node:fs" import { join } from "node:path" import * as p from "@clack/prompts" import * as actualApi from "@actual-app/api" import { CONFIG_DIR, loadConfig } from "../config" import { initActual } from "../actual" const BACKUP_DIR = join(CONFIG_DIR, "backups") export async function backup() { const config = loadConfig() const spinner = p.spinner() spinner.start("Connecting to Actual...") await initActual(config.actual) spinner.stop("Connected.") spinner.start("Exporting budget...") const result = await (actualApi as any).internal.send("export-budget") if (result.error) throw new Error(`Export failed: ${result.error}`) mkdirSync(BACKUP_DIR, { recursive: true }) const filename = `backup-${timestamp()}.zip` const filepath = join(BACKUP_DIR, filename) writeFileSync(filepath, result.data) spinner.stop(`Backup saved to ${filepath}`) } export async function restore(args: string[]) { const config = loadConfig() const backups = listBackups() if (!backups.length) { p.log.warn(`No backups found in ${BACKUP_DIR}`) return } const filepath = await p.select({ message: "Select backup to restore", options: backups.map(f => ({ value: join(BACKUP_DIR, f), label: f })) }) if (p.isCancel(filepath)) { p.cancel("Cancelled."); process.exit(0) } const confirmed = await p.confirm({ message: `Restore ${filepath}? This will overwrite your current budget.` }) if (p.isCancel(confirmed) || !confirmed) { p.cancel("Cancelled."); process.exit(0) } const spinner = p.spinner() spinner.start("Connecting to Actual...") await initActual(config.actual) spinner.start("Restoring backup...") const result = await (actualApi as any).internal.send("import-budget", { filepath, type: "actual" }) if (result?.error) throw new Error(`Restore failed: ${result.error}`) await actualApi.sync() spinner.stop("Restored and synced.") } function listBackups(): string[] { try { return readdirSync(BACKUP_DIR) .filter(f => f.endsWith(".zip")) .sort() .reverse() } catch { return [] } } function timestamp(): string { return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19) }