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.
- reclaim_cli-0.1.1/CHANGELOG.md +21 -0
- reclaim_cli-0.1.1/LICENSE +21 -0
- reclaim_cli-0.1.1/MANIFEST.in +24 -0
- reclaim_cli-0.1.1/PKG-INFO +22 -0
- reclaim_cli-0.1.1/README.md +98 -0
- reclaim_cli-0.1.1/TODO.md +14 -0
- reclaim_cli-0.1.1/pyproject.toml +62 -0
- reclaim_cli-0.1.1/setup.cfg +4 -0
- reclaim_cli-0.1.1/src/reclaim/__init__.py +6 -0
- reclaim_cli-0.1.1/src/reclaim/__main__.py +74 -0
- reclaim_cli-0.1.1/src/reclaim/commands/__init__.py +33 -0
- reclaim_cli-0.1.1/src/reclaim/commands/add-time.py +39 -0
- reclaim_cli-0.1.1/src/reclaim/commands/base.py +57 -0
- reclaim_cli-0.1.1/src/reclaim/commands/create-task.py +111 -0
- reclaim_cli-0.1.1/src/reclaim/commands/delete-task.py +41 -0
- reclaim_cli-0.1.1/src/reclaim/commands/edit-task.py +110 -0
- reclaim_cli-0.1.1/src/reclaim/commands/list-tasks.py +154 -0
- reclaim_cli-0.1.1/src/reclaim/commands/log-work.py +54 -0
- reclaim_cli-0.1.1/src/reclaim/commands/mark-task.py +55 -0
- reclaim_cli-0.1.1/src/reclaim/commands/show-task.py +109 -0
- reclaim_cli-0.1.1/src/reclaim/commands/start-task.py +54 -0
- reclaim_cli-0.1.1/src/reclaim/commands/stop-task.py +41 -0
- reclaim_cli-0.1.1/src/reclaim/utils.py +203 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/PKG-INFO +22 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/SOURCES.txt +30 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/dependency_links.txt +1 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/entry_points.txt +2 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/requires.txt +15 -0
- reclaim_cli-0.1.1/src/reclaim_cli.egg-info/top_level.txt +1 -0
- reclaim_cli-0.1.1/tests/__init__.py +1 -0
- reclaim_cli-0.1.1/tests/conftest.py +33 -0
- 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,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
|