workon 0.24.6

Development workspace launcher with Zellij, Claude CLI, and branchdiff
Documentation
use std::path::{Path, PathBuf};

use anyhow::Result;
use serde_json::Value;

pub fn approve_workspace(ws_dir: &Path) -> Result<()> {
    let claude_json = home_dir()?.join(".claude.json");
    approve_workspace_at(&claude_json, ws_dir)
}

fn approve_workspace_at(claude_json: &Path, ws_dir: &Path) -> Result<()> {
    let ws_key = ws_dir.to_string_lossy().into_owned();

    let mut doc = if claude_json.is_file() {
        let content = std::fs::read_to_string(claude_json)?;
        match serde_json::from_str::<Value>(&content) {
            Ok(v) => v,
            Err(e) => {
                eprintln!("warning: could not parse ~/.claude.json: {e}");
                return Ok(());
            }
        }
    } else {
        Value::Object(serde_json::Map::new())
    };

    let projects =
        doc.as_object_mut().unwrap().entry("projects").or_insert_with(|| Value::Object(serde_json::Map::new()));

    let entry =
        projects.as_object_mut().unwrap().entry(&ws_key).or_insert_with(|| Value::Object(serde_json::Map::new()));

    entry.as_object_mut().unwrap().insert("hasTrustDialogAccepted".into(), Value::Bool(true));

    let output = serde_json::to_string_pretty(&doc)?;

    let parent = claude_json.parent().unwrap_or(Path::new("."));
    let tmp = tempfile::NamedTempFile::new_in(parent)?;
    std::fs::write(tmp.path(), &output)?;
    tmp.persist(claude_json)?;
    Ok(())
}

fn home_dir() -> Result<PathBuf> {
    crate::home::home_dir()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn creates_trust_entry_in_existing_json() {
        let tmp = tempfile::tempdir().unwrap();
        let claude_json = tmp.path().join(".claude.json");
        std::fs::write(&claude_json, r#"{"existingKey": true}"#).unwrap();

        let ws_dir = tmp.path().join(".worktrees/myproject-ws-abc123");
        approve_workspace_at(&claude_json, &ws_dir).unwrap();

        let content: Value = serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
        let ws_key = ws_dir.to_string_lossy().into_owned();
        assert_eq!(content["existingKey"], true);
        assert_eq!(content["projects"][&ws_key]["hasTrustDialogAccepted"], true);
    }

    #[test]
    fn creates_new_claude_json_if_missing() {
        let tmp = tempfile::tempdir().unwrap();
        let claude_json = tmp.path().join(".claude.json");
        let ws_dir = tmp.path().join(".worktrees/proj-ws-def456");

        approve_workspace_at(&claude_json, &ws_dir).unwrap();

        let content: Value = serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
        let ws_key = ws_dir.to_string_lossy().into_owned();
        assert_eq!(content["projects"][&ws_key]["hasTrustDialogAccepted"], true);
    }

    #[test]
    fn handles_malformed_json_gracefully() {
        let tmp = tempfile::tempdir().unwrap();
        let claude_json = tmp.path().join(".claude.json");
        std::fs::write(&claude_json, "not json{{{").unwrap();

        let ws_dir = tmp.path().join(".worktrees/proj-ws-bad");
        let result = approve_workspace_at(&claude_json, &ws_dir);
        assert!(result.is_ok());
    }
}