From 28b2cceefa4b94c2d18d2663227fabb79cdbd015 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Mon, 10 Aug 2026 15:01:34 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20/agy:status=20=E2=80=94=20diagnostica?= =?UTF-8?q?=20estensione=20in=20overlay=20TUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Overlay TUI (ctx.ui.custom) che mostra: binario agy + versione, chiave Gemini (valida/mancante, mascherata), modello attivo, backend STT, notifiche TTS e ultimo errore significativo dal log di agy. - Parsing log migliorato: filtra il rumore di glog e cattura errori reali (permission denied, quota, 404, timeout). - Chiusura con Enter o Esc. README aggiornato. --- README.md | 11 ++++ extensions/index.ts | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/README.md b/README.md index 86bf0ad..fcfe953 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,17 @@ Apre un overlay centrato nel terminale: È il modo interattivo e sicuro per impostare `geminiApiKey` senza digitarla in chiaro nella cronologia del terminale. +### Diagnostica (`/agy:status`) + +Il comando `/agy:status` apre un overlay TUI con lo stato dell'estensione: + +- binario agy (trovato/non trovato) e versione +- chiave Gemini valida/mancante (mascherata) e stato +- modello attivo, backend STT, notifiche TTS +- ultimo errore significativo dai log di agy + +Chiudi con `Enter` o `Esc`. + | Chiave | Default | Descrizione | |---|---|---| | `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) | diff --git a/extensions/index.ts b/extensions/index.ts index 2dc60bf..9158258 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -1781,4 +1781,131 @@ export default function agyExtension(pi: ExtensionAPI) { } }, }); + + // ========================================================================= + // Comando: /agy:status — diagnostica estensione in un overlay TUI + // Mostra: binario agy, versione, chiave Gemini, modello attivo, ultimo + // errore dal log, stato config. Chiuso con Enter o Esc. + // ========================================================================= + pi.registerCommand("agy:status", { + description: "Mostra lo stato dell'estensione (agi, chiave, modello, ultimo errore)", + handler: async (_args, ctx) => { + const { Container, Text, matchesKey, Key } = await import("@earendil-works/pi-tui"); + const { DynamicBorder } = await import("@earendil-works/pi-coding-agent"); + + // ---- raccolta diagnostica ---- + const agyBin = findAgy(); + const agyExists = + agyBin === "agy" ? fs.existsSync("/home/enne2/.local/bin/agy") : fs.existsSync(agyBin); + let agyVersion = "(sconosciuta)"; + if (agyExists) { + try { + agyVersion = + ( + await execFileAsync(agyBin, ["--version"], { + timeout: 8000, + env: { ...process.env, NO_COLOR: "1" }, + }) + ).stdout.trim() || "(sconosciuta)"; + } catch { + agyVersion = "(errore esecuzione)"; + } + } + + const cfgKey = getConfig("geminiApiKey") ?? ""; + const fileKey = (() => { + try { + const f = path.join(AGY_CHAT_DIR, "gemini-key"); + return fs.existsSync(f) ? fs.readFileSync(f, "utf8").trim() : ""; + } catch { + return ""; + } + })(); + const key = cfgKey || fileKey; + const keyStatus = key ? "✅ valida" : "❌ mancante (usa /agy:key)"; + const keyMask = key ? `${key.slice(0, 4)}...${key.slice(-4)}` : "—"; + const model = getConfig("agyDefaultModel") || "(default agy)"; + const sttBackend = getConfig("sttBackend") ?? "gemini"; + const ttsNotify = getConfig("ttsNotify") ?? "true"; + + // ultimo errore dal log agy (ultima riga con 'ERROR'/'denied'/'quota') + let lastError = "(nessuno)"; + try { + const logDir = path.join(os.homedir(), ".gemini", "antigravity-cli", "log"); + if (fs.existsSync(logDir)) { + const logs = fs + .readdirSync(logDir) + .map((f) => path.join(logDir, f)) + .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs); + const logFile = logs[0]; + if (logFile) { + const lines = fs.readFileSync(logFile, "utf8").split("\n"); + const meaningful = [...lines] + .reverse() + .filter((l) => /error|denied|quota|failed|timed out/i.test(l)) + .filter((l) => !/^ERROR: logging before google\.Init:/i.test(l.trim())); + if (meaningful.length) { + const raw = meaningful[0]; + // estrai il messaggio dopo il pattern glog: "go:file.go:NN] msg" oppure dopo ":" + const m = raw.match(/\][\s]*\s*\s*(.*)$/) || raw.match(/:\s*(.*)$/i); + lastError = (m ? m[1] : raw).replace(/^ERROR:|^WARN:/i, "").trim().slice(0, 90) || "(vedi log)"; + } + } + } + } catch { + lastError = "(non disponibile)"; + } + + // ---- overlay TUI ---- + await ctx.ui.custom( + (tui, theme, _keybindings, done) => { + const container = new Container(); + const render = () => { + container.clear(); + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + container.addChild( + new Text(theme.fg("accent", theme.bold("📊 Stato agy-pi")), 1, 1), + ); + container.addChild(new Text("", 0, 0)); + const line = (label: string, v: string, color: "success" | "error" | "text" | "muted" | "accent" | "warning" | "dim") => + container.addChild( + new Text(theme.fg("dim", `${label}: `) + theme.fg(color, v), 1, 0), + ); + line("Binario agy", agyExists ? `trovato ${agyBin}` : "❌ NON trovato", + agyExists ? "success" : "error"); + line("Versione", agyVersion, "text"); + line("Chiave Gemini", keyStatus, key ? "success" : "error"); + line("Chiave (mask)", keyMask, "muted"); + line("Modello attivo", model, "accent"); + line("Backend STT", sttBackend, "text"); + line("Notifiche TTS", ttsNotify, "text"); + container.addChild(new Text("", 0, 0)); + container.addChild( + new Text(theme.fg("warning", "Ultimo errore:") + " " + theme.fg("text", lastError), 1, 0), + ); + container.addChild(new Text("", 0, 0)); + container.addChild( + new Text(theme.fg("dim", "Enter o Esc per chiudere"), 1, 0), + ); + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + }; + render(); + return { + render: (w) => { + render(); + return container.render(w); + }, + invalidate: () => container.invalidate(), + handleInput: (data) => { + if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) done(); + }, + }; + }, + { + overlay: true, + overlayOptions: { width: "55%", minWidth: 56, anchor: "center" }, + }, + ); + }, + }); }