fix: correct formatting in docstrings and enhance checkbox rendering for installed items

This commit is contained in:
X-iZhang
2026-02-12 15:47:13 +00:00
parent 8118d20dfd
commit a61333d4c5
3 changed files with 97 additions and 36 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ app.add_typer(config_app, name="config")
# MCP subcommand group
_MCP_HELP = """\
Configure and manage MCP servers.
Configure and manage MCP servers
Examples:
# Add a local MCP server (stdio auto-detected):
+14 -14
View File
@@ -36,7 +36,7 @@ def onboard(
help="Skip API key validation during setup"
),
):
"""Interactive setup wizard for EvoScientist.
"""Interactive setup wizard for EvoScientist
Guides you through configuring API keys, model selection,
workspace settings, and agent parameters.
@@ -51,14 +51,14 @@ def onboard(
@config_app.callback(invoke_without_command=True)
def config_callback(ctx: typer.Context):
"""Configuration management commands."""
"""Configuration management commands"""
if ctx.invoked_subcommand is None:
config_list()
@config_app.command("list")
def config_list():
"""List all configuration values."""
"""List all configuration values"""
from ..config import list_config, get_config_path
config_data = list_config()
@@ -84,7 +84,7 @@ def config_list():
@config_app.command("get")
def config_get(key: str = typer.Argument(..., help="Configuration key to get")):
"""Get a single configuration value."""
"""Get a single configuration value"""
from ..config import get_config_value
value = get_config_value(key)
@@ -108,7 +108,7 @@ def config_set(
key: str = typer.Argument(..., help="Configuration key to set"),
value: str = typer.Argument(..., help="New value"),
):
"""Set a single configuration value."""
"""Set a single configuration value"""
from ..config import set_config_value
if set_config_value(key, value):
@@ -122,7 +122,7 @@ def config_set(
def config_reset(
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
):
"""Reset configuration to defaults."""
"""Reset configuration to defaults"""
from ..config import reset_config, get_config_path
config_path = get_config_path()
@@ -143,7 +143,7 @@ def config_reset(
@config_app.command("path")
def config_path():
"""Show the configuration file path."""
"""Show the configuration file path"""
from ..config import get_config_path
path = get_config_path()
@@ -158,14 +158,14 @@ def config_path():
@mcp_app.callback(invoke_without_command=True)
def mcp_callback(ctx: typer.Context):
"""MCP server management commands."""
"""MCP server management commands"""
if ctx.invoked_subcommand is None:
mcp_list()
@mcp_app.command("list")
def mcp_list():
"""List configured MCP servers."""
"""List configured MCP servers"""
_mcp_list_servers()
@@ -173,7 +173,7 @@ def mcp_list():
def mcp_config(
name: Optional[str] = typer.Argument(None, help="Server name (omit to show all)"),
):
"""Show detailed configuration for MCP servers.
"""Show detailed configuration for MCP servers
\b
Examples:
@@ -200,7 +200,7 @@ def mcp_add(
env: Optional[list[str]] = typer.Option(None, "--env", help="Env var as KEY=VALUE for stdio (repeatable)"),
env_ref: Optional[list[str]] = typer.Option(None, "--env-ref", help="Env var name as ${NAME} runtime ref (repeatable)"),
):
"""Add an MCP server to user config.
"""Add an MCP server to user config
\b
Transport is auto-detected: URLs default to http, commands default to stdio.
@@ -249,7 +249,7 @@ def mcp_edit(
header: Optional[list[str]] = typer.Option(None, "--header", "-H", help="HTTP header as Key:Value (repeatable)"),
env: Optional[list[str]] = typer.Option(None, "--env", help="Env var as KEY=VALUE for stdio (repeatable)"),
):
"""Edit an existing MCP server in user config.
"""Edit an existing MCP server in user config
\b
Examples:
@@ -278,7 +278,7 @@ def mcp_edit(
def mcp_remove(
name: str = typer.Argument(..., help="Server name to remove"),
):
"""Remove an MCP server from user config."""
"""Remove an MCP server from user config"""
if not _mcp_remove_server(name, show_reload_hint=False):
raise typer.Exit(1)
@@ -308,7 +308,7 @@ def _main_callback(
use_cwd: bool = typer.Option(False, "--use-cwd", help="Use current working directory as workspace"),
no_thinking: bool = typer.Option(False, "--no-thinking", help="Disable thinking display"),
):
"""EvoScientist Agent - AI-powered research & code execution CLI."""
"""EvoScientist Agent - AI-powered research & code execution CLI"""
# If a subcommand was invoked, don't run the default behavior
if ctx.invoked_subcommand is not None:
return
+82 -21
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
import questionary
from prompt_toolkit.formatted_text import FormattedText
@@ -42,6 +43,7 @@ WIZARD_STYLE = Style.from_dict({
"highlighted": "noreverse bold", # No background, bold text
"selected": "fg:#4caf50 bold", # Green ● indicator
"separator": "fg:#6c6c6c", # Dim separator
"disabled": "fg:#858585", # Dim disabled indicator (-)
"instruction": "fg:#858585", # Dim instructions
"text": "fg:#858585", # Dim gray ○ and unselected text
})
@@ -56,6 +58,38 @@ CONFIRM_STYLE = Style.from_dict({
QMARK = "❯"
# Installed-item indicator style for disabled checkbox choices.
_INSTALLED_INDICATOR = ("fg:#4caf50", "✓ ")
def _checkbox_ask(choices, message: str, **kwargs):
"""``questionary.checkbox`` that renders disabled items with ✓ instead of ``-``.
Temporarily patches the rendering so the hard-coded ``"- "`` prefix for
disabled choices is replaced by a green ``"✓ "`` — keeping alignment with
the ``○`` indicator of normal choices.
"""
from questionary.prompts.common import InquirerControl
original = InquirerControl._get_choice_tokens
def _patched(self):
tokens = original(self)
return [
_INSTALLED_INDICATOR
if cls == "class:disabled" and text == "- "
else (cls, text)
for cls, text in tokens
]
InquirerControl._get_choice_tokens = _patched
try:
return questionary.checkbox(
message, choices=choices, style=WIZARD_STYLE, qmark=QMARK, **kwargs,
).ask()
finally:
InquirerControl._get_choice_tokens = original
STEPS = ["Provider", "API Key", "Model", "Tavily Key", "Workspace", "Parameters", "Skills", "MCP Servers", "Channels"]
@@ -757,24 +791,53 @@ def _ensure_npx(reason: str) -> bool:
def _step_skills() -> list[str]:
"""Step 7: Optionally install recommended skills.
Shows checkbox first. If user selects nothing, checks npx as an
easter egg — confirms skill discovery is available, or offers to
install Node.js if missing.
Shows checkbox first. Already-installed skills are shown as disabled
so users don't accidentally reinstall them. If user selects nothing,
checks npx as an easter egg — confirms skill discovery is available,
or offers to install Node.js if missing.
Returns:
List of skill sources that were selected (empty if skipped).
"""
choices = [
Choice(title=skill["label"], value=skill["source"])
for skill in _RECOMMENDED_SKILLS
]
from .paths import USER_SKILLS_DIR
selected = questionary.checkbox(
"Install predefined skills:",
choices=choices,
style=WIZARD_STYLE,
qmark=QMARK,
).ask()
# Collect names of already-installed user skills
skills_dir = Path(USER_SKILLS_DIR)
installed_names: set[str] = set()
if skills_dir.exists():
installed_names = {e.name for e in skills_dir.iterdir() if e.is_dir()}
def _hint_name(source: str) -> str:
"""Derive expected skill directory name from source URL."""
if "@" in source and "://" not in source:
return source.split("@", 1)[1].strip()
return source.rstrip("/").rsplit("/", 1)[-1]
choices = []
for skill in _RECOMMENDED_SKILLS:
if _hint_name(skill["source"]) in installed_names:
choices.append(
Choice(
title=[
("", skill["label"]),
("class:instruction", " (already installed)"),
],
value=skill["source"],
disabled=True,
)
)
else:
choices.append(Choice(title=skill["label"], value=skill["source"]))
all_installed = all(
_hint_name(skill["source"]) in installed_names
for skill in _RECOMMENDED_SKILLS
)
if all_installed:
console.print(" [green]✓ All recommended skills are already installed.[/green]")
return []
selected = _checkbox_ask(choices, "Install predefined skills:")
if selected is None:
raise KeyboardInterrupt()
@@ -896,9 +959,12 @@ def _step_mcp_servers() -> list[str]:
if srv["name"] in existing_config:
choices.append(
Choice(
title=srv["label"],
title=[
("", srv["label"]),
("class:instruction", " (already configured)"),
],
value=srv["name"],
disabled="already configured",
disabled=True,
)
)
else:
@@ -909,12 +975,7 @@ def _step_mcp_servers() -> list[str]:
console.print(" [green]✓ All recommended MCP servers are already configured.[/green]")
return []
selected = questionary.checkbox(
"Install recommended MCP servers:",
choices=choices,
style=WIZARD_STYLE,
qmark=QMARK,
).ask()
selected = _checkbox_ask(choices, "Install recommended MCP servers:")
if selected is None:
raise KeyboardInterrupt()