i-self 0.4.3

Personal developer-companion CLI: scans your repos, indexes code semantically, watches your activity, and moves AI-agent sessions between tools (Claude Code, Aider, Goose, OpenAI Codex CLI, Continue.dev, OpenCode).
#![allow(dead_code)]

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Repository information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Repository {
    pub id: i64,
    pub name: String,
    pub full_name: String,
    pub owner: String,
    pub description: Option<String>,
    pub url: String,
    pub clone_url: String,
    pub default_branch: String,
    pub is_private: bool,
    pub is_fork: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub pushed_at: Option<DateTime<Utc>>,
    pub stargazers_count: i64,
    pub language: Option<String>,
    pub languages: HashMap<String, i64>,
    pub topics: Vec<String>,
    pub size: i64,
    pub open_issues_count: i64,
    pub forks_count: i64,
}

impl Repository {
    pub fn from_octocrab(repo: &octocrab::models::Repository) -> Self {
        Self {
            id: repo.id.0 as i64,
            name: repo.name.clone(),
            full_name: repo.full_name.clone().unwrap_or_default(),
            owner: repo.owner.as_ref().map(|o| o.login.clone()).unwrap_or_default(),
            description: repo.description.clone(),
            url: repo.html_url.as_ref().map(|u| u.to_string()).unwrap_or_default(),
            clone_url: repo.clone_url.as_ref().map(|u| u.to_string()).unwrap_or_default(),
            default_branch: repo.default_branch.clone().unwrap_or_else(|| "main".to_string()),
            is_private: repo.private.unwrap_or(false),
            is_fork: repo.fork.unwrap_or(false),
            created_at: repo.created_at.unwrap_or_else(Utc::now),
            updated_at: repo.updated_at.unwrap_or_else(Utc::now),
            pushed_at: repo.pushed_at,
            stargazers_count: repo.stargazers_count.unwrap_or(0) as i64,
            language: repo.language.as_ref().and_then(|v| v.as_str()).map(|s| s.to_string()),
            languages: HashMap::new(),
            topics: repo.topics.clone().unwrap_or_default(),
            size: repo.size.unwrap_or(0) as i64,
            open_issues_count: repo.open_issues_count.unwrap_or(0) as i64,
            forks_count: repo.forks_count.unwrap_or(0) as i64,
        }
    }
}

/// Branch information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Branch {
    pub name: String,
    pub commit_sha: String,
    pub commit_url: String,
    pub protected: bool,
    pub last_commit_date: Option<DateTime<Utc>>,
    pub last_commit_message: Option<String>,
    pub last_commit_author: Option<String>,
}

impl Branch {
    pub fn from_octocrab(branch: &octocrab::models::repos::Branch) -> Self {
        Self {
            name: branch.name.clone(),
            commit_sha: branch.commit.sha.clone(),
            commit_url: branch.commit.url.to_string(),
            protected: branch.protected,
            last_commit_date: None,
            last_commit_message: None,
            last_commit_author: None,
        }
    }
}

/// Commit information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Commit {
    pub sha: String,
    pub message: String,
    pub author_name: Option<String>,
    pub author_email: Option<String>,
    pub author_date: Option<DateTime<Utc>>,
    pub committer_name: Option<String>,
    pub committer_email: Option<String>,
    pub committer_date: Option<DateTime<Utc>>,
    pub url: String,
}

impl Commit {
    pub fn from_octocrab(commit: &octocrab::models::repos::RepoCommit) -> Self {
        // GitUserTime has a `user` field containing name and email
        let author_name = commit.commit.author.as_ref()
            .map(|a| a.user.name.clone());
        let author_email = commit.commit.author.as_ref()
            .map(|a| a.user.email.clone());
        let author_date = commit.commit.author.as_ref()
            .and_then(|a| a.date);
            
        let committer_name = commit.commit.committer.as_ref()
            .map(|c| c.user.name.clone());
        let committer_email = commit.commit.committer.as_ref()
            .map(|c| c.user.email.clone());
        let committer_date = commit.commit.committer.as_ref()
            .and_then(|c| c.date);

        Self {
            sha: commit.sha.clone(),
            message: commit.commit.message.clone(),
            author_name,
            author_email,
            author_date,
            committer_name,
            committer_email,
            committer_date,
            url: commit.html_url.to_string(),
        }
    }
}

