poolSyncDir konfigurierbar: Pool-Runtime-Verlinkung nur bei gesetztem Sync-Ordner (settings.json), Ziel <poolSyncDir>/<pool> statt hartem ~/claude-projects/pool; ungesetzt bleibt alles lokal
This commit is contained in:
@@ -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). |
|
| `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. |
|
| `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 `<poolSyncDir>/<pool>`, so sessions travel with however that directory is synced (git, Syncthing, …). Unset: everything stays local in the pool directory. |
|
||||||
|
|
||||||
## Claude and Anthropic licensing
|
## Claude and Anthropic licensing
|
||||||
|
|
||||||
|
|||||||
+46
-12
@@ -1205,18 +1205,30 @@ fn init_pool_config(
|
|||||||
const SYNCED_RUNTIME: [(&str, bool); 3] =
|
const SYNCED_RUNTIME: [(&str, bool); 3] =
|
||||||
[("projects", true), ("todos", true), ("history.jsonl", false)];
|
[("projects", true), ("todos", true), ("history.jsonl", false)];
|
||||||
|
|
||||||
/// Zielort der synced Runtime-Daten eines Pools im claude-projects-Repo.
|
/// Sync-Ziel für Pool-Laufzeitdaten (settings.json: poolSyncDir).
|
||||||
fn pool_data_dir(paths: &Paths, pool: &str) -> PathBuf {
|
/// Ungesetzt = Feature aus, alle Daten bleiben lokal im Pool-Ordner.
|
||||||
paths.projects_dir().join("pool").join(pool)
|
fn pool_sync_dir(paths: &Paths) -> Option<PathBuf> {
|
||||||
|
fs::read_to_string(paths.config_dir().join(APP_SETTINGS_FILE))
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| serde_json::from_str::<serde_json::Value>(&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
|
/// Zielort der synced Runtime-Daten eines Pools unterhalb von poolSyncDir.
|
||||||
/// synced Repo. Die Symlinks sind maschinenlokal, die Daten reisen über git.
|
fn pool_data_dir(paths: &Paths, pool: &str) -> Result<PathBuf, String> {
|
||||||
|
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).
|
/// Vorhandene echte Inhalte werden verworfen (kein History-Erhalt — bewusst).
|
||||||
fn link_pool_runtime_in(paths: &Paths, pool: &str) -> Result<(), String> {
|
fn link_pool_runtime_in(paths: &Paths, pool: &str) -> Result<(), String> {
|
||||||
check_name(pool)?;
|
check_name(pool)?;
|
||||||
let src = paths.pool_dir(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 {
|
for (name, is_dir) in SYNCED_RUNTIME {
|
||||||
let target = data.join(name);
|
let target = data.join(name);
|
||||||
if is_dir {
|
if is_dir {
|
||||||
@@ -1266,7 +1278,9 @@ fn create_apikey_pool_in(
|
|||||||
serde_json::json!({ "apiKeyHelper": apikey_helper_command(&dir, &id) }),
|
serde_json::json!({ "apiKeyHelper": apikey_helper_command(&dir, &id) }),
|
||||||
)?;
|
)?;
|
||||||
write_pool_json(&dir, name, "apikey")?;
|
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)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,7 +1293,9 @@ fn create_oauth_pool_in(paths: &Paths, name: &str) -> Result<String, String> {
|
|||||||
init_pool_config(&dir, serde_json::json!({}))?;
|
init_pool_config(&dir, serde_json::json!({}))?;
|
||||||
write_pool_json(&dir, name, "oauth")?;
|
write_pool_json(&dir, name, "oauth")?;
|
||||||
let id = dir.file_name().unwrap().to_string_lossy().into_owned();
|
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)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2611,18 +2627,36 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pool_anlegen_verlinkt_synced_runtime() {
|
fn pool_anlegen_verlinkt_synced_runtime() {
|
||||||
let p = tmp_paths();
|
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 id = make_oauth_pool(&p, "privat");
|
||||||
let pooldir = p.pool_dir(&id);
|
let pooldir = p.pool_dir(&id);
|
||||||
for (name, is_dir) in SYNCED_RUNTIME {
|
for (name, is_dir) in SYNCED_RUNTIME {
|
||||||
let link = pooldir.join(name);
|
let link = pooldir.join(name);
|
||||||
assert!(link.is_symlink(), "{name} sollte Symlink sein");
|
assert!(link.is_symlink(), "{name} sollte Symlink sein");
|
||||||
assert_eq!(fs::read_link(&link).unwrap(), 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).join(name);
|
let target = pool_data_dir(&p, &id).unwrap().join(name);
|
||||||
assert_eq!(target.is_dir(), is_dir);
|
assert_eq!(target.is_dir(), is_dir);
|
||||||
assert!(target.exists());
|
assert!(target.exists());
|
||||||
}
|
}
|
||||||
// Zieldaten liegen im claude-projects-Repo unter pool/<id>/
|
// Zieldaten liegen unter dem konfigurierten poolSyncDir/<id>/
|
||||||
assert!(p.projects_dir().join("pool").join(&id).is_dir());
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user