1use crate::app_config::{InstalledSkill, SkillApps};
10use crate::database::{lock_conn, Database};
11use crate::error::AppError;
12use crate::services::skill::SkillRepo;
13use indexmap::IndexMap;
14use rusqlite::params;
15
16impl Database {
17 pub fn get_all_installed_skills(&self) -> Result<IndexMap<String, InstalledSkill>, AppError> {
21 let conn = lock_conn!(self.conn);
22 let mut stmt = conn
23 .prepare(
24 "SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
25 readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes, installed_at
26 FROM skills ORDER BY name ASC",
27 )
28 .map_err(|e| AppError::Database(e.to_string()))?;
29
30 let skill_iter = stmt
31 .query_map([], |row| {
32 Ok(InstalledSkill {
33 id: row.get(0)?,
34 name: row.get(1)?,
35 description: row.get(2)?,
36 directory: row.get(3)?,
37 repo_owner: row.get(4)?,
38 repo_name: row.get(5)?,
39 repo_branch: row.get(6)?,
40 readme_url: row.get(7)?,
41 apps: SkillApps {
42 claude: row.get(8)?,
43 codex: row.get(9)?,
44 gemini: row.get(10)?,
45 opencode: row.get(11)?,
46 hermes: row.get(12)?,
47 },
48 installed_at: row.get(13)?,
49 })
50 })
51 .map_err(|e| AppError::Database(e.to_string()))?;
52
53 let mut skills = IndexMap::new();
54 for skill_res in skill_iter {
55 let skill = skill_res.map_err(|e| AppError::Database(e.to_string()))?;
56 skills.insert(skill.id.clone(), skill);
57 }
58 Ok(skills)
59 }
60
61 pub fn get_installed_skill(&self, id: &str) -> Result<Option<InstalledSkill>, AppError> {
63 let conn = lock_conn!(self.conn);
64 let mut stmt = conn
65 .prepare(
66 "SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
67 readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes, installed_at
68 FROM skills WHERE id = ?1",
69 )
70 .map_err(|e| AppError::Database(e.to_string()))?;
71
72 let result = stmt.query_row([id], |row| {
73 Ok(InstalledSkill {
74 id: row.get(0)?,
75 name: row.get(1)?,
76 description: row.get(2)?,
77 directory: row.get(3)?,
78 repo_owner: row.get(4)?,
79 repo_name: row.get(5)?,
80 repo_branch: row.get(6)?,
81 readme_url: row.get(7)?,
82 apps: SkillApps {
83 claude: row.get(8)?,
84 codex: row.get(9)?,
85 gemini: row.get(10)?,
86 opencode: row.get(11)?,
87 hermes: row.get(12)?,
88 },
89 installed_at: row.get(13)?,
90 })
91 });
92
93 match result {
94 Ok(skill) => Ok(Some(skill)),
95 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
96 Err(e) => Err(AppError::Database(e.to_string())),
97 }
98 }
99
100 pub fn save_skill(&self, skill: &InstalledSkill) -> Result<(), AppError> {
102 let conn = lock_conn!(self.conn);
103 conn.execute(
104 "INSERT OR REPLACE INTO skills
105 (id, name, description, directory, repo_owner, repo_name, repo_branch,
106 readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes, installed_at)
107 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
108 params![
109 skill.id,
110 skill.name,
111 skill.description,
112 skill.directory,
113 skill.repo_owner,
114 skill.repo_name,
115 skill.repo_branch,
116 skill.readme_url,
117 skill.apps.claude,
118 skill.apps.codex,
119 skill.apps.gemini,
120 skill.apps.opencode,
121 skill.apps.hermes,
122 skill.installed_at,
123 ],
124 )
125 .map_err(|e| AppError::Database(e.to_string()))?;
126 Ok(())
127 }
128
129 pub fn delete_skill(&self, id: &str) -> Result<bool, AppError> {
131 let conn = lock_conn!(self.conn);
132 let affected = conn
133 .execute("DELETE FROM skills WHERE id = ?1", params![id])
134 .map_err(|e| AppError::Database(e.to_string()))?;
135 Ok(affected > 0)
136 }
137
138 pub fn clear_skills(&self) -> Result<(), AppError> {
140 let conn = lock_conn!(self.conn);
141 conn.execute("DELETE FROM skills", [])
142 .map_err(|e| AppError::Database(e.to_string()))?;
143 Ok(())
144 }
145
146 pub fn update_skill_apps(&self, id: &str, apps: &SkillApps) -> Result<bool, AppError> {
148 let conn = lock_conn!(self.conn);
149 let affected = conn
150 .execute(
151 "UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_opencode = ?4, enabled_hermes = ?5 WHERE id = ?6",
152 params![apps.claude, apps.codex, apps.gemini, apps.opencode, apps.hermes, id],
153 )
154 .map_err(|e| AppError::Database(e.to_string()))?;
155 Ok(affected > 0)
156 }
157
158 pub fn get_skill_repos(&self) -> Result<Vec<SkillRepo>, AppError> {
162 let conn = lock_conn!(self.conn);
163 let mut stmt = conn
164 .prepare(
165 "SELECT owner, name, branch, enabled FROM skill_repos ORDER BY owner ASC, name ASC",
166 )
167 .map_err(|e| AppError::Database(e.to_string()))?;
168
169 let repo_iter = stmt
170 .query_map([], |row| {
171 Ok(SkillRepo {
172 owner: row.get(0)?,
173 name: row.get(1)?,
174 branch: row.get(2)?,
175 enabled: row.get(3)?,
176 })
177 })
178 .map_err(|e| AppError::Database(e.to_string()))?;
179
180 let mut repos = Vec::new();
181 for repo_res in repo_iter {
182 repos.push(repo_res.map_err(|e| AppError::Database(e.to_string()))?);
183 }
184 Ok(repos)
185 }
186
187 pub fn save_skill_repo(&self, repo: &SkillRepo) -> Result<(), AppError> {
189 let conn = lock_conn!(self.conn);
190 conn.execute(
191 "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)",
192 params![repo.owner, repo.name, repo.branch, repo.enabled],
193 )
194 .map_err(|e| AppError::Database(e.to_string()))?;
195 Ok(())
196 }
197
198 pub fn delete_skill_repo(&self, owner: &str, name: &str) -> Result<(), AppError> {
200 let conn = lock_conn!(self.conn);
201 conn.execute(
202 "DELETE FROM skill_repos WHERE owner = ?1 AND name = ?2",
203 params![owner, name],
204 )
205 .map_err(|e| AppError::Database(e.to_string()))?;
206 Ok(())
207 }
208
209 pub fn init_default_skill_repos(&self) -> Result<usize, AppError> {
211 let existing = self.get_skill_repos()?;
213 let existing_keys: std::collections::HashSet<(String, String)> = existing
214 .iter()
215 .map(|r| (r.owner.clone(), r.name.clone()))
216 .collect();
217
218 let default_store = crate::services::skill::SkillStore::default();
220 let mut count = 0;
221
222 for repo in &default_store.repos {
224 let key = (repo.owner.clone(), repo.name.clone());
225 if !existing_keys.contains(&key) {
226 self.save_skill_repo(repo)?;
227 count += 1;
228 log::info!("补充默认 Skill 仓库: {}/{}", repo.owner, repo.name);
229 }
230 }
231
232 if count > 0 {
233 log::info!("补充默认 Skill 仓库完成,新增 {count} 个");
234 }
235 Ok(count)
236 }
237}