diff --git a/cecli/commands/add.py b/cecli/commands/add.py index f7080a2512f..502be13d5bc 100644 --- a/cecli/commands/add.py +++ b/cecli/commands/add.py @@ -16,6 +16,7 @@ class AddCommand(BaseCommand): NORM_NAME = "add" DESCRIPTION = "Add files to the chat so cecli can edit them or review them in detail" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/agent_model.py b/cecli/commands/agent_model.py index 1b10617a129..af8d6905caf 100644 --- a/cecli/commands/agent_model.py +++ b/cecli/commands/agent_model.py @@ -9,6 +9,7 @@ class AgentModelCommand(BaseCommand): NORM_NAME = "agent-model" DESCRIPTION = "Switch the Agent Model to a new LLM" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/clear.py b/cecli/commands/clear.py index 0c4ba8b560e..793b55d80ee 100644 --- a/cecli/commands/clear.py +++ b/cecli/commands/clear.py @@ -8,6 +8,7 @@ class ClearCommand(BaseCommand): NORM_NAME = "clear" DESCRIPTION = "Clear the chat history" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/context_blocks.py b/cecli/commands/context_blocks.py index f7d4d05edd6..782cae61f67 100644 --- a/cecli/commands/context_blocks.py +++ b/cecli/commands/context_blocks.py @@ -7,6 +7,7 @@ class ContextBlocksCommand(BaseCommand): NORM_NAME = "context-blocks" DESCRIPTION = "Toggle enhanced context blocks or print a specific block" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/context_management.py b/cecli/commands/context_management.py index 6d7e14bc99e..4a70bab05e7 100644 --- a/cecli/commands/context_management.py +++ b/cecli/commands/context_management.py @@ -8,6 +8,7 @@ class ContextManagementCommand(BaseCommand): NORM_NAME = "context-management" DESCRIPTION = "Toggle context management for large files" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/copy.py b/cecli/commands/copy.py index 0d984d09644..4edb3b1c180 100644 --- a/cecli/commands/copy.py +++ b/cecli/commands/copy.py @@ -10,6 +10,7 @@ class CopyCommand(BaseCommand): NORM_NAME = "copy" DESCRIPTION = "Copy the last assistant message to the clipboard" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/copy_context.py b/cecli/commands/copy_context.py index 900557a7eb4..ced8704a8cf 100644 --- a/cecli/commands/copy_context.py +++ b/cecli/commands/copy_context.py @@ -10,6 +10,7 @@ class CopyContextCommand(BaseCommand): NORM_NAME = "copy-context" DESCRIPTION = "Copy the current chat context as markdown, suitable to paste into a web UI" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/core.py b/cecli/commands/core.py index 3f986d5434c..f25500c216d 100644 --- a/cecli/commands/core.py +++ b/cecli/commands/core.py @@ -37,7 +37,7 @@ class Commands: scraper = None def clone(self): - return Commands( + cloned = Commands( self.io, None, voice_language=self.voice_language, @@ -50,6 +50,8 @@ def clone(self): editor=self.editor, original_read_only_fnames=self.original_read_only_fnames, ) + cloned.last_command_show_notification = self.last_command_show_notification + return cloned def __init__( self, @@ -94,6 +96,7 @@ def __init__( self.cmd_running_event = asyncio.Event() self.cmd_running_event.set() + self.last_command_show_notification = True def _load_custom_commands(self, custom_commands): """ @@ -185,6 +188,8 @@ async def execute(self, cmd_name, args, coder=None, **kwargs): if not command_class: active_coder.io.tool_output(f"Error: Command {cmd_name} not found.") return + + self.last_command_show_notification = command_class.show_completion_notification self.cmd_running_event.clear() try: diff --git a/cecli/commands/diff.py b/cecli/commands/diff.py index c5907788b31..3b35f6e9bcf 100644 --- a/cecli/commands/diff.py +++ b/cecli/commands/diff.py @@ -8,6 +8,7 @@ class DiffCommand(BaseCommand): NORM_NAME = "diff" DESCRIPTION = "Display the diff of changes since the last message" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/drop.py b/cecli/commands/drop.py index 92ef0992514..6587b930149 100644 --- a/cecli/commands/drop.py +++ b/cecli/commands/drop.py @@ -13,6 +13,7 @@ class DropCommand(BaseCommand): NORM_NAME = "drop" DESCRIPTION = "Remove files from the chat session to free up context space" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/editor.py b/cecli/commands/editor.py index 5994382bf85..691dc29dd8e 100644 --- a/cecli/commands/editor.py +++ b/cecli/commands/editor.py @@ -8,6 +8,7 @@ class EditorCommand(BaseCommand): NORM_NAME = "editor" DESCRIPTION = "Open an editor to write a prompt" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/editor_model.py b/cecli/commands/editor_model.py index beb97a86ae9..197d623e782 100644 --- a/cecli/commands/editor_model.py +++ b/cecli/commands/editor_model.py @@ -9,6 +9,7 @@ class EditorModelCommand(BaseCommand): NORM_NAME = "editor-model" DESCRIPTION = "Switch the Editor Model to a new LLM" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/exclude_skill.py b/cecli/commands/exclude_skill.py index 8f8197eba5b..086e967678a 100644 --- a/cecli/commands/exclude_skill.py +++ b/cecli/commands/exclude_skill.py @@ -7,6 +7,7 @@ class ExcludeSkillCommand(BaseCommand): NORM_NAME = "exclude-skill" DESCRIPTION = "Exclude a skill by name (agent mode only)" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/exit.py b/cecli/commands/exit.py index 46b96576df5..7a116c00d0c 100644 --- a/cecli/commands/exit.py +++ b/cecli/commands/exit.py @@ -10,6 +10,7 @@ class ExitCommand(BaseCommand): NORM_NAME = "exit" DESCRIPTION = "Exit the application" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/hooks.py b/cecli/commands/hooks.py index 1ab6d299391..8d0d886361a 100644 --- a/cecli/commands/hooks.py +++ b/cecli/commands/hooks.py @@ -9,6 +9,7 @@ class HooksCommand(BaseCommand): NORM_NAME = "hooks" DESCRIPTION = "List all registered hooks by type with their current state" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/include_skill.py b/cecli/commands/include_skill.py index 79f31fde15d..754ccdf2dd2 100644 --- a/cecli/commands/include_skill.py +++ b/cecli/commands/include_skill.py @@ -7,6 +7,7 @@ class IncludeSkillCommand(BaseCommand): NORM_NAME = "include-skill" DESCRIPTION = "Include a skill by name (agent mode only)" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/invoke_agent.py b/cecli/commands/invoke_agent.py index 6b14c8caf91..a99be81ba76 100644 --- a/cecli/commands/invoke_agent.py +++ b/cecli/commands/invoke_agent.py @@ -6,6 +6,7 @@ class InvokeAgentCommand(BaseCommand): NORM_NAME = "invoke-agent" DESCRIPTION = "Invoke a sub-agent with a prompt (blocking)" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/list_sessions.py b/cecli/commands/list_sessions.py index b896b17e547..4b7834bdaf7 100644 --- a/cecli/commands/list_sessions.py +++ b/cecli/commands/list_sessions.py @@ -7,6 +7,7 @@ class ListSessionsCommand(BaseCommand): NORM_NAME = "list-sessions" DESCRIPTION = "List all saved sessions in .cecli/sessions/" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/list_skills.py b/cecli/commands/list_skills.py index 752dc885c06..440b5660098 100644 --- a/cecli/commands/list_skills.py +++ b/cecli/commands/list_skills.py @@ -7,6 +7,7 @@ class ListSkillsCommand(BaseCommand): NORM_NAME = "list-skills" DESCRIPTION = "List all available skills with their states and file paths" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/load.py b/cecli/commands/load.py index 80fb743d040..e3bdae6a643 100644 --- a/cecli/commands/load.py +++ b/cecli/commands/load.py @@ -8,6 +8,7 @@ class LoadCommand(BaseCommand): NORM_NAME = "load" DESCRIPTION = "Load and execute commands from a file" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/load_hook.py b/cecli/commands/load_hook.py index f8149f69682..da312b85396 100644 --- a/cecli/commands/load_hook.py +++ b/cecli/commands/load_hook.py @@ -7,6 +7,7 @@ class LoadHookCommand(BaseCommand): NORM_NAME = "load-hook" DESCRIPTION = "Enable a specific hook by name" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/load_mcp.py b/cecli/commands/load_mcp.py index 302d568640f..83d86713a55 100644 --- a/cecli/commands/load_mcp.py +++ b/cecli/commands/load_mcp.py @@ -7,6 +7,7 @@ class LoadMcpCommand(BaseCommand): NORM_NAME = "load-mcp" DESCRIPTION = "Load MCP server(s) by name, or use '*' to load all enabled servers" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/load_session.py b/cecli/commands/load_session.py index f3c38396a8b..3278c3a2906 100644 --- a/cecli/commands/load_session.py +++ b/cecli/commands/load_session.py @@ -7,6 +7,7 @@ class LoadSessionCommand(BaseCommand): NORM_NAME = "load-session" DESCRIPTION = "Load a saved session by name or file path" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/load_skill.py b/cecli/commands/load_skill.py index 3750a3f2645..33056214f4a 100644 --- a/cecli/commands/load_skill.py +++ b/cecli/commands/load_skill.py @@ -7,6 +7,7 @@ class LoadSkillCommand(BaseCommand): NORM_NAME = "load-skill" DESCRIPTION = "Load a skill by name (agent mode only)" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/ls.py b/cecli/commands/ls.py index f04fd011d7e..934c4b1bda4 100644 --- a/cecli/commands/ls.py +++ b/cecli/commands/ls.py @@ -7,6 +7,7 @@ class LsCommand(BaseCommand): NORM_NAME = "ls" DESCRIPTION = "List all known files and indicate which are included in the chat session" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/map.py b/cecli/commands/map.py index e362dd23162..beaad37e1b5 100644 --- a/cecli/commands/map.py +++ b/cecli/commands/map.py @@ -8,6 +8,7 @@ class MapCommand(BaseCommand): NORM_NAME = "map" DESCRIPTION = "Print out the current repository map" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/model.py b/cecli/commands/model.py index 8d4212b2f27..cc053cc04bb 100644 --- a/cecli/commands/model.py +++ b/cecli/commands/model.py @@ -9,6 +9,7 @@ class ModelCommand(BaseCommand): NORM_NAME = "model" DESCRIPTION = "Switch the Main Model to a new LLM" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/models.py b/cecli/commands/models.py index 57beb1b4f24..6dbe4ed9900 100644 --- a/cecli/commands/models.py +++ b/cecli/commands/models.py @@ -8,6 +8,7 @@ class ModelsCommand(BaseCommand): NORM_NAME = "models" DESCRIPTION = "Search the list of available models" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/paste.py b/cecli/commands/paste.py index fd7fc4306b8..a7b57339bf4 100644 --- a/cecli/commands/paste.py +++ b/cecli/commands/paste.py @@ -16,6 +16,7 @@ class PasteCommand(BaseCommand): "Paste image/text from the clipboard into the chat. Optionally provide a name for the" " image." ) + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/read_only.py b/cecli/commands/read_only.py index b5f5b4d98d9..6fc127eb802 100644 --- a/cecli/commands/read_only.py +++ b/cecli/commands/read_only.py @@ -19,6 +19,7 @@ class ReadOnlyCommand(BaseCommand): DESCRIPTION = ( "Add files to the chat that are for reference only, or turn added files to read-only" ) + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/read_only_stub.py b/cecli/commands/read_only_stub.py index c75ff9b6628..b5e2b1ecb3b 100644 --- a/cecli/commands/read_only_stub.py +++ b/cecli/commands/read_only_stub.py @@ -19,6 +19,7 @@ class ReadOnlyStubCommand(BaseCommand): DESCRIPTION = ( "Add files to the chat as read-only stubs, or turn added files to read-only (stubs)" ) + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/reap_agent.py b/cecli/commands/reap_agent.py index 4093c2ac5e4..ce0beb6a24c 100644 --- a/cecli/commands/reap_agent.py +++ b/cecli/commands/reap_agent.py @@ -10,6 +10,7 @@ class ReapAgentCommand(BaseCommand): NORM_NAME = "reap-agent" DESCRIPTION = "Force destroy the active sub-agent" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/remove_hook.py b/cecli/commands/remove_hook.py index 2090a05aade..850fb755564 100644 --- a/cecli/commands/remove_hook.py +++ b/cecli/commands/remove_hook.py @@ -7,6 +7,7 @@ class RemoveHookCommand(BaseCommand): NORM_NAME = "remove-hook" DESCRIPTION = "Disable a specific hook by name" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/remove_mcp.py b/cecli/commands/remove_mcp.py index ad212da4051..b3432a97ca0 100644 --- a/cecli/commands/remove_mcp.py +++ b/cecli/commands/remove_mcp.py @@ -7,6 +7,7 @@ class RemoveMcpCommand(BaseCommand): NORM_NAME = "remove-mcp" DESCRIPTION = "Remove a MCP server by name, or use '*' to remove all" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/remove_skill.py b/cecli/commands/remove_skill.py index 97b3628ccd9..35afe8f5e42 100644 --- a/cecli/commands/remove_skill.py +++ b/cecli/commands/remove_skill.py @@ -7,6 +7,7 @@ class RemoveSkillCommand(BaseCommand): NORM_NAME = "remove-skill" DESCRIPTION = "Remove a skill by name (agent mode only)" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/reset.py b/cecli/commands/reset.py index 78841e3c1fa..f45b65f5152 100644 --- a/cecli/commands/reset.py +++ b/cecli/commands/reset.py @@ -9,6 +9,7 @@ class ResetCommand(BaseCommand): NORM_NAME = "reset" DESCRIPTION = "Drop all files and clear the chat history" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/save.py b/cecli/commands/save.py index b63c0ca9511..f7a84a816ac 100644 --- a/cecli/commands/save.py +++ b/cecli/commands/save.py @@ -8,6 +8,7 @@ class SaveCommand(BaseCommand): NORM_NAME = "save" DESCRIPTION = "Save commands to a file that can reconstruct the current chat session's files" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/save_session.py b/cecli/commands/save_session.py index 16a96d61453..8fce2bda5e6 100644 --- a/cecli/commands/save_session.py +++ b/cecli/commands/save_session.py @@ -7,6 +7,7 @@ class SaveSessionCommand(BaseCommand): NORM_NAME = "save-session" DESCRIPTION = "Save the current chat session to a named file in .cecli/sessions/" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/settings.py b/cecli/commands/settings.py index 864db4686bf..02f9bd01b85 100644 --- a/cecli/commands/settings.py +++ b/cecli/commands/settings.py @@ -8,6 +8,7 @@ class SettingsCommand(BaseCommand): NORM_NAME = "settings" DESCRIPTION = "Print out the current settings" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/think_tokens.py b/cecli/commands/think_tokens.py index 71b77e31166..4243f071f66 100644 --- a/cecli/commands/think_tokens.py +++ b/cecli/commands/think_tokens.py @@ -7,6 +7,7 @@ class ThinkTokensCommand(BaseCommand): NORM_NAME = "think-tokens" DESCRIPTION = "Set the thinking token budget, eg: 8096, 8k, 10.5k, 0.5M, or 0 to disable" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/tokens.py b/cecli/commands/tokens.py index 1ef30e6e825..38fec9ef49f 100644 --- a/cecli/commands/tokens.py +++ b/cecli/commands/tokens.py @@ -8,6 +8,7 @@ class TokensCommand(BaseCommand): NORM_NAME = "tokens" DESCRIPTION = "Report on the number of tokens used by the current chat context" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/undo.py b/cecli/commands/undo.py index 66b02c24f11..15dbfb74b82 100644 --- a/cecli/commands/undo.py +++ b/cecli/commands/undo.py @@ -9,6 +9,7 @@ class UndoCommand(BaseCommand): NORM_NAME = "undo" DESCRIPTION = "Undo the last git commit if it was done by cecli" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/utils/base_command.py b/cecli/commands/utils/base_command.py index 3200dacc961..bc7e0951350 100644 --- a/cecli/commands/utils/base_command.py +++ b/cecli/commands/utils/base_command.py @@ -37,6 +37,7 @@ class BaseCommand(ABC, metaclass=CommandMeta): NORM_NAME = None # Normalized command name (e.g., "add", "model") DESCRIPTION = None # Command description for help SCHEMA = None # Optional schema for parameter validation + show_completion_notification = True @classmethod @abstractmethod diff --git a/cecli/commands/voice.py b/cecli/commands/voice.py index 6b40fddb538..d364573da95 100644 --- a/cecli/commands/voice.py +++ b/cecli/commands/voice.py @@ -10,6 +10,7 @@ class VoiceCommand(BaseCommand): NORM_NAME = "voice" DESCRIPTION = "Record and transcribe voice input" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/commands/weak_model.py b/cecli/commands/weak_model.py index 97f75c0c2f3..effdae7ef58 100644 --- a/cecli/commands/weak_model.py +++ b/cecli/commands/weak_model.py @@ -9,6 +9,7 @@ class WeakModelCommand(BaseCommand): NORM_NAME = "weak-model" DESCRIPTION = "Switch the Weak Model to a new LLM" + show_completion_notification = False @classmethod async def execute(cls, io, coder, args, **kwargs): diff --git a/cecli/io.py b/cecli/io.py index 1a75fa6e76a..3cf149b0fb9 100644 --- a/cecli/io.py +++ b/cecli/io.py @@ -818,7 +818,9 @@ async def get_input( **kwargs, ): self.rule() - self.notify_user_input_required() + if commands.last_command_show_notification: + self.notify_user_input_required() + self.notify_user_input_required() rel_fnames = list(rel_fnames) show = "" diff --git a/cecli/run_cmd.py b/cecli/run_cmd.py index 5cbb13d6601..0e550a5c502 100644 --- a/cecli/run_cmd.py +++ b/cecli/run_cmd.py @@ -124,6 +124,20 @@ async def run_cmd_async( if platform.system() == "Windows": print("Parent process:", parent_process) + if platform.system() == "Windows": + loop = asyncio.get_running_loop() + if not isinstance(loop, asyncio.SelectorEventLoop): + # Fallback to synchronous version if not using SelectorEventLoop + return await loop.run_in_executor( + None, + run_cmd_subprocess, + command, + verbose, + cwd, + encoding, + should_print, + ) + try: process = await asyncio.create_subprocess_shell( command, diff --git a/cecli/tui/io.py b/cecli/tui/io.py index f3ff187f86f..f204bf2c44c 100644 --- a/cecli/tui/io.py +++ b/cecli/tui/io.py @@ -434,7 +434,8 @@ async def get_input( """ self.interrupted = False - self.notify_user_input_required() + if commands.last_command_show_notification: + self.notify_user_input_required() # Signal TUI that we're ready for input command_names = commands.get_commands() if commands else []