From f7cc2372942d2f85ea8e79da7a7aa42c6be908f7 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Mon, 10 Aug 2026 14:37:07 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20/agy:key=20=E2=80=94=20dialog=20overlay?= =?UTF-8?q?=20TUI=20mascherato=20per=20la=20chiave=20API=20Gemini?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Comando /agy:key che apre un overlay TUI (ctx.ui.custom overlay) con campo mascherato per inserire la chiave API Gemini senza digitarla in chiaro. - Enter conferma: salva in config.json e ~/.agy-chat/gemini-key + suona chime. - Esc annulla, Backspace cancella, filtra caratteri consentiti per chiave API. - Import dinamico dei componenti pi-tui (Container, Text, DynamicBorder) per non rompere il load dell'estensione se pi-tui non fosse disponibile. - Aggiunto tsconfig.json per il typecheck con mapping ai moduli pi. - README: documentato il nuovo comando. --- README.md | 19 +++++++ extensions/index.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 22 ++++++++ 3 files changed, 172 insertions(+) create mode 100644 tsconfig.json diff --git a/README.md b/README.md index 88ab805..86bf0ad 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,25 @@ Tutte le impostazioni dell'estensione si gestiscono con il comando `/agy:config` /agy:config reset # ripristina i default ``` +### Dialog TUI per la chiave Gemini (`/agy:key`) + +Per inserire/modificare la **chiave API Gemini** con un'interfaccia grafica in +sovrimpressione (dialog TUI, campo mascherato) usa il comando `/agy:key`: + +``` +/agy:key +``` + +Apre un overlay centrato nel terminale: + +- mostra la chiave attuale mascherata (`AIza...yfDY`) +- campo di input **mascherato** (digiti la chiave, vedi `•`) +- `Enter` conferma (salva in config + `~/.agy-chat/gemini-key`) +- `Esc` annulla + +È il modo interattivo e sicuro per impostare `geminiApiKey` senza digitarla in +chiaro nella cronologia del terminale. + | Chiave | Default | Descrizione | |---|---|---| | `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) | diff --git a/extensions/index.ts b/extensions/index.ts index bac5319..2dc60bf 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -1650,4 +1650,135 @@ export default function agyExtension(pi: ExtensionAPI) { ); }, }); + + // ========================================================================= + // Comando: /agy:key — dialog overlay TUI per inserire/modificare la chiave + // API Gemini in modo interattivo (campo mascherato, Enter conferma, Esc + // annulla). Usa i componenti TUI di pi (overlay in sovrimpressione). + // ========================================================================= + pi.registerCommand("agy:key", { + description: + "Apre un dialog overlay per inserire/modificare la chiave API Gemini (campo mascherato)", + handler: async (_args, ctx) => { + // Import dinamico: se pi-tui non fosse disponibile, fallisce solo questo + // comando senza rompere il caricamento dell'intera estensione. + const { + Container, + Text, + matchesKey, + Key, + } = await import("@earendil-works/pi-tui"); + const { DynamicBorder } = await import("@earendil-works/pi-coding-agent"); + + const result = await ctx.ui.custom( + (tui, theme, _keybindings, done) => { + let value = ""; + const currentKey = getConfig("geminiApiKey") ?? ""; + const maskCurrent = currentKey + ? `${currentKey.slice(0, 4)}...${currentKey.slice(-4)}` + : "(non impostata)"; + const mask = (v: string) => "•".repeat(v.length); + + const container = new Container(); + const renderDialog = () => { + container.clear(); + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + container.addChild( + new Text(theme.fg("accent", theme.bold("🔑 Chiave API Gemini")), 1, 1), + ); + container.addChild( + new Text(theme.fg("dim", `Attuale: ${maskCurrent}`), 1, 0), + ); + container.addChild(new Text("", 0, 0)); + const field = value ? mask(value) : "(vuota)"; + container.addChild( + new Text( + theme.fg("text", "Nuova chiave: ") + theme.fg("warning", field), + 1, + 0, + (s) => theme.bg("toolPendingBg", s), + ), + ); + container.addChild(new Text("", 0, 0)); + container.addChild( + new Text( + theme.fg("dim", "Digita la chiave • Enter conferma • Esc annulla"), + 1, + 0, + ), + ); + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + }; + renderDialog(); + + return { + render: (w) => { + renderDialog(); + return container.render(w); + }, + invalidate: () => container.invalidate(), + handleInput: (data) => { + if (matchesKey(data, Key.enter)) { + const trimmed = value.trim(); + if (trimmed) { + setConfig("geminiApiKey", trimmed); + // Aggiorna anche ~/.agy-chat/gemini-key per coerenza con gli script TTS/STT + try { + fs.mkdirSync(AGY_CHAT_DIR, { recursive: true }); + fs.writeFileSync( + path.join(AGY_CHAT_DIR, "gemini-key"), + trimmed, + { mode: 0o600 }, + ); + } catch { + /* ignora */ + } + done(trimmed); + } + return; + } + if (matchesKey(data, Key.escape)) { + done(null); + return; + } + if (matchesKey(data, Key.backspace)) { + value = value.slice(0, -1); + tui.requestRender(); + return; + } + // Caratteri stampabili permessi per una chiave API (A-Z a-z 0-9 _ . -) + if (data.length === 1 && data.charCodeAt(0) >= 32) { + if (/^[A-Za-z0-9._-]+$/.test(data)) { + value += data; + } + tui.requestRender(); + } + }, + }; + }, + { + overlay: true, + overlayOptions: { + width: "50%", + minWidth: 54, + anchor: "center", + }, + }, + ); + + if (result) { + ctx.ui.notify( + `✅ Chiave API Gemini aggiornata: ${result.slice(0, 4)}...${result.slice(-4)}`, + "info", + ); + try { + playSound("done"); + } catch { + /* ignora */ + } + } else { + ctx.ui.notify("Chiave non modificata.", "info"); + } + }, + }); } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4fc747a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@earendil-works/pi-tui": [ + "/home/enne2/.local/share/pi-node/node-v22.23.2-linux-x64/lib/node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui/dist/index.d.ts" + ], + "@earendil-works/pi-coding-agent": [ + "/home/enne2/.local/share/pi-node/node-v22.23.2-linux-x64/lib/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts" + ] + } + }, + "include": ["extensions/**/*.ts"] +}