reclaim-cli 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. reclaim_cli-0.1.1/CHANGELOG.md +21 -0
  2. reclaim_cli-0.1.1/LICENSE +21 -0
  3. reclaim_cli-0.1.1/MANIFEST.in +24 -0
  4. reclaim_cli-0.1.1/PKG-INFO +22 -0
  5. reclaim_cli-0.1.1/README.md +98 -0
  6. reclaim_cli-0.1.1/TODO.md +14 -0
  7. reclaim_cli-0.1.1/pyproject.toml +62 -0
  8. reclaim_cli-0.1.1/setup.cfg +4 -0
  9. reclaim_cli-0.1.1/src/reclaim/__init__.py +6 -0
  10. reclaim_cli-0.1.1/src/reclaim/__main__.py +74 -0
  11. reclaim_cli-0.1.1/src/reclaim/commands/__init__.py +33 -0
  12. reclaim_cli-0.1.1/src/reclaim/commands/add-time.py +39 -0
  13. reclaim_cli-0.1.1/src/reclaim/commands/base.py +57 -0
  14. reclaim_cli-0.1.1/src/reclaim/commands/create-task.py +111 -0
  15. reclaim_cli-0.1.1/src/reclaim/commands/delete-task.py +41 -0
  16. reclaim_cli-0.1.1/src/reclaim/commands/edit-task.py +110 -0
  17. reclaim_cli-0.1.1/src/reclaim/commands/list-tasks.py +154 -0
  18. reclaim_cli-0.1.1/src/reclaim/commands/log-work.py +54 -0
  19. reclaim_cli-0.1.1/src/reclaim/commands/mark-task.py +55 -0
  20. reclaim_cli-0.1.1/src/reclaim/commands/show-task.py +109 -0
  21. reclaim_cli-0.1.1/src/reclaim/commands/start-task.py +54 -0
  22. reclaim_cli-0.1.1/src/reclaim/commands/stop-task.py +41 -0
  23. reclaim_cli-0.1.1/src/reclaim/utils.py +203 -0
  24. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/PKG-INFO +22 -0
  25. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/SOURCES.txt +30 -0
  26. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/dependency_links.txt +1 -0
  27. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/entry_points.txt +2 -0
  28. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/requires.txt +15 -0
  29. reclaim_cli-0.1.1/src/reclaim_cli.egg-info/top_level.txt +1 -0
  30. reclaim_cli-0.1.1/tests/__init__.py +1 -0
  31. reclaim_cli-0.1.1/tests/conftest.py +33 -0
  32. reclaim_cli-0.1.1/tests/test_cmds.py +130 -0
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.1] - 2025-04-26
9
+
10
+ ### Added
11
+
12
+ - Initial release of Reclaim CLI
13
+ - Basic task management commands (create, list, show, edit, delete)
14
+ - Time tracking functionality
15
+ - Configuration management
16
+ - Command-line interface with help formatting
17
+
18
+ ### Changed
19
+
20
+ - Fixed issues with double parsing of durations
21
+ - Renamed location of patched reclaim-sdk on Github
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Konrad Rieck (<konrad@mlsec.org>)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include pyproject.toml
5
+
6
+ recursive-include tests *
7
+
8
+ global-exclude __pycache__
9
+ global-exclude *.py[cod]
10
+ global-exclude *.so
11
+ global-exclude *.egg-info
12
+
13
+ global-exclude build/
14
+ global-exclude dist/
15
+
16
+ global-exclude .git
17
+ global-exclude .gitignore
18
+ global-exclude .coverage
19
+ global-exclude .pytest_cache
20
+ global-exclude .mypy_cache
21
+ global-exclude .ruff_cache
22
+ global-exclude .DS_Store
23
+ global-exclude .idea/
24
+ global-exclude .vscode/
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: reclaim-cli
3
+ Version: 0.1.1
4
+ Summary: CLI for Reclaim.ai service
5
+ License: MIT
6
+ Requires-Python: >=3.8
7
+ License-File: LICENSE
8
+ Requires-Dist: PyYAML>=6.0
9
+ Requires-Dist: dateparser==1.2.1
10
+ Requires-Dist: rich==13.9.4
11
+ Requires-Dist: reclaim-sdk-fixed==0.6.3.post1
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest; extra == "dev"
14
+ Requires-Dist: pytest-cov; extra == "dev"
15
+ Requires-Dist: black; extra == "dev"
16
+ Requires-Dist: isort; extra == "dev"
17
+ Requires-Dist: pre-commit; extra == "dev"
18
+ Requires-Dist: flake8; extra == "dev"
19
+ Requires-Dist: flake8-docstrings; extra == "dev"
20
+ Requires-Dist: flake8-bugbear; extra == "dev"
21
+ Requires-Dist: flake8-quotes; extra == "dev"
22
+ Dynamic: license-file
@@ -0,0 +1,98 @@
1
+ # Reclaim CLI
2
+
3
+ Copyright (c) 2025 Konrad Rieck (<konrad@mlsec.org>)
4
+
5
+ ## About
6
+
7
+ This is a simple Python CLI for the Reclaim service.
8
+
9
+ ## Usage
10
+
11
+ The `reclaim` tool provides a simple and flexible command-line interface (CLI) for managing tasks at [Reclaim.ai](https://reclaim.ai). At its core, `reclaim` operates through a set of commands, each targeting a specific aspect of task management. The commands allow you to create tasks, log work, lists tasks, start and stop time tracking, and more.
12
+
13
+ To see the available commands, you can run:
14
+
15
+ ```console
16
+ reclaim --help
17
+ usage: reclaim [options] <command> ...
18
+
19
+ Reclaim CLI
20
+
21
+ positional arguments:
22
+ add-time (add) add time to a task
23
+ create-task (create) create a task
24
+ delete-task (delete) delete a task
25
+ list-tasks (list) list tasks
26
+ log-work (log) log work to a task
27
+ mark-task (mark) mark a task (in)complete
28
+ show-task (show) show a task
29
+ start-task (start) start a task
30
+ stop-task (stop) stop a task
31
+
32
+ options:
33
+ -h, --help show this help message and exit
34
+ -c <file>, --config <file> set config file (default: ~/.reclaim)
35
+ ```
36
+
37
+ Each command follows a consistent pattern and can be extended with its own options. Simply run `reclaim <command> --help`.
38
+
39
+ ## Example
40
+
41
+ Here is a simple example illustrating how to use the tool. Suppose you want to create a task for writing a new blog post with a duration of 8 hours and a due date in 20 days. You would run:
42
+
43
+ ```sh
44
+ reclaim create-task "Write new blog post" --duration 8h -due "in 10 days"
45
+ # ✓ Created | Id: 5g5p4 | Title: Write new blog post
46
+ ```
47
+
48
+ The output of the tool is shown as comment in the example. You can then list your tasks like this:
49
+
50
+ ```sh
51
+ reclaim list-tasks
52
+ # Id Due Left Prog State Title
53
+ # 5g5p4 2025-04-13 8h0m 0% N3 Write new blog post
54
+ ```
55
+
56
+ You task has state `N` (new) with default priority 3. Later, you realize that you need less time for the task. Simply update it using its identifier:
57
+
58
+ ```sh
59
+ reclaim edit-task 5g5p4 --duration 4h
60
+ # ✓ Edited | Id: 5g5p4 | Title: Write new blog post
61
+ ```
62
+
63
+ Eventually, you notice that nobody reads blogs anymore, so you delete the task and move on:
64
+
65
+ ```sh
66
+ reclaim delete-task 5g5p4
67
+ # ✓ Deleted | Id: 5g5p4 | Title: Write new blog post
68
+ ```
69
+
70
+ ## Installation
71
+
72
+ The tool is easiest installed directly from Github. You can run
73
+
74
+ ```sh
75
+ pip install git+https://github.com/rieck/reclaim-cli.git
76
+ ```
77
+
78
+ This will install the latest version of the tool directly from the repository. You can also install it in development mode by cloning the repository and running:
79
+
80
+ ```sh
81
+ git clone https://github.com/rieck/reclaim-cli.git
82
+ cd reclaim-cli
83
+ pip install -e ".[dev]"
84
+ ```
85
+
86
+ After installation, the `reclaim` command will be available in your terminal. If you want to work on the tool, you can use the following tools to streamline your implementation.
87
+
88
+ ```sh
89
+ pre-commit install
90
+ black src/
91
+ isort src/
92
+ flake8 src/
93
+ pytest
94
+ ```
95
+
96
+ ## Dependencies
97
+
98
+ The tool relies on the [unofficial Reclaim.ai Python SDK](https://github.com/labiso-gmbh/reclaim-sdk) developed by Labiso GmbH. However, since it requires functionality beyond what is available in version `v0.6.3` of the SDK, the dependency is installed from a [patched fork](https://github.com/rieck/reclaim-sdk/tree/fixed).
@@ -0,0 +1,14 @@
1
+ # TODOs
2
+
3
+ - [X] Create separate parsing for dates and times
4
+ - [X] Consider option to align printing functions
5
+ - [X] Support relative dates to "now", e.g. "in 2 days"
6
+ - [X] Implement `show` command to show all about a task
7
+ - [X] Implement `edit` command to change a task
8
+ - [X] Check out `rich` package for display functions
9
+ - [x] Implement some test cases
10
+ - [x] Improve packaging and maintance scripts
11
+ - [ ] Implement load estimation (this week, next week, ...)
12
+ - [ ] Detect incorrect dates
13
+ - [ ] Fix timezone madness when testing on Github
14
+ - [ ] Fix problem with snooze_until int vs. str
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "packaging>=24.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "reclaim-cli"
7
+ version = "0.1.1"
8
+ description = "CLI for Reclaim.ai service"
9
+ license = {text = "MIT"}
10
+ requires-python = ">=3.8"
11
+ dependencies = [
12
+ "PyYAML>=6.0",
13
+ "dateparser==1.2.1",
14
+ "rich==13.9.4",
15
+ "reclaim-sdk-fixed==0.6.3.post1",
16
+ ]
17
+
18
+ [project.scripts]
19
+ reclaim = "reclaim.__main__:main"
20
+
21
+ [tool.setuptools]
22
+ package-dir = {"" = "src"}
23
+ packages = ["reclaim", "reclaim.commands"]
24
+
25
+ # Testing configuration
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+ addopts = "-v --cov=reclaim"
29
+ filterwarnings = [
30
+ "ignore::DeprecationWarning:pydantic.*"
31
+ ]
32
+
33
+ # Type checking
34
+ [tool.mypy]
35
+ python_version = "3.8"
36
+ warn_return_any = true
37
+ warn_unused_configs = true
38
+ disallow_untyped_defs = true
39
+
40
+ # Code formatting
41
+ [tool.black]
42
+ line-length = 79
43
+ target-version = ['py38']
44
+ include = '\.pyi?$'
45
+
46
+ # Import sorting
47
+ [tool.isort]
48
+ profile = "black"
49
+ multi_line_output = 3
50
+
51
+ [project.optional-dependencies]
52
+ dev = [
53
+ "pytest",
54
+ "pytest-cov",
55
+ "black",
56
+ "isort",
57
+ "pre-commit",
58
+ "flake8",
59
+ "flake8-docstrings",
60
+ "flake8-bugbear",
61
+ "flake8-quotes",
62
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """Reclaim CLI Tool.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,74 @@
1
+ """Reclaim CLI Main.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ import argparse
7
+ import os
8
+ import sys
9
+
10
+ import reclaim.commands as commands
11
+ from reclaim.utils import HelpFormatter, load_config, set_api_key
12
+
13
+
14
+ def parse_args(cmds):
15
+ """Parse command line arguments."""
16
+ parser = argparse.ArgumentParser(
17
+ prog="reclaim",
18
+ description="Reclaim CLI",
19
+ formatter_class=HelpFormatter,
20
+ )
21
+
22
+ # Global options
23
+ parser.add_argument(
24
+ "-c",
25
+ "--config",
26
+ type=str,
27
+ metavar="<file>",
28
+ default="~/.reclaim",
29
+ help="set config file",
30
+ )
31
+
32
+ # Create subparsers
33
+ subparsers = parser.add_subparsers(dest="command", required=True)
34
+ for cmd in cmds:
35
+ cmd.parse_args(subparsers)
36
+
37
+ # Parse global args
38
+ args = parser.parse_args()
39
+
40
+ # Expand user home directory
41
+ args.config = os.path.expanduser(args.config)
42
+
43
+ # Validate arguments
44
+ for cmd in cmds:
45
+ if args.command == cmd.name or args.command in cmd.aliases:
46
+ cmd.validate_args(args)
47
+
48
+ return args
49
+
50
+
51
+ def format_exception(error):
52
+ """Format an error message."""
53
+ return f"Error: {str(error)}"
54
+
55
+
56
+ def main():
57
+ """Run the Reclaim CLI."""
58
+ try:
59
+ cmds = commands.load()
60
+ args = parse_args(cmds)
61
+ args = load_config(args)
62
+ set_api_key(args)
63
+ args.func(args)
64
+
65
+ except Exception as e:
66
+ print(format_exception(e), file=sys.stderr)
67
+ import traceback
68
+
69
+ traceback.print_exc()
70
+ sys.exit(1)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,33 @@
1
+ """Load and initialize the commands.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ import glob
7
+ import os
8
+
9
+ from .base import Command
10
+
11
+
12
+ def load():
13
+ """Find all files in the command directory and import them."""
14
+ cmd_dir = os.path.relpath(os.path.dirname(__file__))
15
+
16
+ # get cmd files
17
+ cmd_files = [
18
+ filename
19
+ for filename in glob.glob("%s/*.py" % cmd_dir)
20
+ if not filename.endswith("__init__.py")
21
+ ]
22
+
23
+ # import all cmds (except base cmd)
24
+ for cmd_file in cmd_files:
25
+ module_name = os.path.splitext(cmd_file)[0]
26
+ module_name = module_name[module_name.find("reclaim/commands") :]
27
+ module_name = module_name.replace("/", ".")
28
+ if not module_name.endswith("base"):
29
+ __import__(module_name)
30
+
31
+ cmds = Command.__subclasses__()
32
+ cmds = [c() for c in cmds] # Initialize the commands
33
+ return sorted(cmds, key=lambda x: x.name)
@@ -0,0 +1,39 @@
1
+ """Command to add time to a task at Reclaim.ai.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ from ..utils import get_task, print_done, str_duration
7
+ from .base import Command
8
+
9
+
10
+ class AddTimeCommand(Command):
11
+ """Add time to task at Reclaim.ai."""
12
+
13
+ name = "add-time"
14
+ description = "add time to a task"
15
+ aliases = ["add"]
16
+
17
+ def parse_args(self, subparsers):
18
+ """Add arguments to the subparser."""
19
+ subparser = super().parse_args(subparsers)
20
+
21
+ subparser.add_argument(
22
+ "id", type=str, metavar="<id>", help="task id to add time to"
23
+ )
24
+ subparser.add_argument(
25
+ "duration", type=str, metavar="<duration>", help="duration to add"
26
+ )
27
+
28
+ return subparser
29
+
30
+ def run(self, args):
31
+ """Add time to task at Reclaim.ai."""
32
+ task = get_task(args.id)
33
+
34
+ # Add time to task
35
+ task.add_time(args.duration / 60) # Expects hours
36
+ dur = str_duration(args.duration)
37
+ print_done(f"Added: {dur}", task)
38
+
39
+ return task
@@ -0,0 +1,57 @@
1
+ """Base class for CLI commands.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ from ..utils import (
7
+ HelpFormatter,
8
+ parse_datetime,
9
+ parse_duration,
10
+ parse_priority,
11
+ str_to_id,
12
+ )
13
+
14
+
15
+ class Command(object):
16
+ """Abstract base class for CLI commands."""
17
+
18
+ name = None # Command name
19
+ description = None # Command description
20
+ aliases = [] # Command aliases
21
+
22
+ def parse_args(self, subparsers):
23
+ """Add arguments to the subparser."""
24
+ subparser = subparsers.add_parser(
25
+ self.name,
26
+ help=self.description,
27
+ aliases=self.aliases,
28
+ formatter_class=HelpFormatter,
29
+ )
30
+ subparser.set_defaults(func=self.run)
31
+ return subparser
32
+
33
+ def run(self, args):
34
+ """Execute the command."""
35
+ pass
36
+
37
+ def validate_args(self, args):
38
+ """Validate and transform command arguments."""
39
+ check_args = {
40
+ "id": str_to_id,
41
+ "snooze_until": parse_datetime,
42
+ "due": parse_datetime,
43
+ "log_time": parse_datetime,
44
+ "priority": parse_priority,
45
+ "duration": parse_duration,
46
+ "min_chunk_size": parse_duration,
47
+ "max_chunk_size": parse_duration,
48
+ }
49
+
50
+ for name, validate in check_args.items():
51
+ if hasattr(args, name) and getattr(args, name) is not None:
52
+ try:
53
+ setattr(args, name, validate(getattr(args, name)))
54
+ except ValueError as e:
55
+ raise ValueError(f"Invalid {name}: {str(e)}")
56
+
57
+ return args
@@ -0,0 +1,111 @@
1
+ """Command to create a task at Reclaim.ai.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ from reclaim_sdk.resources.task import Task
7
+
8
+ from ..utils import print_done
9
+ from .base import Command
10
+
11
+
12
+ class CreateTaskCommand(Command):
13
+ """Create a task at Reclaim.ai."""
14
+
15
+ name = "create-task"
16
+ description = "create a task"
17
+ aliases = ["create"]
18
+
19
+ def parse_args(self, subparsers):
20
+ """Add arguments to the subparser."""
21
+ subparser = super().parse_args(subparsers)
22
+
23
+ subparser.add_argument(
24
+ "title", type=str, metavar="<title>", help="title of the task"
25
+ )
26
+
27
+ subparser.add_argument(
28
+ "-d",
29
+ "--due",
30
+ type=str,
31
+ metavar="<datetime>",
32
+ help="due date of the task",
33
+ default=None,
34
+ )
35
+ subparser.add_argument(
36
+ "-p",
37
+ "--priority",
38
+ type=str,
39
+ metavar="<priority>",
40
+ help="priority of the task",
41
+ default=None,
42
+ )
43
+ subparser.add_argument(
44
+ "-D",
45
+ "--duration",
46
+ type=str,
47
+ metavar="<duration>",
48
+ help="duration of the task",
49
+ default=None,
50
+ )
51
+ subparser.add_argument(
52
+ "-m",
53
+ "--min-chunk-size",
54
+ type=str,
55
+ metavar="<duration>",
56
+ help="minimum chunk size",
57
+ default=None,
58
+ )
59
+ subparser.add_argument(
60
+ "-M",
61
+ "--max-chunk-size",
62
+ type=str,
63
+ metavar="<duration>",
64
+ help="maximum chunk size",
65
+ default=None,
66
+ )
67
+ subparser.add_argument(
68
+ "-s",
69
+ "--snooze-until",
70
+ type=str,
71
+ metavar="<datetime>",
72
+ help="snooze until",
73
+ default=None,
74
+ )
75
+
76
+ return subparser
77
+
78
+ def validate_args(self, args):
79
+ """Validate and transform command arguments."""
80
+ args = super().validate_args(args)
81
+
82
+ # Add custom checks here
83
+ return args
84
+
85
+ def run(self, args):
86
+ """Create task at Reclaim.ai."""
87
+ task_args = {"title": args.title}
88
+
89
+ # Prepare optional arguments
90
+ if args.due:
91
+ task_args["due"] = args.due
92
+ if args.snooze_until:
93
+ task_args["snoozeUntil"] = args.snooze_until
94
+ if args.priority:
95
+ task_args["priority"] = args.priority
96
+
97
+ # Create task and save it
98
+ task = Task(**task_args)
99
+
100
+ # Set optional arguments
101
+ if args.duration:
102
+ task.duration = args.duration / 60 # Hours
103
+ if args.min_chunk_size:
104
+ task.min_chunk_size = int(args.min_chunk_size / 15) # Chunks
105
+ if args.max_chunk_size:
106
+ task.max_chunk_size = int(args.max_chunk_size / 15) # Chunks
107
+
108
+ # Save task
109
+ task.save()
110
+ print_done("Created", task)
111
+ return task
@@ -0,0 +1,41 @@
1
+ """Command to delete a task at Reclaim.ai.
2
+
3
+ Copyright (c) 2025 Konrad Rieck <konrad@mlsec.org>
4
+ """
5
+
6
+ from ..utils import get_task, print_done
7
+ from .base import Command
8
+
9
+
10
+ class DeleteTaskCommand(Command):
11
+ """Delete a task at Reclaim.ai."""
12
+
13
+ name = "delete-task"
14
+ description = "delete a task"
15
+ aliases = ["delete"]
16
+
17
+ def parse_args(self, subparsers):
18
+ """Add arguments to the subparser."""
19
+ subparser = super().parse_args(subparsers)
20
+
21
+ subparser.add_argument(
22
+ "id", type=str, metavar="<id>", help="task id to delete"
23
+ )
24
+
25
+ return subparser
26
+
27
+ def validate_args(self, args):
28
+ """Validate and transform command arguments."""
29
+ args = super().validate_args(args)
30
+
31
+ # Add custom checks here
32
+ return args
33
+
34
+ def run(self, args):
35
+ """Delete tasks at Reclaim.ai."""
36
+ task = get_task(args.id)
37
+
38
+ # Delete task
39
+ task.delete()
40
+ print_done("Deleted", task)
41
+ return None