Rebuild backup on the library API with a durable failure ledger (closes #51)
check / check (push) Successful in 26s

backup() is now a Library method: it refreshes, fetches each pending
original (and, optionally, thumbnail) through the content cache and pools,
then rebuilds the sidecar, symlink, and per-collection JSON views from the
model. The downloadDirectory layout and the exit-code contract are unchanged.

An original already on disk is treated as complete and never re-fetched, so
runs are idempotent and an interrupted run resumes from the directory
listing. Per-file download and symlink failures are recorded in a durable
failures.json (classification, attempt count, last-tried) and no longer
abort the run, subsuming #8. exit stays non-zero while any failure remains.
The CLI now opens a Library and calls backup().

Model: opus-4-8
This commit is contained in:
2026-09-22 16:55:32 +00:00
parent 5db59a6e2b
commit 78dd2b8ebf
5 changed files with 724 additions and 484 deletions
+8 -3
View File
@@ -8,7 +8,7 @@ import { Command } from "commander";
import envPaths from "env-paths";
import { Client, type ClientSnapshot } from "../src/client.js";
import { init } from "../src/crypto/index.js";
import { runBackup } from "../src/backup.js";
import { Library } from "../src/library/index.js";
import { runMetadataBackup } from "../src/metadata-backup.js";
import {
listMissingThumbnails,
@@ -321,9 +321,14 @@ program
const client = requireSession();
stderr.write("Starting backup...\n");
const result = await runBackup(client, dir, (msg) => {
if (!opts.json) stderr.write(msg + "\n");
const lib = await Library.open({ client, downloadDirectory: dir });
const result = await lib.backup({
downloadDirectory: dir,
onProgress: (msg) => {
if (!opts.json) stderr.write(msg + "\n");
},
});
lib.close();
if (opts.json) {
stdout.write(JSON.stringify(result, null, 2) + "\n");