Panel: Entwürfe archivieren, Titel aus Überschrift, Titel-Edit

- Archiv pro Projekt: Panel-Button „Archivieren" und MCP-Tool archive_panel
  legen den Entwurf als <YYYY-MM-DD_HHMM>-<slug>.md mit Frontmatter im
  Archiv-Ordner ab. Ordner in ai-control.json (archiveDir) + als
  additionalDirectories/Edit in .claude/settings.json (scanbar). Kein Ordner:
  Panel öffnet Ordner-Dialog, Zuruf nutzt Argument dir.
- Panel-Titel zeigt die erste Überschrift des Entwurfs (Inline-Markdown
  gestrippt), Fallback „Entwurf"; Bleistift-Button macht ihn editierbar,
  schreibt die Überschrift via panel_set zurück.
- Panel-Kopf zweizeilig: Titel + Edit oben, Aktionen rechtsbündig darunter.
- AI_CONTROL_PROJECT als PTY-Env für den MCP-Server.
This commit is contained in:
marcus.hinz
2026-07-11 13:40:17 +02:00
parent 8aa758ae64
commit e2e9992b1c
8 changed files with 508 additions and 44 deletions
+66 -22
View File
@@ -58,25 +58,48 @@ fn handle(method: &str, req: &Value) -> Option<Value> {
}))
}
"tools/list" => Some(json!({
"tools": [{
"name": "write_panel",
"description":
"Legt einen längeren Entwurf (ADR, E-Mail, Dokument, Spezifikation, \
Commit-Message, Textbaustein) im ai-control-Panel neben dem Terminal \
ab, wo er mit der Maus selektierbar und über einen Button kopierbar \
ist. Statt den Text zusätzlich als Fließtext auszugeben, dieses Tool \
aufrufen und im Chat nur kurz bestätigen.",
"inputSchema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Vollständiger Entwurf als Markdown-Rohtext.",
}
"tools": [
{
"name": "write_panel",
"description":
"Legt einen längeren Entwurf (ADR, E-Mail, Dokument, Spezifikation, \
Commit-Message, Textbaustein) im ai-control-Panel neben dem Terminal \
ab, wo er mit der Maus selektierbar und über einen Button kopierbar \
ist. Statt den Text zusätzlich als Fließtext auszugeben, dieses Tool \
aufrufen und im Chat nur kurz bestätigen.",
"inputSchema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Vollständiger Entwurf als Markdown-Rohtext.",
}
},
"required": ["text"],
},
"required": ["text"],
},
}],
{
"name": "archive_panel",
"description":
"Archiviert den aktuell im Panel liegenden Entwurf dauerhaft als \
Markdown-Datei im Archiv-Ordner des Projekts. Auf Wunsch nutzen \
(Nutzer sagt etwa „archiviere das“). Ist kein Ordner konfiguriert, \
den Zielpfad im \
Argument `dir` mitgeben (der Nutzer nennt ihn); ohne `dir` und ohne \
konfigurierten Ordner meldet das Tool das zurück.",
"inputSchema": {
"type": "object",
"properties": {
"dir": {
"type": "string",
"description":
"Optionaler Archiv-Ordner. Wird gesetzt und für künftige \
Archivierungen gemerkt.",
}
},
},
},
],
})),
"tools/call" => Some(call_tool(req)),
"ping" => Some(json!({})),
@@ -85,13 +108,17 @@ fn handle(method: &str, req: &Value) -> Option<Value> {
}
fn call_tool(req: &Value) -> Value {
let name = req["params"]["name"].as_str().unwrap_or("");
if name != "write_panel" {
return json!({
"content": [{ "type": "text", "text": format!("Unbekanntes Tool: {name}") }],
match req["params"]["name"].as_str().unwrap_or("") {
"write_panel" => call_write(req),
"archive_panel" => call_archive(req),
other => json!({
"content": [{ "type": "text", "text": format!("Unbekanntes Tool: {other}") }],
"isError": true,
});
}),
}
}
fn call_write(req: &Value) -> Value {
let text = req["params"]["arguments"]["text"].as_str().unwrap_or("");
match std::env::var("AI_CONTROL_PANEL") {
Ok(path) => match std::fs::write(&path, text) {
@@ -107,3 +134,20 @@ fn call_tool(req: &Value) -> Value {
}),
}
}
fn call_archive(req: &Value) -> Value {
let project = std::env::var("AI_CONTROL_PROJECT").unwrap_or_default();
let dir = req["params"]["arguments"]["dir"].as_str();
match crate::archive_panel_content(&project, dir) {
Ok(path) => json!({
"content": [{ "type": "text", "text": format!("Archiviert: {}", path.display()) }],
}),
Err(e) => json!({
"content": [{
"type": "text",
"text": format!("Nicht archiviert: {e}. Zielordner im Argument `dir` angeben oder im Panel wählen."),
}],
"isError": true,
}),
}
}