diff --git a/docs/start-without-technical-background.md b/docs/start-without-technical-background.md index 997546a3..ad9da92c 100644 --- a/docs/start-without-technical-background.md +++ b/docs/start-without-technical-background.md @@ -190,13 +190,15 @@ For the first setup, choose `[Q] Quick Start (recommended)`. It asks for the mod If you are following the OpenRouter example: 1. Choose `[Q] Quick Start (recommended)`. -2. Choose `WebUI only (recommended)` unless you already have a Telegram, Feishu/Lark, Slack, or Discord bot token ready. +2. Choose `No chat channel yet (recommended)` unless you already have a Telegram, Feishu/Lark, Slack, or Discord bot token ready. 3. Select OpenRouter. 4. Paste your OpenRouter API key. 5. Enter a model ID, for example `anthropic/claude-sonnet-4.5`. 6. Review the Quick Start summary. 7. Choose `[S] Save and Exit`. +Enable WebUI later from `[C] Chat Channel` -> `WebSocket` after the first local reply works. + If OpenRouter says your account cannot use that model, use another OpenRouter model ID that your account can access. If you are using another provider, use the same wizard choices but substitute that provider's values: diff --git a/nanobot/cli/onboard.py b/nanobot/cli/onboard.py index f30314b4..84e172e1 100644 --- a/nanobot/cli/onboard.py +++ b/nanobot/cli/onboard.py @@ -54,7 +54,7 @@ _BACK_PRESSED = object() # Sentinel value for back navigation _MODEL_PRESET_CACHE: set[str] = set() _QUICK_START_TARGETS = { - "WebUI only (recommended)": "websocket", + "No chat channel yet (recommended)": None, "Telegram": "telegram", "Feishu / Lark": "feishu", "Slack": "slack", @@ -414,11 +414,9 @@ def _show_main_menu_header() -> None: body = Table.grid(expand=True) body.add_column(ratio=1) body.add_row(f"{__logo__} [bold {_UI_TEXT}]nanobot[/] [{_UI_MUTED}]v{__version__}[/]") + body.add_row(f"[{_UI_ACCENT}]Quick Start configures one AI model first.[/]") body.add_row( - f"[{_UI_ACCENT}]Quick Start configures a model and enables WebUI by default.[/]" - ) - body.add_row( - f"[{_UI_MUTED}]Chat channels and advanced settings stay available when you need them.[/]" + f"[{_UI_MUTED}]WebUI, chat channels, and advanced settings stay available when you need them.[/]" ) console.print( Panel( @@ -1445,13 +1443,21 @@ def _configure_quick_start_channel(config: Config, channel_name: str | None) -> current = getattr(config.channels, channel_name, None) or {} model = config_cls.model_validate(current) - if channel_name == "websocket": - console.print("[dim]WebUI uses the built-in local WebSocket channel. No token is needed now.[/dim]") - for field_name, prompt in _QUICK_START_CHANNEL_FIELDS.get(channel_name, ()): + required_fields = _QUICK_START_CHANNEL_FIELDS.get(channel_name, ()) + for field_name, prompt in required_fields: value = _input_with_existing(prompt, getattr(model, field_name, ""), "str") if value is not None: setattr(model, field_name, value) + missing = [ + prompt + for field_name, prompt in required_fields + if not str(getattr(model, field_name, "") or "").strip() + ] + if missing: + console.print(f"[yellow]! {missing[0]} is required; channel was not enabled[/yellow]") + return False + if hasattr(model, "enabled"): setattr(model, "enabled", True) setattr(config.channels, channel_name, model.model_dump(by_alias=True, exclude_none=True)) @@ -1465,8 +1471,9 @@ def _show_quick_start_summary(config: Config, channel_name: str | None) -> None: rows = [ ("Provider", preset.provider if preset else "[not set]"), ("Model", preset.model if preset else "[not set]"), - ("Entry point", "WebUI" if channel_name in {None, "websocket"} else channel_name), - ("Next", "Save, then run `nanobot gateway`"), + ("Entry point", "Not enabled yet" if channel_name is None else channel_name), + ("Next", "Save, then run `nanobot agent -m \"Hello!\"`"), + ("WebUI", "Use [C] Chat Channel -> WebSocket when you are ready"), ] _print_summary_panel(rows, "Quick Start") @@ -1482,7 +1489,7 @@ def _configure_quick_start(config: Config) -> None: answer = _select_with_back( "How do you want to use nanobot first?", list(_QUICK_START_TARGETS) + ["<- Back"], - default="WebUI only (recommended)", + default="No chat channel yet (recommended)", ) if answer is _BACK_PRESSED or answer is None or answer == "<- Back": return diff --git a/tests/agent/test_onboard_logic.py b/tests/agent/test_onboard_logic.py index a003c082..6cc44466 100644 --- a/tests/agent/test_onboard_logic.py +++ b/tests/agent/test_onboard_logic.py @@ -926,10 +926,10 @@ class TestMainMenuUpdate: assert telegram["enabled"] is True assert telegram["token"] == "123:abc" - def test_quick_start_webui_enables_websocket(self, monkeypatch): - """The recommended Quick Start target should create a working WebUI channel.""" + def test_quick_start_recommended_path_does_not_enable_channel(self, monkeypatch): + """The recommended Quick Start target should leave chat surfaces as explicit opt-ins.""" config = Config() - selections = iter(["WebUI only (recommended)", "OpenRouter"]) + selections = iter(["No chat channel yet (recommended)", "OpenRouter"]) def fake_select_with_back(*_args, **_kwargs): return next(selections) @@ -948,9 +948,18 @@ class TestMainMenuUpdate: onboard_wizard._configure_quick_start(config) - websocket = getattr(config.channels, "websocket") - assert websocket["enabled"] is True - assert websocket["allowFrom"] == ["*"] + assert getattr(config.channels, "websocket", None) is None + assert config.agents.defaults.model_preset == "primary" + + def test_quick_start_channel_requires_token_before_enable(self, monkeypatch): + """Quick Start should not enable token-based channels with blank credentials.""" + config = Config() + + monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None) + monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "") + + assert onboard_wizard._configure_quick_start_channel(config, "telegram") is False + assert getattr(config.channels, "telegram", None) is None def test_main_menu_dispatch_includes_channel_common(self): """Main menu dispatch should route [H] to Channel Common."""