diff --git a/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py b/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py index fb0a457..b198013 100644 --- a/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py +++ b/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py @@ -258,6 +258,7 @@ def handle( ) -> BaseResult: source: str | None = kwargs.get("source") use_mic: bool = kwargs.get("mic", False) + guided_flow = False # ── Resolve mode ─────────────────────────────────────────────── if source == "-": @@ -280,12 +281,12 @@ def handle( ), ) else: - # Interactive: ask the user selected_mode, selected_source = self._interactive_select_source() if not selected_mode: return BaseResult(status="cancelled", message="Cancelled.") mode = selected_mode source = selected_source + guided_flow = True # ── Gather options ───────────────────────────────────────────── model = kwargs.get("model") or "nova-3" @@ -322,13 +323,7 @@ def handle( if caption_format and not diarize: diarize = False # keep explicit — user can add --diarize themselves - # Interactive feature selection when user chose source interactively - # (signals they want a guided experience) - if ( - not _agentic - and mode in ("prerecorded_file", "prerecorded_url") - and not any([diarize, summarize, topics, sentiment]) - ): + if guided_flow and mode in ("prerecorded_file", "prerecorded_url"): diarize, summarize, topics, sentiment = self._interactive_features() # ── Dispatch ─────────────────────────────────────────────────── diff --git a/packages/deepctl-cmd-listen/tests/unit/test_listen_command.py b/packages/deepctl-cmd-listen/tests/unit/test_listen_command.py index 832cffb..9ef74f6 100644 --- a/packages/deepctl-cmd-listen/tests/unit/test_listen_command.py +++ b/packages/deepctl-cmd-listen/tests/unit/test_listen_command.py @@ -241,6 +241,98 @@ def test_handle_explicit_stdin_dash( assert result.source == "stdin" +class TestGuidedFlow: + """Verify the guided-flow gate: prompts only fire on bare `dg listen`.""" + + @pytest.fixture + def command(self): + return ListenCommand() + + @pytest.fixture + def common_kwargs(self): + return { + "client": Mock(spec=DeepgramClient), + "config": Mock(spec=Config), + "auth_manager": Mock(spec=AuthManager), + } + + def test_url_arg_skips_both_prompts(self, command, common_kwargs): + with patch.object(command, "_interactive_features") as feat, patch.object( + command, "_interactive_select_source" + ) as src, patch.object( + command, "_prerecorded", return_value=BaseResult(status="ok") + ): + command.handle( + **common_kwargs, source="https://example.com/audio.wav" + ) + assert feat.call_count == 0 + assert src.call_count == 0 + + def test_file_arg_skips_both_prompts(self, command, common_kwargs): + with patch.object(command, "_interactive_features") as feat, patch.object( + command, "_interactive_select_source" + ) as src, patch.object( + command, "_prerecorded", return_value=BaseResult(status="ok") + ): + command.handle(**common_kwargs, source="/tmp/audio.wav") + assert feat.call_count == 0 + assert src.call_count == 0 + + def test_url_arg_with_diarize_skips_both_prompts( + self, command, common_kwargs + ): + with patch.object(command, "_interactive_features") as feat, patch.object( + command, "_interactive_select_source" + ) as src, patch.object( + command, "_prerecorded", return_value=BaseResult(status="ok") + ): + command.handle( + **common_kwargs, + source="https://example.com/audio.wav", + diarize=True, + ) + assert feat.call_count == 0 + assert src.call_count == 0 + + def test_bare_invocation_runs_full_guided_flow( + self, command, common_kwargs + ): + with patch.object( + command, + "_interactive_features", + return_value=(False, False, False, False), + ) as feat, patch.object( + command, + "_interactive_select_source", + return_value=("prerecorded_url", "https://x.com/a.wav"), + ) as src, patch.object( + command, "_prerecorded", return_value=BaseResult(status="ok") + ), patch( + "sys.stdin" + ) as mock_stdin, patch( + "deepctl_cmd_listen.command._agentic", False + ): + mock_stdin.isatty.return_value = True + command.handle(**common_kwargs) + assert src.call_count == 1 + assert feat.call_count == 1 + + def test_cancelled_source_select_returns_cancelled( + self, command, common_kwargs + ): + with patch.object( + command, "_interactive_select_source", return_value=(None, None) + ), patch.object(command, "_interactive_features") as feat, patch( + "sys.stdin" + ) as mock_stdin, patch( + "deepctl_cmd_listen.command._agentic", False + ): + mock_stdin.isatty.return_value = True + result = command.handle(**common_kwargs) + assert result.status == "cancelled" + assert feat.call_count == 0 + + class TestListenResult: """Test ListenResult model fields and defaults."""