feat: skills import — 从其他工具复制 Skills 的交互式 TUI
新增: - skills_importer.rs: 扫描已知路径 (Claude Code, Agent Skills, OpenCode, pi) 发现可导入的 Skill - ias skills list: 列出可导入的 Skill - ias skills import: 交互式多选 TUI (dialoguer MultiSelect) 复制选中的 Skill 到 skills/ 目录 依赖: dialoguer + console + dirs
This commit is contained in:
+68
-1
@@ -5,12 +5,13 @@ mod db;
|
||||
mod llm;
|
||||
mod logger;
|
||||
mod scheduler;
|
||||
mod skills_importer;
|
||||
mod state;
|
||||
mod tools;
|
||||
mod wechat;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands};
|
||||
use cli::{Cli, Commands, SkillsAction};
|
||||
use config::AppConfig;
|
||||
use context::MemoryStore;
|
||||
use db::Database;
|
||||
@@ -90,6 +91,9 @@ async fn main() {
|
||||
Commands::Service => {
|
||||
cmd_listen(true, false, &database, &file_state, &config, &memory_store).await
|
||||
}
|
||||
Commands::Skills { action } => {
|
||||
cmd_skills(action).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,3 +753,66 @@ async fn cmd_send(
|
||||
Err(e) => error!("发送失败: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn cmd_skills(action: SkillsAction) {
|
||||
use dialoguer::{MultiSelect, theme::ColorfulTheme};
|
||||
use console::Term;
|
||||
|
||||
match action {
|
||||
SkillsAction::List => {
|
||||
let discovered = skills_importer::discover_skills();
|
||||
if discovered.is_empty() {
|
||||
println!("未发现可导入的 Skill");
|
||||
return;
|
||||
}
|
||||
println!("发现 {} 个可导入的 Skill:", discovered.len());
|
||||
for (source, name, _path) in &discovered {
|
||||
println!(" [{source}] {name}");
|
||||
}
|
||||
}
|
||||
SkillsAction::Import => {
|
||||
let discovered = skills_importer::discover_skills();
|
||||
if discovered.is_empty() {
|
||||
println!("未发现可导入的 Skill");
|
||||
return;
|
||||
}
|
||||
|
||||
let items: Vec<String> = discovered.iter()
|
||||
.map(|(src, name, _)| format!("[{}] {}", src, name))
|
||||
.collect();
|
||||
|
||||
println!("发现 {} 个可导入的 Skill(空格选择,回车确认,q 退出):", discovered.len());
|
||||
let selection = MultiSelect::with_theme(&ColorfulTheme::default())
|
||||
.items(&items)
|
||||
.interact_on(&Term::stderr())
|
||||
.unwrap_or_default();
|
||||
|
||||
if selection.is_empty() {
|
||||
println!("未选择任何 Skill");
|
||||
return;
|
||||
}
|
||||
|
||||
let dest = std::path::PathBuf::from("skills");
|
||||
std::fs::create_dir_all(&dest).ok();
|
||||
|
||||
let mut imported = 0;
|
||||
let mut skipped = 0;
|
||||
for idx in selection {
|
||||
let (src, name, path) = &discovered[idx];
|
||||
println!(" 导入 [{src}] {name}...");
|
||||
match skills_importer::copy_skill(path, name, &dest) {
|
||||
Ok(_) => {
|
||||
println!(" ✅ 已导入");
|
||||
imported += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ⚠️ {e}");
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("\n完成:导入 {imported} 个,跳过 {skipped} 个");
|
||||
println!("重启 iAs 后生效");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user