/// Pull Request information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PullRequest {
    pub id: i64,
    pub number: i64,
    pub title: String,
    pub body: Option<String>,
    pub state: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub closed_at: Option<DateTime<Utc>>,
    pub merged_at: Option<DateTime<Utc>>,
    pub author: String,
    pub head_branch: String,
    pub base_branch: String,
    pub url: String,
    pub additions: i64,
    pub deletions: i64,
    pub changed_files: i64,
    pub labels: Vec<String>,
}

impl PullRequest {
    pub fn from_octocrab(pr: &octocrab::models::pulls::PullRequest) -> Self {
        Self {
            id: pr.id.0 as i64,
            number: pr.number as i64,
            title: pr.title.clone().unwrap_or_default(),
            body: pr.body.clone(),
            state: format!("{:?}", pr.state),
            created_at: pr.created_at.unwrap_or_else(Utc::now),
            updated_at: pr.updated_at.unwrap_or_else(Utc::now),
            closed_at: pr.closed_at,
            merged_at: pr.merged_at,
            author: pr.user.as_ref().map(|u| u.login.clone()).unwrap_or_default(),
            head_branch: pr.head.ref_field.clone(),
            base_branch: pr.base.ref_field.clone(),
            url: pr.html_url.as_ref().map(|u| u.to_string()).unwrap_or_default(),
            additions: pr.additions.map(|a| a as i64).unwrap_or(0),
            deletions: pr.deletions.map(|d| d as i64).unwrap_or(0),
            changed_files: pr.changed_files.map(|c| c as i64).unwrap_or(0),
            labels: Vec::new(),
        }
    }
}

/// Issue information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
    pub id: i64,
    pub number: i64,
    pub title: String,
    pub body: Option<String>,
    pub state: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub closed_at: Option<DateTime<Utc>>,
    pub author: String,
    pub url: String,
    pub labels: Vec<String>,
    pub comments_count: i64,
}

impl Issue {
    pub fn from_octocrab(issue: &octocrab::models::issues::Issue) -> Self {
        Self {
            id: issue.id.0 as i64,
            number: issue.number as i64,
            title: issue.title.clone(),
            body: issue.body.clone(),
            state: format!("{:?}", issue.state),
            created_at: issue.created_at,
            updated_at: issue.updated_at,
            closed_at: issue.closed_at,
            author: issue.user.login.clone(),
            url: issue.html_url.to_string(),
            labels: Vec::new(),
            comments_count: issue.comments as i64,
        }
    }
}

/// Language statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageStats {
    pub languages: HashMap<String, (i64, f64)>, // (bytes, percentage)
    pub total_bytes: i64,
}

/// Complete repository scan result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepositoryScan {
    pub repository: Repository,
    pub branches: Vec<Branch>,
    pub recent_commits: Vec<Commit>,
    pub language_stats: LanguageStats,
    pub pull_requests: Vec<PullRequest>,
    pub scan_timestamp: DateTime<Utc>,
}

/// Summary of GitHub activity
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GitHubSummary {
    pub total_repos: usize,
    pub total_branches: usize,
    pub total_commits_30d: usize,
    pub total_prs_open: usize,
    pub total_prs_merged_30d: usize,
    pub total_issues_open: usize,
    pub top_languages: Vec<(String, i64)>,
    pub most_active_repos: Vec<String>,
    pub recent_activity: Vec<ActivityEvent>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityEvent {
    pub event_type: String,
    pub repo: String,
    pub description: String,
    pub timestamp: DateTime<Utc>,
    pub url: Option<String>,
}