sid-isnt-done 0.2.0

sid is a UNIX-inspired coding agent for Anthropic-compatible APIs
Documentation
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

use utf8path::Path;

use crate::config::{TOOL_PROTOCOL_VERSION, TOOLS_DIR};

pub(crate) fn temp_config_root(prefix: &str) -> Path<'static> {
    let root = unique_temp_dir(prefix);
    fs::create_dir_all(root.as_str()).unwrap();
    root
}

pub(crate) fn unique_temp_dir(prefix: &str) -> Path<'static> {
    static NEXT_ID: AtomicU64 = AtomicU64::new(0);
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let sequence = NEXT_ID.fetch_add(1, Ordering::Relaxed);
    Path::try_from(
        std::env::temp_dir().join(format!("sid-isnt-done-{prefix}-{timestamp}-{sequence}")),
    )
    .unwrap()
    .into_owned()
}

pub(crate) fn write_tool_script(root: &Path, tool: &str, body: &str) {
    fs::create_dir_all(root.join(TOOLS_DIR).as_str()).unwrap();
    let path = root.join(format!("{TOOLS_DIR}/{tool}")).into_owned();
    fs::write(path.as_str(), body).unwrap();
    make_executable(&path);
}

pub(crate) fn write_tool_manifest(
    root: &Path,
    tool: &str,
    protocol_version: u32,
    description: &str,
) {
    write_tool_manifest_with_schema(
        root,
        tool,
        protocol_version,
        description,
        serde_json::json!({
            "type": "object",
            "properties": {
                "paths": {
                    "type": "array",
                    "items": { "type": "string" }
                }
            },
            "required": ["paths"]
        }),
    );
}

pub(crate) fn write_default_tool_manifest(root: &Path, tool: &str, description: &str) {
    write_tool_manifest(root, tool, TOOL_PROTOCOL_VERSION, description);
}

pub(crate) fn write_tool_manifest_with_schema(
    root: &Path,
    tool: &str,
    protocol_version: u32,
    description: &str,
    input_schema: serde_json::Value,
) {
    fs::create_dir_all(root.join(TOOLS_DIR).as_str()).unwrap();
    fs::write(
        root.join(format!("{TOOLS_DIR}/{tool}.json")).as_str(),
        serde_json::to_vec_pretty(&serde_json::json!({
            "protocol_version": protocol_version,
            "description": description,
            "input_schema": input_schema
        }))
        .unwrap(),
    )
    .unwrap();
}

#[cfg(unix)]
pub(crate) fn make_executable(path: &Path) {
    let mut permissions = fs::metadata(path.as_str()).unwrap().permissions();
    permissions.set_mode(0o755);
    fs::set_permissions(path.as_str(), permissions).unwrap();
}

#[cfg(not(unix))]
pub(crate) fn make_executable(_path: &Path) {}