diff --git a/src/ucode/cli.py b/src/ucode/cli.py index aabaa01f..c5294407 100644 --- a/src/ucode/cli.py +++ b/src/ucode/cli.py @@ -1764,7 +1764,11 @@ def claude_router_hook_cmd( def _auto_configure_tool(tool: str, custom_oauth: CustomOAuthConfig | None = None) -> None: - """First-time setup for a single tool — mirrors configure_workspace_command.""" + """Configure a tool for launch without sending a separate validation prompt. + + The real agent session follows immediately; explicit configure retains the + test-prompt validation. + """ existing = load_state() workspace = existing.get("workspace") profile = existing.get("profile") @@ -1787,19 +1791,6 @@ def _auto_configure_tool(tool: str, custom_oauth: CustomOAuthConfig | None = Non ) ) - with spinner(f"Validating {spec['display']}..."): - ok, err = validate_tool(tool) - if ok: - print_success(f"{spec['display']} is working") - else: - print_err(f"{spec['display']}: {provider_permission_error(tool, state, err)}") - managed = bool(state.get("managed_configs", {}).get(tool)) - restore_file(spec["config_path"], spec["backup_path"], managed) - available_tools = [t for t in (state.get("available_tools") or []) if t != tool] - state["available_tools"] = available_tools - save_state(state) - raise RuntimeError(f"{spec['display']} validation failed — config reverted.") - CAN_USE_CACHED_CONFIG_AGENTS = frozenset({"claude", "codex"}) diff --git a/tests/test_cli.py b/tests/test_cli.py index 36f4d842..32e81f71 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1845,6 +1845,38 @@ def test_reports_runtime_error(self): class TestAutoConfigureOnFirstRun: + @pytest.mark.parametrize("tool", list(cli_mod.TOOL_SPECS)) + @pytest.mark.parametrize("has_workspace", [False, True]) + def test_launch_autoconfigures_without_test_prompt(self, tool, has_workspace): + initial_state = {**MINIMAL_STATE, "available_tools": []} if has_workspace else {} + configured_state = {**MINIMAL_STATE, "available_tools": [tool]} + with ( + patch("ucode.cli.ensure_bootstrap_dependencies"), + patch("ucode.cli.load_state", return_value=initial_state), + patch( + "ucode.cli._prompt_for_configuration", + return_value=(MINIMAL_STATE["workspace"], None), + ), + patch("ucode.cli.configure_shared_state", return_value=configured_state), + patch( + "ucode.cli.configure_single_tool", return_value=configured_state + ) as mock_configure, + patch("ucode.cli.ensure_provider_state", return_value=configured_state), + patch("ucode.cli._fetch_managed_config", return_value=(None, False)), + patch("ucode.cli.configure_tool", return_value=configured_state), + patch("ucode.cli.validate_tool", return_value=(False, "timed out")) as mock_validate, + patch("ucode.cli.restore_file") as mock_restore, + patch("ucode.cli.launch_agent") as mock_launch, + ): + result = runner.invoke(app, [tool]) + + assert result.exit_code == 0, result.output + mock_configure.assert_called_once_with(tool, configured_state) + mock_validate.assert_not_called() + mock_restore.assert_not_called() + mock_launch.assert_called_once() + assert mock_launch.call_args.args[:2] == (tool, configured_state) + def test_triggers_when_no_workspace(self): """Auto-configure runs when state has no workspace.""" empty_state = {} @@ -3205,7 +3237,9 @@ def test_skip_validate_skips_agent_validation(self, monkeypatch): assert result == 0 assert validated == [] - def test_skip_validate_skips_single_tool_validation(self, monkeypatch): + @pytest.mark.parametrize("skip_validate", [False, True]) + @pytest.mark.parametrize("tool", list(cli_mod.TOOL_SPECS)) + def test_single_tool_validation_is_optional(self, monkeypatch, skip_validate, tool): import ucode.cli as cli_mod state = {**MINIMAL_STATE, "workspace": "https://first.com"} @@ -3221,16 +3255,16 @@ def test_skip_validate_skips_single_tool_validation(self, monkeypatch): monkeypatch.setattr(cli_mod, "validate_tool", lambda t: validated.append(t) or (True, "")) result = cli_mod.configure_workspace_command( - "claude", + tool, workspaces=[("https://first.com", None)], - skip_validate=True, + skip_validate=skip_validate, ) assert result == 0 - assert validated == [] + assert validated == ([] if skip_validate else [tool]) # `ucode configure` (single-agent) still installs AI Tools — it's the # configure path, unlike launch which auto-configures without installing. - assert installed == [["claude"]] + assert installed == [[tool]] class TestConfigureSharedStateMcpCleanup: