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 anyhow::Result;
use std::collections::VecDeque;
use std::time::{Duration, Instant};

use crate::monitor::MonitorConfig;

#[derive(Debug, Clone)]
pub struct UserActivity {
    pub active_window: Option<String>,
    pub keyboard_events: u64,
    pub mouse_clicks: u64,
    pub mouse_moves: u64,
    pub is_idle: bool,
    pub timestamp: Instant,
}

impl Default for UserActivity {
    fn default() -> Self {
        Self {
            active_window: None,
            keyboard_events: 0,
            mouse_clicks: 0,
            mouse_moves: 0,
            is_idle: false,
            timestamp: Instant::now(),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivityType {
    Keyboard,
    MouseMove,
    MouseClick,
    WindowChange,
    Idle,
}

pub struct InputTracker {
    config: MonitorConfig,
    keyboard_count: u64,
    mouse_moves: u64,
    mouse_clicks: u64,
    active_window: Option<String>,
    last_activity: Instant,
    history: VecDeque<UserActivity>,
    history_duration: Duration,
}

impl InputTracker {
    pub fn new(config: MonitorConfig) -> Result<Self> {
        Ok(Self {
            config,
            keyboard_count: 0,
            mouse_moves: 0,
            mouse_clicks: 0,
            active_window: None,
            last_activity: Instant::now(),
            history: VecDeque::new(),
            history_duration: Duration::from_secs(60),
        })
    }

    pub fn record_keyboard(&mut self) {
        self.keyboard_count += 1;
        self.last_activity = Instant::now();
    }

    pub fn record_mouse_move(&mut self) {
        self.mouse_moves += 1;
        self.last_activity = Instant::now();
    }

    pub fn record_mouse_click(&mut self) {
        self.mouse_clicks += 1;
        self.last_activity = Instant::now();
    }

    pub fn record_window_change(&mut self, window_title: String) {
        self.active_window = Some(window_title);
        self.last_activity = Instant::now();
    }

    pub fn get_current_activity(&self) -> UserActivity {
        let is_idle = self.last_activity.elapsed().as_secs() > self.config.idle_threshold_secs;

        UserActivity {
            active_window: self.active_window.clone(),
            keyboard_events: self.keyboard_count,
            mouse_clicks: self.mouse_clicks,
            mouse_moves: self.mouse_moves,
            is_idle,
            timestamp: Instant::now(),
        }
    }

    pub fn reset_counters(&mut self) {
        self.keyboard_count = 0;
        self.mouse_moves = 0;
        self.mouse_clicks = 0;
    }

    pub fn get_active_window() -> Option<String> {
        #[cfg(target_os = "macos")]
        {
            std::process::Command::new("osascript")
                .args(["-e", "tell application \"System Events\" to get name of first process whose frontmost is true"])
                .output()
                .ok()
                .and_then(|o| String::from_utf8(o.stdout).ok())
                .map(|s| s.trim().to_string())
        }

        #[cfg(target_os = "linux")]
        {
            std::process::Command::new("xdotool")
                .arg("getactivewindow")
                .arg("getwindowname")
                .output()
                .ok()
                .and_then(|o| String::from_utf8(o.stdout).ok())
                .map(|s| s.trim().to_string())
        }

        #[cfg(target_os = "windows")]
        {
            use std::process::Command;
            let script = r#"
                Add-Type @"
                using System;
                using System.Runtime.InteropServices;
                using System.Text;
                public class Win32 {
                    [DllImport("user32.dll")]
                    public static extern IntPtr GetForegroundWindow();
                    [DllImport("user32.dll")]
                    public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
                }
"@
                $hwnd = [Win32]::GetForegroundWindow()
                $title = New-Object System.Text.StringBuilder 256
                [Win32]::GetWindowText($hwnd, $title, 256) | Out-Null
                $title.ToString()
            "#;
            Command::new("powershell")
                .args(["-Command", script])
                .output()
                .ok()
                .and_then(|o| String::from_utf8(o.stdout).ok())
                .map(|s| s.trim().to_string())
        }

        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
        {
            None
        }
    }

}