A macOS CLI tool that exports original photos and videos from the macOS Photos library using PhotoKit.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update CLAUDE.md for library architecture

+30 -4
+30 -4
CLAUDE.md
··· 1 1 # Ladder 2 2 3 - Swift PhotoKit export helper for iCloud Photos backup. Part of the photo-cloud system (companion: [attic](https://github.com/tijs/attic)). 3 + Swift library and CLI for accessing the macOS Photos library. Part of the photo-cloud system (companion: [attic](https://github.com/tijs/attic)). 4 4 5 5 ## Commands 6 6 7 7 ```bash 8 8 swift build -c release | xcsift # Build release binary 9 - swift test | xcsift # Run tests (19 tests) 9 + swift test | xcsift # Run tests (44 tests, 8 suites) 10 10 swiftlint --fix # Auto-fix lint issues 11 11 ``` 12 12 13 13 ## Architecture 14 14 15 - - **LadderKit** (library): `PhotoExporter`, `StreamingHasher`, `FileHasher`, model types, path safety 16 - - **CLI** (executable): Reads JSON request from stdin, calls PhotoExporter, writes JSON response to stdout 15 + - **LadderKit** (library product): consumed by other Swift packages via SPM 16 + - `PhotoLibrary` protocol + `PhotoKitLibrary` — asset discovery and export via PhotoKit 17 + - `PhotosDatabase` — reads Photos.sqlite for metadata enrichment (keywords, people, descriptions, albums, filenames, edits) 18 + - `PhotosLibraryPath` — validates `.photoslibrary` bundles, derives database paths 19 + - `PhotoExporter` — concurrent file export with inline SHA-256 hashing 20 + - `StreamingHasher` / `FileHasher` — incremental and one-shot SHA-256 21 + - `PathSafety` — filename sanitization and path traversal prevention 22 + - `AssetInfo`, `AlbumInfo`, `PersonInfo`, `AssetKind` — data models (all `Codable` + `Sendable`) 23 + - **CLI** (executable): thin JSON-in/JSON-out wrapper around `PhotoExporter` for subprocess use by the attic Deno CLI 24 + 25 + ## Key Design Decisions 26 + 27 + - **PhotoKit for discovery/export, Photos.sqlite for enrichment**: PhotoKit provides core asset enumeration and file export (including transparent iCloud downloads). Photos.sqlite provides metadata PhotoKit doesn't expose (keywords, people, descriptions, albums, filenames, edit details). This hybrid approach gives complete metadata. 28 + - **No per-asset XPC during enumeration**: `enumerateAssets()` only uses PHAsset properties. Expensive `PHAssetResource` calls are deferred to export time. Album membership comes from sqlite. 29 + - **User selects library path**: No default Photos.sqlite path assumed. The consumer provides the `.photoslibrary` URL (e.g., from NSOpenPanel with a security-scoped bookmark). This avoids Full Disk Access and supports multiple/moved libraries. 30 + - **`safeQuery` resilience**: Photos.sqlite schema varies across macOS versions. Enrichment queries silently return empty results if tables don't exist. 17 31 18 32 ## Conventions 19 33 ··· 22 36 - `StreamingHasher` uses `@unchecked Sendable` with `NSLock` — documented safety invariant in `Hasher.swift`. TODO: migrate to `Mutex<CC_SHA256_CTX>` when targeting macOS 15+ 23 37 - Pipe all xcodebuild/swift commands through `xcsift` for clean output 24 38 - Files should stay under 500 lines 39 + - Caseless namespace types use `enum` (e.g., `PhotosDatabase`, `PhotosLibraryPath`) 40 + 41 + ## Testing 42 + 43 + All tests use mock implementations (`MockPhotoLibrary`, `MockAssetHandle`). No Photos library access, credentials, or network required. Tests verify: 44 + 45 + - Export with inline hashing 46 + - Path traversal prevention 47 + - Photos.sqlite enrichment and in-place application 48 + - Library path validation 49 + - AssetInfo Codable round-trip (including `description` key mapping) 50 + - UUID extraction from PhotoKit localIdentifier format