import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import { Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui"; import { OfficeError } from "../core/errors.ts"; export async function requestMaskedSecret( ctx: ExtensionContext, title: string, signal?: AbortSignal, ): Promise { if (!ctx.hasUI) throw new OfficeError("当前模式没有交互式 UI,密码操作已失败关闭。", "MASKED_UI_REQUIRED"); const result = await ctx.ui.custom((tui, theme, _keybindings, done) => { let value = ""; return { render(width: number): string[] { const heading = theme.fg("accent", theme.bold(title)); const masked = value.length > 0 ? "●".repeat(Array.from(value).length) : theme.fg("dim", "(尚未输入)"); return [ truncateToWidth(heading, width), truncateToWidth(`密码:${masked}`, width), truncateToWidth(theme.fg("dim", "Enter 确认 · Esc 取消 · 输入内容不会显示或发送给模型"), width), ]; }, handleInput(data: string): void { if (matchesKey(data, Key.enter)) { done(value || undefined); return; } if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) { value = ""; done(undefined); return; } if (matchesKey(data, Key.backspace) || matchesKey(data, Key.delete)) { value = Array.from(value).slice(0, -1).join(""); tui.requestRender(); return; } if (matchesKey(data, Key.ctrl("u"))) { value = ""; tui.requestRender(); return; } if (!data.includes("\u001b")) { value += Array.from(data).filter((char) => char.codePointAt(0)! >= 32 && char !== "\r" && char !== "\n").join(""); tui.requestRender(); } }, invalidate(): void {}, }; }, { overlay: true, ...(signal ? { signal } : {}) }); if (!result) throw new OfficeError("密码输入已取消。", "PASSWORD_CANCELLED"); return result; }