diff --git a/README.md b/README.md index c8cce72..f2cefa6 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Prerequisites: Rust (stable), Node.js/npm, macOS. `dev.sh`/`build.sh` expect `CA |--------------------|----------|---------| | `claudeCommand` | `claude` | Command launched in the project terminal (via `zsh -ic`, so your `.zshrc` PATH applies). | | `syncOnSessionEnd` | `false` | After a session ends, commit and push the Git repository containing the project. | +| `poolSyncDir` | unset | Directory to sync pool runtime data into (session transcripts, todos, prompt history — what `/resume` needs). When set, a pool's runtime files are replaced by symlinks into `/`, so sessions travel with however that directory is synced (git, Syncthing, …). Unset: everything stays local in the pool directory. | ## Claude and Anthropic licensing diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ae2746f..bd266d9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1205,18 +1205,30 @@ fn init_pool_config( const SYNCED_RUNTIME: [(&str, bool); 3] = [("projects", true), ("todos", true), ("history.jsonl", false)]; -/// Zielort der synced Runtime-Daten eines Pools im claude-projects-Repo. -fn pool_data_dir(paths: &Paths, pool: &str) -> PathBuf { - paths.projects_dir().join("pool").join(pool) +/// Sync-Ziel für Pool-Laufzeitdaten (settings.json: poolSyncDir). +/// Ungesetzt = Feature aus, alle Daten bleiben lokal im Pool-Ordner. +fn pool_sync_dir(paths: &Paths) -> Option { + fs::read_to_string(paths.config_dir().join(APP_SETTINGS_FILE)) + .ok() + .and_then(|s| serde_json::from_str::(&s).ok()) + .and_then(|v| v["poolSyncDir"].as_str().map(|d| expand_home(paths, d))) } -/// Ersetzt im Pool-Ordner projects/todos/history.jsonl durch Symlinks auf das -/// synced Repo. Die Symlinks sind maschinenlokal, die Daten reisen über git. +/// Zielort der synced Runtime-Daten eines Pools unterhalb von poolSyncDir. +fn pool_data_dir(paths: &Paths, pool: &str) -> Result { + pool_sync_dir(paths) + .map(|d| d.join(pool)) + .ok_or_else(|| "poolSyncDir ist nicht konfiguriert".to_string()) +} + +/// Ersetzt im Pool-Ordner projects/todos/history.jsonl durch Symlinks auf den +/// konfigurierten Sync-Ordner. Die Symlinks sind maschinenlokal, die Daten +/// reisen über den Sync des Zielordners (z. B. git). /// Vorhandene echte Inhalte werden verworfen (kein History-Erhalt — bewusst). fn link_pool_runtime_in(paths: &Paths, pool: &str) -> Result<(), String> { check_name(pool)?; let src = paths.pool_dir(pool); - let data = pool_data_dir(paths, pool); + let data = pool_data_dir(paths, pool)?; for (name, is_dir) in SYNCED_RUNTIME { let target = data.join(name); if is_dir { @@ -1266,7 +1278,9 @@ fn create_apikey_pool_in( serde_json::json!({ "apiKeyHelper": apikey_helper_command(&dir, &id) }), )?; write_pool_json(&dir, name, "apikey")?; - link_pool_runtime_in(paths, &id)?; + if pool_sync_dir(paths).is_some() { + link_pool_runtime_in(paths, &id)?; + } Ok(id) } @@ -1279,7 +1293,9 @@ fn create_oauth_pool_in(paths: &Paths, name: &str) -> Result { init_pool_config(&dir, serde_json::json!({}))?; write_pool_json(&dir, name, "oauth")?; let id = dir.file_name().unwrap().to_string_lossy().into_owned(); - link_pool_runtime_in(paths, &id)?; + if pool_sync_dir(paths).is_some() { + link_pool_runtime_in(paths, &id)?; + } Ok(id) } @@ -2611,18 +2627,36 @@ mod tests { #[test] fn pool_anlegen_verlinkt_synced_runtime() { let p = tmp_paths(); + fs::create_dir_all(p.config_dir()).unwrap(); + fs::write( + p.config_dir().join(APP_SETTINGS_FILE), + "{ \"poolSyncDir\": \"~/claude-projects/pool\" }\n", + ) + .unwrap(); let id = make_oauth_pool(&p, "privat"); let pooldir = p.pool_dir(&id); for (name, is_dir) in SYNCED_RUNTIME { let link = pooldir.join(name); assert!(link.is_symlink(), "{name} sollte Symlink sein"); - assert_eq!(fs::read_link(&link).unwrap(), pool_data_dir(&p, &id).join(name)); - let target = pool_data_dir(&p, &id).join(name); + assert_eq!(fs::read_link(&link).unwrap(), pool_data_dir(&p, &id).unwrap().join(name)); + let target = pool_data_dir(&p, &id).unwrap().join(name); assert_eq!(target.is_dir(), is_dir); assert!(target.exists()); } - // Zieldaten liegen im claude-projects-Repo unter pool// - assert!(p.projects_dir().join("pool").join(&id).is_dir()); + // Zieldaten liegen unter dem konfigurierten poolSyncDir// + assert!(p.home.join("claude-projects/pool").join(&id).is_dir()); + } + + /// Ohne poolSyncDir bleibt alles lokal: keine Symlinks, Verlinken scheitert laut. + #[test] + fn pool_anlegen_ohne_sync_dir_bleibt_lokal() { + let p = tmp_paths(); + let id = make_oauth_pool(&p, "privat"); + for (name, _) in SYNCED_RUNTIME { + assert!(!p.pool_dir(&id).join(name).is_symlink(), "{name} darf kein Symlink sein"); + } + let err = link_pool_runtime_in(&p, &id).unwrap_err(); + assert!(err.contains("poolSyncDir")); } #[test]