96659759b0
- Agent OS:agents 决策循环、runtime 编排、providers、governance/security/sandbox - app 服务层(认证转发到 server-core)、channels 19 路频道、memory、plugins
226 lines
6.5 KiB
Python
226 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Shared interactive prompt helpers used by CLI commands.
|
|
|
|
All terminal interaction with *questionary* is centralised here so that
|
|
the rest of the CLI code never imports questionary directly.
|
|
"""
|
|
from __future__ import annotations
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import click
|
|
import questionary
|
|
|
|
|
|
def prompt_confirm(question: str, *, default: bool = False) -> bool:
|
|
"""Prompt the user for a yes/no answer.
|
|
Args:
|
|
question: The question shown to the user.
|
|
default: Pre-selected answer (``False`` → *No*).
|
|
|
|
Returns:
|
|
``True`` for *Yes*, ``False`` for *No*.
|
|
Falls back to *default* on Ctrl+C.
|
|
"""
|
|
items = [
|
|
questionary.Choice("Yes", value=True),
|
|
questionary.Choice("No", value=False),
|
|
]
|
|
preselect = items[0] if default else items[1]
|
|
|
|
result = questionary.select(
|
|
question,
|
|
choices=items,
|
|
default=preselect,
|
|
use_shortcuts=False,
|
|
use_arrow_keys=True,
|
|
use_jk_keys=False,
|
|
).ask()
|
|
if result is None:
|
|
return default
|
|
return result
|
|
|
|
|
|
def prompt_path(label: str, *, default: str = "") -> str:
|
|
"""Ask the user for a filesystem path, warning if it doesn't exist.
|
|
|
|
Args:
|
|
label: Prompt text (e.g. ``"iMessage database path"``).
|
|
default: Pre-filled path value.
|
|
|
|
Returns:
|
|
The resolved absolute path (if it exists) or the raw input
|
|
(if the user chose to continue anyway).
|
|
"""
|
|
while True:
|
|
value = click.prompt(label, default=default, type=str)
|
|
if not value:
|
|
return value
|
|
path = Path(value).expanduser()
|
|
if path.exists():
|
|
return str(path.resolve())
|
|
if prompt_confirm(
|
|
f"Path '{value}' does not exist, continue anyway?",
|
|
default=True,
|
|
):
|
|
return value
|
|
# Otherwise loop and re-prompt
|
|
|
|
|
|
def prompt_choice(
|
|
question: str,
|
|
options: list[str],
|
|
*,
|
|
default: Optional[str] = None,
|
|
) -> str:
|
|
"""Let the user pick one item from a plain string list.
|
|
|
|
Args:
|
|
question: Prompt text shown above the list.
|
|
options: Selectable string values (label == value).
|
|
default: Value to pre-select; ``None`` → first item.
|
|
|
|
Returns:
|
|
The selected string.
|
|
Falls back to *default* (or the first option) on Ctrl+C.
|
|
"""
|
|
items = [questionary.Choice(opt, value=opt) for opt in options]
|
|
|
|
preselect = None
|
|
if default is not None:
|
|
try:
|
|
idx = options.index(default)
|
|
preselect = items[idx]
|
|
except (ValueError, IndexError):
|
|
pass
|
|
|
|
result = questionary.select(
|
|
question,
|
|
choices=items,
|
|
default=preselect,
|
|
use_shortcuts=False,
|
|
use_arrow_keys=True,
|
|
use_jk_keys=False,
|
|
).ask()
|
|
if result is None:
|
|
return default or options[0]
|
|
return result
|
|
|
|
|
|
def prompt_select(
|
|
question: str,
|
|
options: list[tuple[str, str]],
|
|
*,
|
|
default: Optional[str] = None,
|
|
) -> Optional[str]:
|
|
"""Let the user pick one item from a list of (label, value) pairs.
|
|
|
|
Unlike :func:`prompt_choice`, the displayed label here can differ from
|
|
the returned value — useful for showing status icons, etc.
|
|
|
|
Args:
|
|
question: Prompt text shown above the list.
|
|
options: ``(label, value)`` pairs; *label* is displayed,
|
|
*value* is returned.
|
|
default: The *value* to pre-select; ``None`` → no default.
|
|
|
|
Returns:
|
|
The selected *value*, or ``None`` on Ctrl+C.
|
|
"""
|
|
items = [
|
|
questionary.Choice(label, value=value) for label, value in options
|
|
]
|
|
|
|
preselect = None
|
|
if default is not None:
|
|
for item in items:
|
|
if item.value == default:
|
|
preselect = item
|
|
break
|
|
|
|
return questionary.select(
|
|
question,
|
|
choices=items,
|
|
default=preselect,
|
|
use_shortcuts=False,
|
|
use_arrow_keys=True,
|
|
use_jk_keys=False,
|
|
).ask()
|
|
|
|
|
|
def prompt_checkbox(
|
|
question: str,
|
|
options: list[tuple[str, str]],
|
|
*,
|
|
checked: Optional[set[str]] = None,
|
|
select_all_option: bool = True,
|
|
) -> Optional[list[str]]:
|
|
"""Let the user pick multiple items from a list with checkboxes.
|
|
|
|
Supports a "Select All / Deselect All" toggle at the top of the list.
|
|
When the user checks "Select All" and presses Enter, the list is
|
|
re-rendered with **all** items checked (or all unchecked if they
|
|
were already all checked), so the toggle is visually reflected.
|
|
|
|
Args:
|
|
question: Prompt text shown above the list.
|
|
options: ``(label, value)`` pairs; *label* is displayed,
|
|
*value* is returned for each selected item.
|
|
checked: Set of *values* that should be pre-checked.
|
|
``None`` → nothing checked.
|
|
select_all_option: Whether to show a "Select All" toggle.
|
|
|
|
Returns:
|
|
List of selected *values*, or ``None`` on Ctrl+C.
|
|
"""
|
|
_SELECT_ALL = "__select_all__"
|
|
all_values = {v for _, v in options}
|
|
current_checked = set(checked or set()) & all_values
|
|
|
|
while True:
|
|
all_currently_checked = (
|
|
current_checked == all_values and len(all_values) > 0
|
|
)
|
|
|
|
items: list[questionary.Choice] = []
|
|
if select_all_option:
|
|
items.append(
|
|
questionary.Choice(
|
|
"✦ Select All / Deselect All",
|
|
value=_SELECT_ALL,
|
|
checked=all_currently_checked,
|
|
),
|
|
)
|
|
|
|
for label, value in options:
|
|
items.append(
|
|
questionary.Choice(
|
|
label,
|
|
value=value,
|
|
checked=value in current_checked,
|
|
),
|
|
)
|
|
|
|
result = questionary.checkbox(
|
|
question,
|
|
choices=items,
|
|
use_jk_keys=False,
|
|
).ask()
|
|
|
|
if result is None:
|
|
return None
|
|
|
|
# If "Select All" is toggled, refresh the list with all
|
|
# items checked (or unchecked) and let the user confirm.
|
|
if _SELECT_ALL in result:
|
|
if all_currently_checked:
|
|
# Was all-selected → deselect all
|
|
current_checked = set()
|
|
else:
|
|
# Not all selected → select all
|
|
current_checked = set(all_values)
|
|
continue # re-render the checkbox
|
|
|
|
# Normal submit without "Select All" → return selection
|
|
return [r for r in result if r != _SELECT_ALL]
|