feat: /agy:status — diagnostica estensione in overlay TUI
- 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.
This commit is contained in:
@@ -60,6 +60,17 @@ Apre un overlay centrato nel terminale:
|
|||||||
È il modo interattivo e sicuro per impostare `geminiApiKey` senza digitarla in
|
È il modo interattivo e sicuro per impostare `geminiApiKey` senza digitarla in
|
||||||
chiaro nella cronologia del terminale.
|
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 |
|
| Chiave | Default | Descrizione |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) |
|
| `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) |
|
||||||
|
|||||||
@@ -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<void>(
|
||||||
|
(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" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user