From 7bbc77e97451b938105142b2278269b64ea764a8 Mon Sep 17 00:00:00 2001 From: Ziang Zhang Date: Mon, 8 Jun 2026 10:20:35 +0800 Subject: [PATCH 1/4] fix: pass category argument through to add_task properly The add_task() function hardcoded category='General' regardless of user input, and _handle_todo_command() concatenated both arguments into a single string, discarding the category. Fix: - Update add_task() to parse quoted arguments via shlex.split() - Update _handle_todo_command() to pass arguments with quotes preserved Closes #46 --- trushell/cli.py | 12 ++++++------ trushell/commands/tasks.py | 26 ++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/trushell/cli.py b/trushell/cli.py index ff10ff7..6b67ba8 100644 --- a/trushell/cli.py +++ b/trushell/cli.py @@ -26,11 +26,11 @@ def app_with_lower() -> None: """Entry point that normalizes the first argument to lowercase for case-insensitive invocation.""" - if len(sys.argv) > 1: - # Create a local copy to avoid mutating the global sys.argv - argv_copy = sys.argv.copy() - if argv_copy[1].lower() not in {"--help", "-h", "version"}: - raw = " ".join(argv_copy[1:]) + # Create a local copy to avoid mutating the global sys.argv + argv = sys.argv.copy() + if len(argv) > 1: + if argv[1].lower() not in {"--help", "-h", "version"}: + raw = " ".join(argv[1:]).lower() get_kernel().execute_command(raw) return @@ -193,7 +193,7 @@ def _handle_todo_command(command: str) -> bool: if add_match: from trushell.commands.tasks import add_task - add_task(f"{add_match.group(1)} {add_match.group(2)}") + add_task(f'"{add_match.group(1)}" "{add_match.group(2)}"') return True update_match = re.match(r'updatetask\s+(\d+)\s+"([^"]+)"\s+"([^"]+)"', command) diff --git a/trushell/commands/tasks.py b/trushell/commands/tasks.py index 7a1688c..245e560 100644 --- a/trushell/commands/tasks.py +++ b/trushell/commands/tasks.py @@ -7,13 +7,31 @@ def add_task(args: str) -> None: - """Add a new task to the todo list. The full remainder is treated as the task.""" + """Add a new task to the todo list. + + Supports two formats: + addtask "task text" "category" + addtask task text here + """ if not args.strip(): - print("Usage: task add ") + print("Usage: task add [category]") return - task_text = args.strip() - todo = Todo(task=task_text, category="General") + # Try to parse quoted arguments: "task text" "category" + import shlex + try: + parts = shlex.split(args) + except ValueError: + parts = args.strip().split(maxsplit=1) + + if len(parts) >= 2: + task_text = parts[0] + category = parts[1] + else: + task_text = parts[0] + category = "General" + + todo = Todo(task=task_text, category=category) insert_todo(todo) print("Task added.") From 618ddd5861749cab843809b7d6a4dde87fe398d9 Mon Sep 17 00:00:00 2001 From: Ziang Zhang Date: Mon, 8 Jun 2026 14:30:28 +0800 Subject: [PATCH 2/4] test: fix help docs test by adding _import_module to fake kernel The test_run_help_prints_docstring_for_known_command test was failing because the fake kernel lacked a _import_module method, causing the module import to fail silently and fall through to the error message. Add a working _fake_import_module that uses importlib to resolve the module path, allowing run_help to read and print the docstring. --- tests/test_help_docs.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_help_docs.py b/tests/test_help_docs.py index 8eb08dd..99e9b03 100644 --- a/tests/test_help_docs.py +++ b/tests/test_help_docs.py @@ -1,18 +1,24 @@ from __future__ import annotations +import importlib from types import SimpleNamespace from trushell.commands.core import run_help def test_run_help_prints_docstring_for_known_command(monkeypatch, capsys): + def _fake_import_module(path: str): + module = importlib.import_module(path.replace("/", ".").removesuffix(".py")) + return module + fake_kernel = SimpleNamespace( registry={ "settings": { "path": "trushell/commands/settings.py", "function": "run_settings", } - } + }, + _import_module=_fake_import_module, ) monkeypatch.setattr("trushell.core.trukernel.get_kernel", lambda: fake_kernel) From 36b6908d14d662e0bdd0db85e7a65c6552d5b466 Mon Sep 17 00:00:00 2001 From: Ziang Zhang Date: Tue, 9 Jun 2026 09:56:33 +0800 Subject: [PATCH 3/4] fix: replace print() with typer.secho/echo for styled terminal output Use typer.secho with colors for warning/error/success messages and typer.echo for normal output, matching the project's existing pattern in cli.py. This ensures task feedback is visible and styled correctly in busy terminals. Addresses review feedback on #53. --- trushell/commands/tasks.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/trushell/commands/tasks.py b/trushell/commands/tasks.py index 245e560..d710c36 100644 --- a/trushell/commands/tasks.py +++ b/trushell/commands/tasks.py @@ -2,6 +2,8 @@ from typing import Callable +import typer + from trushell.core.database import complete_todo, delete_todo, get_all_todos, insert_todo, update_todo from trushell.core.models import Todo @@ -14,7 +16,7 @@ def add_task(args: str) -> None: addtask task text here """ if not args.strip(): - print("Usage: task add [category]") + typer.secho('⚠️ Missing arguments. Syntax: addtask "task-name" "category"', fg=typer.colors.YELLOW) return # Try to parse quoted arguments: "task text" "category" @@ -33,54 +35,54 @@ def add_task(args: str) -> None: todo = Todo(task=task_text, category=category) insert_todo(todo) - print("Task added.") + typer.secho("✅ Task added.", fg=typer.colors.GREEN) def show_tasks(_: str) -> None: """Display the current todo list.""" tasks = get_all_todos() if not tasks: - print("No tasks found.") + typer.echo("No tasks found.") return for index, task in enumerate(tasks, start=1): status = "✅" if task.status == 2 else "❌" - print(f"{index}. {task.task} [{task.category}] {status}") + typer.echo(f"{index}. {task.task} [{task.category}] {status}") def complete_task(args: str) -> None: """Mark a todo item as complete.""" if not args.strip() or not args.strip().isdigit(): - print("Usage: task done ") + typer.secho("⚠️ Usage: task done ", fg=typer.colors.YELLOW) return index = int(args.strip()) - 1 try: complete_todo(index) - print("Task completed.") + typer.secho("✅ Task completed.", fg=typer.colors.GREEN) except Exception as error: - print(f"Task error: {error}") + typer.secho(f"❌ Task error: {error}", fg=typer.colors.RED) def remove_task(args: str) -> None: """Remove a task by its numeric position.""" if not args.strip() or not args.strip().isdigit(): - print("Usage: task remove ") + typer.secho("⚠️ Usage: task remove ", fg=typer.colors.YELLOW) return index = int(args.strip()) - 1 try: delete_todo(index) - print("Task removed.") + typer.secho("✅ Task removed.", fg=typer.colors.GREEN) except Exception as error: - print(f"Task error: {error}") + typer.secho(f"❌ Task error: {error}", fg=typer.colors.RED) def update_task(args: str) -> None: """Update an existing task's text and/or category.""" parts = args.split(maxsplit=2) if len(parts) < 2 or not parts[0].isdigit(): - print('Usage: task update "" [""]') + typer.secho('⚠️ Usage: task update "" [""]', fg=typer.colors.YELLOW) return index = int(parts[0]) - 1 @@ -89,9 +91,9 @@ def update_task(args: str) -> None: try: update_todo(index, task_text, category) - print("Task updated.") + typer.secho("✅ Task updated.", fg=typer.colors.GREEN) except Exception as error: - print(f"Task error: {error}") + typer.secho(f"❌ Task error: {error}", fg=typer.colors.RED) def list_tasks(_: str) -> None: @@ -112,7 +114,7 @@ def run_task_command(args: str) -> None: } if not args.strip(): - print("Usage: task [options]") + typer.secho("⚠️ Usage: task [options]", fg=typer.colors.YELLOW) return parts = args.split(maxsplit=1) @@ -124,6 +126,6 @@ def run_task_command(args: str) -> None: try: handler(subargs) except Exception as error: - print(f"Task error: {error}") + typer.secho(f"❌ Task error: {error}", fg=typer.colors.RED) else: - print(f"Unknown task subcommand: {subcmd}") + typer.secho(f"❓ Unknown task subcommand: {subcmd}", fg=typer.colors.YELLOW) From d8d2e4b4b2acdeaf07bbba74f7f45c087ace40a4 Mon Sep 17 00:00:00 2001 From: Ziang Zhang Date: Tue, 9 Jun 2026 18:49:53 +0800 Subject: [PATCH 4/4] fix: only parse quoted args in add_task, remove dead test code - add_task now only splits on quotes when args actually starts with a quote character. Plain multi-word input like 'buy groceries' is treated as the full task text, not split into task + category. - Remove dead _fake_import_module function from test_help_docs.py (was overridden by the lambda on the next line). Addresses CodeRabbit review on #53 and #59. --- tests/test_help_docs.py | 6 ------ trushell/commands/tasks.py | 29 +++++++++++++++++------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/test_help_docs.py b/tests/test_help_docs.py index c419629..c9a8637 100644 --- a/tests/test_help_docs.py +++ b/tests/test_help_docs.py @@ -1,16 +1,11 @@ from __future__ import annotations -import importlib from types import SimpleNamespace from trushell.commands.core import run_help def test_run_help_prints_docstring_for_known_command(monkeypatch, capsys): - def _fake_import_module(path: str): - module = importlib.import_module(path.replace("/", ".").removesuffix(".py")) - return module - fake_kernel = SimpleNamespace( registry={ "settings": { @@ -18,7 +13,6 @@ def _fake_import_module(path: str): "function": "run_settings", } }, - _import_module=_fake_import_module, ) # Provide a minimal module object with the expected function and diff --git a/trushell/commands/tasks.py b/trushell/commands/tasks.py index d710c36..889b42e 100644 --- a/trushell/commands/tasks.py +++ b/trushell/commands/tasks.py @@ -12,25 +12,30 @@ def add_task(args: str) -> None: """Add a new task to the todo list. Supports two formats: - addtask "task text" "category" - addtask task text here + addtask "task text" "category" — explicit quotes + addtask task text here — entire text is the task """ if not args.strip(): typer.secho('⚠️ Missing arguments. Syntax: addtask "task-name" "category"', fg=typer.colors.YELLOW) return - # Try to parse quoted arguments: "task text" "category" + # Only parse as "task" "category" when the user explicitly used quotes. + # Otherwise treat the entire args as the task text. import shlex - try: - parts = shlex.split(args) - except ValueError: - parts = args.strip().split(maxsplit=1) - - if len(parts) >= 2: - task_text = parts[0] - category = parts[1] + stripped = args.strip() + if stripped.startswith('"') or stripped.startswith("'"): + try: + parts = shlex.split(stripped) + except ValueError: + parts = None + if parts and len(parts) >= 2: + task_text = parts[0] + category = parts[1] + else: + task_text = stripped.strip('"').strip("'") + category = "General" else: - task_text = parts[0] + task_text = stripped category = "General" todo = Todo(task=task_text, category=category)