feat: add reminder edit and delete commands
This commit is contained in:
@@ -209,6 +209,20 @@ def gitea_repo(name: str, json_output: bool = typer.Option(False, "--json")):
|
|||||||
fail(str(exc), json_output)
|
fail(str(exc), json_output)
|
||||||
|
|
||||||
|
|
||||||
|
def run_remindctl(args: List[str]) -> Any:
|
||||||
|
"""Execute the supported Reminders CLI without invoking a shell."""
|
||||||
|
result = subprocess.run(["remindctl", *args], capture_output=True, text=True, check=False)
|
||||||
|
if result.returncode:
|
||||||
|
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or "remindctl command failed")
|
||||||
|
output = result.stdout.strip()
|
||||||
|
if not output:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
return json.loads(output)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {"output": output}
|
||||||
|
|
||||||
|
|
||||||
def get_required_device(name: str, json_output: bool = False) -> Device:
|
def get_required_device(name: str, json_output: bool = False) -> Device:
|
||||||
device = get_device(name)
|
device = get_device(name)
|
||||||
if not device:
|
if not device:
|
||||||
@@ -2139,6 +2153,56 @@ def macmini_reminders_create(
|
|||||||
fail(str(exc), json_output)
|
fail(str(exc), json_output)
|
||||||
|
|
||||||
|
|
||||||
|
@macmini_reminders_app.command("edit")
|
||||||
|
def macmini_reminders_edit(
|
||||||
|
reminder_id: str,
|
||||||
|
title: Optional[str] = typer.Option(None, "--title"),
|
||||||
|
list_name: Optional[str] = typer.Option(None, "--list"),
|
||||||
|
due: Optional[str] = typer.Option(None, "--due"),
|
||||||
|
notes: Optional[str] = typer.Option(None, "--notes"),
|
||||||
|
priority: Optional[str] = typer.Option(None, "--priority"),
|
||||||
|
clear_due: bool = typer.Option(False, "--clear-due"),
|
||||||
|
complete: bool = typer.Option(False, "--complete"),
|
||||||
|
incomplete: bool = typer.Option(False, "--incomplete"),
|
||||||
|
json_output: bool = typer.Option(False, "--json"),
|
||||||
|
):
|
||||||
|
"""Edit a reminder by its ID or ID prefix returned by ``reminders list``."""
|
||||||
|
if complete and incomplete:
|
||||||
|
fail("Use only one of --complete or --incomplete", json_output)
|
||||||
|
if not any((title is not None, list_name is not None, due is not None, notes is not None, priority is not None, clear_due, complete, incomplete)):
|
||||||
|
fail("Specify at least one field to edit", json_output)
|
||||||
|
args = ["edit", reminder_id]
|
||||||
|
for flag, value in (("--title", title), ("--list", list_name), ("--due", due), ("--notes", notes), ("--priority", priority)):
|
||||||
|
if value is not None:
|
||||||
|
args.extend([flag, value])
|
||||||
|
if clear_due:
|
||||||
|
args.append("--clear-due")
|
||||||
|
if complete:
|
||||||
|
args.append("--complete")
|
||||||
|
if incomplete:
|
||||||
|
args.append("--incomplete")
|
||||||
|
args.extend(["--json", "--no-input"])
|
||||||
|
try:
|
||||||
|
emit({"ok": True, "source": "remindctl", "result": run_remindctl(args)}, json_output)
|
||||||
|
except Exception as exc:
|
||||||
|
fail(str(exc), json_output)
|
||||||
|
|
||||||
|
|
||||||
|
@macmini_reminders_app.command("delete")
|
||||||
|
def macmini_reminders_delete(
|
||||||
|
reminder_ids: List[str] = typer.Argument(..., help="Reminder IDs or ID prefixes returned by reminders list."),
|
||||||
|
force: bool = typer.Option(False, "--force", help="Required: permanently delete without an interactive prompt."),
|
||||||
|
json_output: bool = typer.Option(False, "--json"),
|
||||||
|
):
|
||||||
|
"""Permanently delete one or more reminders; requires explicit --force."""
|
||||||
|
if not force:
|
||||||
|
fail("Deletion requires --force", json_output)
|
||||||
|
try:
|
||||||
|
emit({"ok": True, "source": "remindctl", "result": run_remindctl(["delete", *reminder_ids, "--force", "--json", "--no-input"])}, json_output)
|
||||||
|
except Exception as exc:
|
||||||
|
fail(str(exc), json_output)
|
||||||
|
|
||||||
|
|
||||||
def emit_deco_result(tool: str, result: Dict[str, Any], json_output: bool = False) -> None:
|
def emit_deco_result(tool: str, result: Dict[str, Any], json_output: bool = False) -> None:
|
||||||
emit({"ok": True, "source": "direct", "tool": tool, "result": result}, json_output)
|
emit({"ok": True, "source": "direct", "tool": tool, "result": result}, json_output)
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,42 @@ def test_cli_reminders_no_mcp_tool_call_remaining():
|
|||||||
assert 'call_macmini_tool("reminders_create"' not in src
|
assert 'call_macmini_tool("reminders_create"' not in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_reminders_edit_uses_remindctl(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_run(args):
|
||||||
|
captured["args"] = args
|
||||||
|
return {"updated": {"id": "r1", "title": "New title"}}
|
||||||
|
|
||||||
|
monkeypatch.setattr("reyna_cli.cli.run_remindctl", fake_run)
|
||||||
|
res = runner.invoke(app, ["macmini", "reminders", "edit", "r1", "--title", "New title", "--due", "tomorrow", "--json"])
|
||||||
|
|
||||||
|
assert res.exit_code == 0
|
||||||
|
assert captured["args"] == ["edit", "r1", "--title", "New title", "--due", "tomorrow", "--json", "--no-input"]
|
||||||
|
assert json.loads(res.stdout)["source"] == "remindctl"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_reminders_delete_requires_force(monkeypatch):
|
||||||
|
res = runner.invoke(app, ["macmini", "reminders", "delete", "r1", "--json"])
|
||||||
|
|
||||||
|
assert res.exit_code == 1
|
||||||
|
assert "--force" in res.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_reminders_delete_uses_remindctl_with_force(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_run(args):
|
||||||
|
captured["args"] = args
|
||||||
|
return {"deleted": ["r1"]}
|
||||||
|
|
||||||
|
monkeypatch.setattr("reyna_cli.cli.run_remindctl", fake_run)
|
||||||
|
res = runner.invoke(app, ["macmini", "reminders", "delete", "r1", "--force", "--json"])
|
||||||
|
|
||||||
|
assert res.exit_code == 0
|
||||||
|
assert captured["args"] == ["delete", "r1", "--force", "--json", "--no-input"]
|
||||||
|
|
||||||
|
|
||||||
def test_privacy_host_cli_has_reminders_authorize():
|
def test_privacy_host_cli_has_reminders_authorize():
|
||||||
res = runner.invoke(app, ["privacy-host", "--help"])
|
res = runner.invoke(app, ["privacy-host", "--help"])
|
||||||
assert res.exit_code == 0
|
assert res.exit_code == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user