feat: /agy:key — dialog overlay TUI mascherato per la chiave API Gemini
- 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.
This commit is contained in:
@@ -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) |
|
||||
|
||||
@@ -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<string | null>(
|
||||
(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");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user