debugger-cli 0.1.3

LLM-friendly debugger CLI using the Debug Adapter Protocol
Documentation
Based on the project structure and the goal to add `js-debug` (VS Code's built-in JavaScript/TypeScript debugger) support, here is the implementation plan.

The plan involves:

1. **Creating a new adapter installer** (`src/setup/adapters/js_debug.rs`) that downloads the `ms-vscode.js-debug` VSIX from GitHub releases, extracts it, and verifies `node` is available.
2. **Registering the adapter** in `src/setup/registry.rs`.
3. **Updating project detection** in `src/setup/detector.rs` to recommend this debugger for Node.js/TypeScript projects.
4. **Exposing the new module** in `src/setup/adapters/mod.rs`.

### 1. Create `src/setup/adapters/js_debug.rs`

This file implements the `Installer` trait for `js-debug`. It handles finding `node`, downloading the VSIX, and configuring the execution command.

```rust
//! js-debug installer
//!
//! Installs the VS Code JavaScript debugger from GitHub releases.

use crate::common::{Error, Result};
use crate::setup::installer::{
    adapters_dir, download_file, ensure_adapters_dir, extract_zip, get_github_release,
    make_executable, run_command_args, write_version_file, InstallMethod, InstallOptions,
    InstallResult, InstallStatus, Installer,
};
use crate::setup::registry::{DebuggerInfo, Platform};
use crate::setup::verifier::{verify_dap_adapter, VerifyResult};
use async_trait::async_trait;
use std::path::PathBuf;

static INFO: DebuggerInfo = DebuggerInfo {
    id: "js-debug",
    name: "VS Code JavaScript Debugger",
    languages: &["javascript", "typescript"],
    platforms: &[Platform::Linux, Platform::MacOS, Platform::Windows],
    description: "Microsoft's JavaScript debugger (aka js-debug)",
    primary: true,
};

const GITHUB_REPO: &str = "microsoft/vscode-js-debug";

pub struct JsDebugInstaller;

#[async_trait]
impl Installer for JsDebugInstaller {
    fn info(&self) -> &DebuggerInfo {
        &INFO
    }

    async fn status(&self) -> Result<InstallStatus> {
        let adapter_dir = adapters_dir().join("js-debug");
        let server_path = find_dap_server(&adapter_dir);

        if server_path.exists() {
             // Check if node is available
            if let Ok(node_path) = find_node().await {
                let version = crate::setup::installer::read_version_file(&adapter_dir);
                return Ok(InstallStatus::Installed {
                    path: node_path, // We run 'node', passing the server script as arg
                    version,
                });
            } else {
                 return Ok(InstallStatus::Broken {
                    path: server_path,
                    reason: "Node.js not found in PATH".to_string(),
                });
            }
        }

        Ok(InstallStatus::NotInstalled)
    }

    async fn best_method(&self) -> Result<InstallMethod> {
        Ok(InstallMethod::GitHubRelease {
            repo: GITHUB_REPO.to_string(),
            asset_pattern: "ms-vscode.js-debug-*.vsix".to_string(),
        })
    }

    async fn install(&self, opts: InstallOptions) -> Result<InstallResult> {
        install_from_github(&opts).await
    }

    async fn uninstall(&self) -> Result<()> {
        let adapter_dir = adapters_dir().join("js-debug");
        if adapter_dir.exists() {
            std::fs::remove_dir_all(&adapter_dir)?;
            println!("Removed {}", adapter_dir.display());
        } else {
            println!("js-debug is not installed");
        }
        Ok(())
    }

    async fn verify(&self) -> Result<VerifyResult> {
        let status = self.status().await?;

        match status {
            InstallStatus::Installed { path: node_path, .. } => {
                let adapter_dir = adapters_dir().join("js-debug");
                let server_path = find_dap_server(&adapter_dir);
                
                // js-debug DAP server runs via: node <path/to/dapDebugServer.js>
                let args = vec![server_path.to_string_lossy().to_string()];
                
                verify_dap_adapter(&node_path, &args).await
            }
            InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
                success: false,
                capabilities: None,
                error: Some(reason),
            }),
            InstallStatus::NotInstalled => Ok(VerifyResult {
                success: false,
                capabilities: None,
                error: Some("Not installed".to_string()),
            }),
        }
    }
}

async fn find_node() -> Result<PathBuf> {
    which::which("node")
        .map_err(|_| Error::Internal("Node.js not found. Please install Node.js (v14+) first.".to_string()))
}

fn find_dap_server(adapter_dir: &std::path::Path) -> PathBuf {
    // The VSIX extracts to an 'extension' folder usually
    // Server entry point is typically src/dapDebugServer.js within the extension folder
    adapter_dir.join("extension").join("src").join("dapDebugServer.js")
}

async fn install_from_github(opts: &InstallOptions) -> Result<InstallResult> {
    println!("Checking for existing installation... not found");
    
    // Ensure Node is present
    let node_path = find_node().await?;
    println!("Using Node.js: {}", node_path.display());

    println!("Finding latest js-debug release...");
    let release = get_github_release(GITHUB_REPO, opts.version.as_deref()).await?;
    let version = release.tag_name.trim_start_matches('v').to_string();
    println!("Found version: {}", version);

    // Find the vsix asset
    let asset = release
        .find_asset(&["ms-vscode.js-debug-*.vsix"])
        .ok_or_else(|| {
            Error::Internal(format!(
                "No js-debug VSIX found in release {}.",
                version
            ))
        })?;

    // Download and extract
    let temp_dir = tempfile::tempdir()?;
    let archive_path = temp_dir.path().join(&asset.name);

    println!(
        "Downloading {}... {:.1} MB",
        asset.name,
        asset.size as f64 / 1_000_000.0
    );
    download_file(&asset.browser_download_url, &archive_path).await?;

    println!("Extracting...");
    let adapter_dir = ensure_adapters_dir()?.join("js-debug");
    if adapter_dir.exists() {
        std::fs::remove_dir_all(&adapter_dir)?;
    }
    std::fs::create_dir_all(&adapter_dir)?;

    // Extract vsix (it's a zip)
    extract_zip(&archive_path, &adapter_dir)?;

    let server_path = find_dap_server(&adapter_dir);
    if !server_path.exists() {
        return Err(Error::Internal(format!(
            "DAP server script not found at expected location: {}",
            server_path.display()
        )));
    }

    // Write version file
    write_version_file(&adapter_dir, &version)?;

    println!("Verifying installation...");

    Ok(InstallResult {
        path: node_path,
        version: Some(version),
        args: vec![server_path.to_string_lossy().to_string()],
    })
}

```

### 2. Update `src/setup/adapters/mod.rs`

Register the new module.

```rust
//! Debug adapter installers
//!
//! Individual installers for each supported debug adapter.

pub mod codelldb;
pub mod debugpy;
pub mod delve;
pub mod lldb;
pub mod js_debug; // Add this line

```

### 3. Update `src/setup/registry.rs`

Register the new adapter in the global list and factory function.

```rust
// ... imports

static DEBUGGERS: &[DebuggerInfo] = &[
    // ... existing adapters ...
    DebuggerInfo {
        id: "js-debug",
        name: "VS Code JavaScript Debugger",
        languages: &["javascript", "typescript"],
        platforms: &[Platform::Linux, Platform::MacOS, Platform::Windows],
        description: "Microsoft's JavaScript debugger (aka js-debug)",
        primary: true,
    },
];

// ...

pub fn get_installer(id: &str) -> Option<Arc<dyn Installer>> {
    use super::adapters;

    match id {
        "lldb" => Some(Arc::new(adapters::lldb::LldbInstaller)),
        "codelldb" => Some(Arc::new(adapters::codelldb::CodeLldbInstaller)),
        "python" => Some(Arc::new(adapters::debugpy::DebugpyInstaller)),
        "go" => Some(Arc::new(adapters::delve::DelveInstaller)),
        "js-debug" => Some(Arc::new(adapters::js_debug::JsDebugInstaller)), // Add this match arm
        _ => None,
    }
}

```

### 4. Update `src/setup/detector.rs`

Update the project detector to recommend `js-debug`.

```rust
pub fn debuggers_for_project(project: &ProjectType) -> Vec<&'static str> {
    match project {
        ProjectType::Rust => vec!["codelldb", "lldb"],
        ProjectType::Go => vec!["go"],
        ProjectType::Python => vec!["python"],
        ProjectType::JavaScript | ProjectType::TypeScript => vec!["js-debug"], // Update this line
        ProjectType::C | ProjectType::Cpp => vec!["lldb", "codelldb"],
        ProjectType::CSharp => vec![],
        ProjectType::Java => vec![],
    }
}
```