Skip to main content

nexus_core/app/
skills_popup.rs

1use super::App;
2use tokio::sync::mpsc;
3
4impl App {
5    /// Re-read installed skills from disk (after an install/remove, or a
6    /// Ctrl+E hand-edit of a SKILL.md). The view re-clamps its cursor after
7    /// calling this.
8    pub fn reload_skills(&mut self) {
9        self.skills = crate::skills::load_skills(&crate::skills::skills_dir(&self.space.root));
10    }
11
12    /// Domain half of `/skills` install: parse the typed `owner/repo/path`
13    /// (or `owner/repo`) and kick off the background GitHub fetch. Same
14    /// bg-task shape as memory extraction. The view owns the edit buffer and
15    /// mode; `Ok(())` means the task started (or the spec was invalid — the
16    /// message is pushed as a status line).
17    pub fn start_skill_install(&mut self, spec: &str) {
18        let spec = spec.trim().to_string();
19        let Some((owner, repo, path)) = crate::skills::parse_gh_shorthand(&spec) else {
20            self.push_status(format!("expected owner/repo/path, got: {spec}"));
21            return;
22        };
23        let dest = crate::skills::skills_dir(&self.space.root);
24        let (tx, rx) = mpsc::unbounded_channel();
25        self.skills_rx = Some(rx);
26        self.push_status(format!("installing {spec}…"));
27        tokio::spawn(async move {
28            let client = reqwest::Client::new();
29            let result = crate::skills::install_from_github(&client, &owner, &repo, &path, &dest)
30                .await
31                .map_err(|e| e.to_string());
32            let _ = tx.send(result);
33        });
34    }
35
36    pub fn on_skill_install_result(&mut self, result: Option<Result<String, String>>) {
37        self.skills_rx = None;
38        match result {
39            Some(Ok(name)) => {
40                self.reload_skills();
41                self.push_status(format!("installed skill: {name}"));
42            }
43            Some(Err(e)) => self.push_status(format!("skill install failed: {e}")),
44            None => {}
45        }
46    }
47}