| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- """在没有终端的环境里,用操作系统的密码对话框读取密码。
- 为什么需要它:Claude Desktop、IDE 集成、agent 代跑等场景都没有 TTY,`getpass`
- 用不了。而让用户把密码打进对话、或放进命令行参数,都会让密码进入会话上下文或
- `ps` / shell history——那是真正的泄漏。
- 系统对话框解决了这个矛盾:**密码由用户直接输给操作系统,不经过终端、不经过 argv、
- 调用方(包括 agent)全程看不到**,只从子进程的 stdout 拿回来,留在本进程内存里。
- 支持 zenity / kdialog(Linux)、osascript(macOS)、PowerShell(Windows)。
- 没有图形界面时返回 None,由调用方给出可操作的提示。
- """
- import os
- import shutil
- import subprocess
- import sys
- DIALOG_TIMEOUT = 180
- def gui_available():
- """当前环境有没有可用的图形界面。"""
- if sys.platform == 'darwin':
- return shutil.which('osascript') is not None
- if os.name == 'nt':
- return True
- if not (os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY')):
- return False
- return shutil.which('zenity') is not None or shutil.which('kdialog') is not None
- def _run(cmd):
- """跑对话框命令,返回用户输入;取消或失败返回 None。
- stdout 只在本函数内流转,绝不打印——它装着密码。
- """
- try:
- proc = subprocess.run(cmd, capture_output=True, text=True, timeout=DIALOG_TIMEOUT)
- except (OSError, subprocess.TimeoutExpired):
- return None
- if proc.returncode != 0:
- return None
- return proc.stdout.rstrip('\n')
- def ask(prompt, title='WikiPali', secret=True):
- """弹一个对话框问用户要一行输入。secret=True 时输入被遮蔽。
- 返回字符串;用户取消、超时或没有图形界面时返回 None。
- """
- if sys.platform == 'darwin':
- hidden = ' with hidden answer' if secret else ''
- script = (f'display dialog "{prompt}" with title "{title}" '
- f'default answer ""{hidden}')
- out = _run(['osascript', '-e', script, '-e',
- 'text returned of result'])
- return out
- if os.name == 'nt':
- if secret:
- ps = (f'$s = Read-Host -AsSecureString "{prompt}"; '
- '[Runtime.InteropServices.Marshal]::PtrToStringAuto('
- '[Runtime.InteropServices.Marshal]::SecureStringToBSTR($s))')
- else:
- ps = f'Read-Host "{prompt}"'
- return _run(['powershell', '-NoProfile', '-Command', ps])
- if shutil.which('zenity'):
- if secret:
- # --password 只给密码框;要用户名时用 --entry
- return _run(['zenity', '--password', '--title', f'{title} — {prompt}'])
- return _run(['zenity', '--entry', '--title', title, '--text', prompt])
- if shutil.which('kdialog'):
- flag = '--password' if secret else '--inputbox'
- return _run(['kdialog', '--title', title, flag, prompt])
- return None
|