diff --git a/apps/lightcraft-cli/src/main.rs b/apps/lightcraft-cli/src/main.rs index 8bc2869c..11c04265 100644 --- a/apps/lightcraft-cli/src/main.rs +++ b/apps/lightcraft-cli/src/main.rs @@ -24,6 +24,7 @@ USAGE: given files imported. Options: --connect [ADDR] drive a running app instead (`lightcraft --control 7980`; default 127.0.0.1:7980) --demo headless: start with the procedurally generated demo library + --library DIR headless: open (or create) a persistent LightCraft library; edits are saved --compact list only the helper tools (every command stays reachable via run_command) lightcraft-cli render -o [OPTIONS] Develop one file and write the result (.png, .jpg, .tif or .webp by extension). Options: @@ -66,6 +67,7 @@ fn main() -> ExitCode { fn mcp(args: &[String]) -> Result<(), String> { let mut connect: Option = None; let mut demo = false; + let mut library: Option = None; let mut compact = false; let mut files = Vec::new(); let mut i = 0; @@ -85,6 +87,7 @@ fn mcp(args: &[String]) -> Result<(), String> { "--headless" => connect = None, "--demo" => demo = true, "--compact" => compact = true, + "--library" => library = Some(take_value(args, &mut i, "--library")?.to_string()), a if a.starts_with("--") => return Err(format!("unknown option `{a}`")), f => files.push(f.to_string()), } @@ -92,8 +95,8 @@ fn mcp(args: &[String]) -> Result<(), String> { } let backend: Box = match connect { Some(addr) => { - if !files.is_empty() || demo { - return Err("FILES and --demo apply to headless mode only (import through the `import` tool instead)".into()); + if !files.is_empty() || demo || library.is_some() { + return Err("FILES, --demo and --library apply to headless mode only (import through the `import` tool instead)".into()); } match Remote::connect(&addr) { Ok(r) => { @@ -107,7 +110,16 @@ fn mcp(args: &[String]) -> Result<(), String> { } } None => { - let mut h = if demo { Headless::demo() } else { Headless::default() }; + let mut h = match &library { + Some(dir) => { + let mut h = Headless::default(); + let r = h.session.open_library(dir, demo).map_err(|e| e.to_string())?; + eprintln!("lightcraft-cli mcp: opened library {dir} ({r:?})"); + h + } + None if demo => Headless::demo(), + None => Headless::default(), + }; if !files.is_empty() { let paths = expand_paths(&files); let r = h.session.execute("library.import", &json!({"paths": paths})).map_err(|e| e.to_string())?; diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index 2d56b459..c9cd303f 100644 --- a/crates/engine/src/media.rs +++ b/crates/engine/src/media.rs @@ -20,7 +20,7 @@ use lightcraft_raster::{Histogram, Rgb32f}; use serde::{Deserialize, Serialize}; /// Bump when the pipeline's output changes, to invalidate cached thumbnails. -pub const RENDER_CACHE_VERSION: u64 = 1; +pub const RENDER_CACHE_VERSION: u64 = 2; /// Thumbnails render at one of these long edges (so window/cell size changes reuse the cache). pub const THUMB_SIZES: [usize; 4] = [128, 256, 384, 512]; diff --git a/crates/mcp/src/headless.rs b/crates/mcp/src/headless.rs index 0ad3f7f7..b0e5386d 100644 --- a/crates/mcp/src/headless.rs +++ b/crates/mcp/src/headless.rs @@ -19,6 +19,13 @@ pub struct Headless { pub session: Session, } +impl Drop for Headless { + /// Snapshot a persistent library on exit (a no-op for in-memory sessions). + fn drop(&mut self) { + let _ = self.session.close_library(); + } +} + impl Default for Headless { fn default() -> Self { Self::new(Session::new().with_fs()) diff --git a/docs/mcp.md b/docs/mcp.md index 1242a7a1..02bdcb09 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -13,7 +13,10 @@ It runs in one of two modes: | **Headless** (default) | `lightcraft-cli mcp [--demo] [FILES/FOLDERS…]` | An in-process engine `Session`. Develop, render and export without a window. | | **Connect** | `lightcraft-cli mcp --connect [127.0.0.1:7980]` | A running desktop app started with `lightcraft --control 7980`, through its loopback JSON-lines control channel ([control-protocol.md](control-protocol.md)). Adds the UI tools (screenshot, clicks, keys, pointer gestures). | -Options: `--demo` starts the headless session with the procedurally generated demo library; +Options: `--library DIR` opens (or creates) a persistent LightCraft library — the same crash-safe +format the desktop app uses (`~/Pictures/LightCraft Library` by default there) — so ratings, edits and albums +survive between sessions (with `--demo`, a new library is seeded with the demo photos); +`--demo` starts the headless session with the procedurally generated demo library; `--compact` lists only the helper tools (see below). In connect mode the server starts even when the app is not running yet and connects on the first call (and reconnects if the app restarts).