All checks were successful
Build AppImage / build (push) Successful in 7m51s
- KB-Hints werden automatisch in jeden Claude-Prompt injiziert - SQL-Queries berücksichtigen jetzt Priority (DESC) - Voice-zu-Claude-Pipeline: Sprache → Transkription → Claude → TTS - Hook-System feuert echte Events (SessionStart, Pre/PostToolUse) - Pattern-Detektion bei Tool-Fehlern aktiviert - Slash-Command Autocomplete mit CommandPalette - Updater abgesichert: Lock-Datei, Prozess-Guard, Bestätigungs-Dialog - ROADMAP.md und CHANGELOG.md aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
140 lines
4.8 KiB
Rust
140 lines
4.8 KiB
Rust
// Slash-Command Registry — scannt ~/.claude/commands/ und ~/.claude/skills/
|
|
// und liefert eine kombinierte Liste inkl. Built-in-Commands an das Frontend.
|
|
|
|
use serde::Serialize;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
/// Ein Slash-Command mit Metadaten
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct SlashCommand {
|
|
/// Name des Commands (ohne führenden Slash)
|
|
pub name: String,
|
|
/// Beschreibung (erste Zeile der .md-Datei oder fest hinterlegt)
|
|
pub description: String,
|
|
/// Kategorie: "builtin", "custom", "skill"
|
|
pub category: String,
|
|
/// Herkunft: Dateipfad oder "builtin"
|
|
pub source: String,
|
|
}
|
|
|
|
/// Liest die erste nicht-leere Zeile einer Datei als Beschreibung.
|
|
/// Entfernt dabei führende Markdown-Header-Zeichen (#).
|
|
fn read_first_line(path: &PathBuf) -> String {
|
|
match fs::read_to_string(path) {
|
|
Ok(content) => {
|
|
content
|
|
.lines()
|
|
.find(|line| !line.trim().is_empty())
|
|
.map(|line| line.trim().trim_start_matches('#').trim().to_string())
|
|
.unwrap_or_else(|| "Keine Beschreibung".into())
|
|
}
|
|
Err(_) => "Datei nicht lesbar".into(),
|
|
}
|
|
}
|
|
|
|
/// Scannt alle verfügbaren Slash-Commands aus verschiedenen Quellen
|
|
fn scan_commands() -> Vec<SlashCommand> {
|
|
let mut commands: Vec<SlashCommand> = Vec::new();
|
|
|
|
// 1. Built-in-Commands (hardcoded)
|
|
let builtins = vec![
|
|
("help", "Hilfe und verfügbare Commands anzeigen"),
|
|
("clear", "Chat-Verlauf leeren"),
|
|
("compact", "Konversation kompaktieren (Token sparen)"),
|
|
("model", "KI-Modell wechseln (Sonnet/Opus/Haiku)"),
|
|
("cost", "Aktuelle Token-Kosten anzeigen"),
|
|
("doctor", "System-Diagnose und Health-Check"),
|
|
("review", "Code-Review für aktuelles Projekt"),
|
|
];
|
|
|
|
for (name, desc) in builtins {
|
|
commands.push(SlashCommand {
|
|
name: name.to_string(),
|
|
description: desc.to_string(),
|
|
category: "builtin".to_string(),
|
|
source: "builtin".to_string(),
|
|
});
|
|
}
|
|
|
|
// Home-Verzeichnis ermitteln (ohne externe Dependency)
|
|
let home = match std::env::var("HOME") {
|
|
Ok(h) => PathBuf::from(h),
|
|
Err(_) => {
|
|
eprintln!("⚠️ HOME-Umgebungsvariable nicht gesetzt");
|
|
return commands;
|
|
}
|
|
};
|
|
|
|
// 2. Custom Commands aus ~/.claude/commands/*.md
|
|
let commands_dir = home.join(".claude").join("commands");
|
|
if commands_dir.is_dir() {
|
|
if let Ok(entries) = fs::read_dir(&commands_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path.extension().map_or(false, |ext| ext == "md") {
|
|
let name = path
|
|
.file_stem()
|
|
.unwrap_or_default()
|
|
.to_string_lossy()
|
|
.to_string();
|
|
let description = read_first_line(&path);
|
|
commands.push(SlashCommand {
|
|
name,
|
|
description,
|
|
category: "custom".to_string(),
|
|
source: path.to_string_lossy().to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Skills aus ~/.claude/skills/*/SKILL.md
|
|
let skills_dir = home.join(".claude").join("skills");
|
|
if skills_dir.is_dir() {
|
|
if let Ok(entries) = fs::read_dir(&skills_dir) {
|
|
for entry in entries.flatten() {
|
|
let skill_dir = entry.path();
|
|
if skill_dir.is_dir() {
|
|
let skill_md = skill_dir.join("SKILL.md");
|
|
if skill_md.exists() {
|
|
let name = skill_dir
|
|
.file_name()
|
|
.unwrap_or_default()
|
|
.to_string_lossy()
|
|
.to_string();
|
|
let description = read_first_line(&skill_md);
|
|
commands.push(SlashCommand {
|
|
name,
|
|
description,
|
|
category: "skill".to_string(),
|
|
source: skill_md.to_string_lossy().to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alphabetisch sortieren (Built-ins zuerst, dann Custom, dann Skills)
|
|
commands.sort_by(|a, b| {
|
|
let cat_order = |c: &str| match c {
|
|
"builtin" => 0,
|
|
"custom" => 1,
|
|
"skill" => 2,
|
|
_ => 3,
|
|
};
|
|
cat_order(&a.category)
|
|
.cmp(&cat_order(&b.category))
|
|
.then_with(|| a.name.cmp(&b.name))
|
|
});
|
|
|
|
commands
|
|
}
|
|
|
|
/// Tauri-Command: Gibt alle verfügbaren Slash-Commands zurück
|
|
#[tauri::command]
|
|
pub fn get_slash_commands() -> Vec<SlashCommand> {
|
|
scan_commands()
|
|
}
|