refactor: optimize onboard wizard - mask secrets, remove emoji, reduce repetition

- Mask sensitive fields (api_key/token/secret/password) in all display
  surfaces, showing only the last 4 characters
- Replace all emoji with pure ASCII labels for consistent cross-platform
  terminal rendering
- Extract _print_summary_panel helper, eliminating 5x duplicate table
  construction in _show_summary
- Replace 3 one-line wrapper functions with declarative _SETTINGS_SECTIONS
  dispatch tables and _MENU_DISPATCH in run_onboard
- Extract _handle_model_field / _handle_context_window_field into a
  _FIELD_HANDLERS registry, shrinking _configure_pydantic_model
- Return FieldTypeInfo NamedTuple from _get_field_type_info for clarity
- Replace global mutable _PROVIDER_INFO / _CHANNEL_INFO with @lru_cache
- Use vars() instead of dir() in _get_channel_info for reliable config
  class discovery
- Defer litellm import in model_info.py so non-wizard CLI paths stay fast
- Clarify README Quick Start wording (Add -> Configure)
This commit is contained in:
Xubin Ren
2026-03-20 07:53:18 +00:00
committed by Xubin Ren
parent 45e89d917b
commit c3a4b16e76
7 changed files with 344 additions and 345 deletions

View File

@@ -61,7 +61,7 @@ def test_onboard_fresh_install(mock_paths):
"""No existing config — should create from scratch."""
config_file, workspace_dir, mock_ws = mock_paths
result = runner.invoke(app, ["onboard", "--no-interactive"])
result = runner.invoke(app, ["onboard"])
assert result.exit_code == 0
assert "Created config" in result.stdout
@@ -79,7 +79,7 @@ def test_onboard_existing_config_refresh(mock_paths):
config_file, workspace_dir, _ = mock_paths
config_file.write_text('{"existing": true}')
result = runner.invoke(app, ["onboard", "--no-interactive"], input="n\n")
result = runner.invoke(app, ["onboard"], input="n\n")
assert result.exit_code == 0
assert "Config already exists" in result.stdout
@@ -93,7 +93,7 @@ def test_onboard_existing_config_overwrite(mock_paths):
config_file, workspace_dir, _ = mock_paths
config_file.write_text('{"existing": true}')
result = runner.invoke(app, ["onboard", "--no-interactive"], input="y\n")
result = runner.invoke(app, ["onboard"], input="y\n")
assert result.exit_code == 0
assert "Config already exists" in result.stdout
@@ -107,7 +107,7 @@ def test_onboard_existing_workspace_safe_create(mock_paths):
workspace_dir.mkdir(parents=True)
config_file.write_text("{}")
result = runner.invoke(app, ["onboard", "--no-interactive"], input="n\n")
result = runner.invoke(app, ["onboard"], input="n\n")
assert result.exit_code == 0
assert "Created workspace" not in result.stdout
@@ -130,6 +130,7 @@ def test_onboard_help_shows_workspace_and_config_options():
assert "-w" in stripped_output
assert "--config" in stripped_output
assert "-c" in stripped_output
assert "--wizard" in stripped_output
assert "--dir" not in stripped_output
@@ -143,7 +144,7 @@ def test_onboard_interactive_discard_does_not_save_or_create_workspace(mock_path
lambda initial_config: OnboardResult(config=initial_config, should_save=False),
)
result = runner.invoke(app, ["onboard"])
result = runner.invoke(app, ["onboard", "--wizard"])
assert result.exit_code == 0
assert "No changes were saved" in result.stdout
@@ -159,7 +160,7 @@ def test_onboard_uses_explicit_config_and_workspace_paths(tmp_path, monkeypatch)
result = runner.invoke(
app,
["onboard", "--config", str(config_path), "--workspace", str(workspace_path), "--no-interactive"],
["onboard", "--config", str(config_path), "--workspace", str(workspace_path)],
)
assert result.exit_code == 0
@@ -173,6 +174,31 @@ def test_onboard_uses_explicit_config_and_workspace_paths(tmp_path, monkeypatch)
assert f"--config {resolved_config}" in compact_output
def test_onboard_wizard_preserves_explicit_config_in_next_steps(tmp_path, monkeypatch):
config_path = tmp_path / "instance" / "config.json"
workspace_path = tmp_path / "workspace"
from nanobot.cli.onboard_wizard import OnboardResult
monkeypatch.setattr(
"nanobot.cli.onboard_wizard.run_onboard",
lambda initial_config: OnboardResult(config=initial_config, should_save=True),
)
monkeypatch.setattr("nanobot.channels.registry.discover_all", lambda: {})
result = runner.invoke(
app,
["onboard", "--wizard", "--config", str(config_path), "--workspace", str(workspace_path)],
)
assert result.exit_code == 0
stripped_output = _strip_ansi(result.stdout)
compact_output = stripped_output.replace("\n", "")
resolved_config = str(config_path.resolve())
assert f'nanobot agent -m "Hello!" --config {resolved_config}' in compact_output
assert f"nanobot gateway --config {resolved_config}" in compact_output
def test_config_matches_github_copilot_codex_with_hyphen_prefix():
config = Config()
config.agents.defaults.model = "github-copilot/gpt-5.3-codex"

View File

@@ -75,7 +75,7 @@ def test_onboard_refresh_rewrites_legacy_config_template(tmp_path, monkeypatch)
from typer.testing import CliRunner
from nanobot.cli.commands import app
runner = CliRunner()
result = runner.invoke(app, ["onboard", "--no-interactive"], input="n\n")
result = runner.invoke(app, ["onboard"], input="n\n")
assert result.exit_code == 0
assert "contextWindowTokens" in result.stdout
@@ -127,7 +127,7 @@ def test_onboard_refresh_backfills_missing_channel_fields(tmp_path, monkeypatch)
from typer.testing import CliRunner
from nanobot.cli.commands import app
runner = CliRunner()
result = runner.invoke(app, ["onboard", "--no-interactive"], input="n\n")
result = runner.invoke(app, ["onboard"], input="n\n")
assert result.exit_code == 0
saved = json.loads(config_path.read_text(encoding="utf-8"))

View File

@@ -401,7 +401,7 @@ class TestConfigurePydanticModelDrafts:
if token == "first":
return choices[0]
if token == "done":
return "Done"
return "[Done]"
if token == "back":
return _BACK_PRESSED
return token
@@ -461,9 +461,9 @@ class TestRunOnboardExitBehavior:
responses = iter(
[
"🤖 Configure Agent Settings",
"[A] Agent Settings",
KeyboardInterrupt(),
"🗑️ Exit Without Saving",
"[X] Exit Without Saving",
]
)
@@ -479,12 +479,13 @@ class TestRunOnboardExitBehavior:
def fake_select(*_args, **_kwargs):
return FakePrompt(next(responses))
def fake_configure_agents(config):
config.agents.defaults.model = "test/provider-model"
def fake_configure_general_settings(config, section):
if section == "Agent Settings":
config.agents.defaults.model = "test/provider-model"
monkeypatch.setattr(onboard_wizard, "_show_main_menu_header", lambda: None)
monkeypatch.setattr(onboard_wizard.questionary, "select", fake_select)
monkeypatch.setattr(onboard_wizard, "_configure_agents", fake_configure_agents)
monkeypatch.setattr(onboard_wizard, "questionary", SimpleNamespace(select=fake_select))
monkeypatch.setattr(onboard_wizard, "_configure_general_settings", fake_configure_general_settings)
result = run_onboard(initial_config=initial_config)