smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
//! Collaboration Module - Humans + AIs working together
//!
//! Each user (human or AI) brings their own skills to the project.
//! Users connect via i1.is or GitHub identity, get their own space
//! (optionally containerized), and collaborate in real-time.
//!
//! ## Philosophy
//! - **Work by default** - Don't block people, enable them
//! - **Bring your skills** - Each collaborator has unique abilities
//! - **Any machine** - Connect to any std daemon you have access to

pub mod identity;
pub mod permissions;
pub mod space;
pub mod templates;

pub use identity::{Identity, IdentityProvider};
pub use permissions::{AccessLevel, Permission, ProjectAccess};
pub use space::{UserSpace, SpaceConfig};
pub use templates::Template;

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// A collaborator - human or AI
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Collaborator {
    /// Their identity (i1.is, GitHub, or local)
    pub identity: Identity,

    /// What kind of collaborator
    pub kind: CollaboratorKind,

    /// Their preferred template/environment
    pub template: Option<String>,

    /// Skills they bring (for AI: tools they can use, for humans: expertise)
    pub skills: Vec<String>,

    /// Current status
    pub status: CollaboratorStatus,
}

/// Type of collaborator
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CollaboratorKind {
    /// Human developer
    Human,
    /// AI assistant (Claude, etc.)
    Ai { model: String },
    /// Automated system (CI, bots)
    System,
}

/// Current status of a collaborator
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CollaboratorStatus {
    /// Currently active in the space
    Active,
    /// Connected but idle
    Idle,
    /// Not currently connected
    Offline,
    /// Invited but hasn't joined yet
    Invited,
}

/// A collaborative project/workspace
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
    /// Project identifier (e.g., "hue@i1.is/smart-tree")
    pub id: String,

    /// Display name
    pub name: String,

    /// Owner identity
    pub owner: Identity,

    /// Path on the host machine
    pub path: PathBuf,

    /// Collaborators with their access levels
    pub collaborators: Vec<(Collaborator, AccessLevel)>,

    /// Project-level settings
    pub settings: ProjectSettings,
}

/// Project settings
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProjectSettings {
    /// Allow container isolation for users
    pub allow_containers: bool,

    /// Default template for new collaborators
    pub default_template: Option<String>,

    /// Sync memories to i1.is cloud
    pub cloud_sync: bool,

    /// Specific denies (not allows - work by default!)
    pub deny_patterns: Vec<String>,
}

impl Project {
    /// Create a new project
    pub fn new(name: &str, owner: Identity, path: PathBuf) -> Self {
        let id = format!("{}/{}", owner, name);
        Project {
            id,
            name: name.to_string(),
            owner,
            path,
            collaborators: Vec::new(),
            settings: ProjectSettings::default(),
        }
    }

    /// Add a collaborator
    pub fn add_collaborator(&mut self, collab: Collaborator, access: AccessLevel) {
        self.collaborators.push((collab, access));
    }

    /// Check if an identity has access
    pub fn can_access(&self, identity: &Identity) -> bool {
        // Owner always has access
        if &self.owner == identity {
            return true;
        }

        // Check collaborators
        self.collaborators.iter().any(|(c, _)| &c.identity == identity)
    }

    /// Get access level for an identity
    pub fn access_level(&self, identity: &Identity) -> AccessLevel {
        if &self.owner == identity {
            return AccessLevel::Owner;
        }

        self.collaborators
            .iter()
            .find(|(c, _)| &c.identity == identity)
            .map(|(_, level)| level.clone())
            .unwrap_or(AccessLevel::None)
    }
}

/// Join a remote project
pub async fn join_project(project_id: &str) -> Result<Project> {
    // Parse project ID: "user@i1.is/project" or "github:user/project"
    let (owner_str, project_name) = project_id
        .rsplit_once('/')
        .ok_or_else(|| anyhow::anyhow!("Invalid project ID format"))?;

    let owner = Identity::parse(owner_str)?;

    // TODO: Connect to remote daemon via STUN/relay
    // For now, return a placeholder
    Ok(Project::new(project_name, owner, PathBuf::from(".")))
}