ggltasks 0.1.7__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.
- ggltasks-0.1.7/LICENSE +0 -0
- ggltasks-0.1.7/PKG-INFO +102 -0
- ggltasks-0.1.7/README.md +84 -0
- ggltasks-0.1.7/ggltasks/__init__.py +0 -0
- ggltasks-0.1.7/ggltasks/auth.py +53 -0
- ggltasks-0.1.7/ggltasks/local_storage.py +37 -0
- ggltasks-0.1.7/ggltasks/main.py +444 -0
- ggltasks-0.1.7/ggltasks/task_service.py +514 -0
- ggltasks-0.1.7/ggltasks/ui_manager.py +321 -0
- ggltasks-0.1.7/ggltasks.egg-info/PKG-INFO +102 -0
- ggltasks-0.1.7/ggltasks.egg-info/SOURCES.txt +15 -0
- ggltasks-0.1.7/ggltasks.egg-info/dependency_links.txt +1 -0
- ggltasks-0.1.7/ggltasks.egg-info/entry_points.txt +2 -0
- ggltasks-0.1.7/ggltasks.egg-info/requires.txt +5 -0
- ggltasks-0.1.7/ggltasks.egg-info/top_level.txt +1 -0
- ggltasks-0.1.7/pyproject.toml +33 -0
- ggltasks-0.1.7/setup.cfg +4 -0
ggltasks-0.1.7/LICENSE
ADDED
|
File without changes
|
ggltasks-0.1.7/PKG-INFO
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ggltasks
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Summary: TUI for Google Tasks
|
|
5
|
+
Author-email: Noble Alex <noblejosephalex@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Environment :: Console :: Curses
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: google-api-python-client
|
|
13
|
+
Requires-Dist: google-auth-oauthlib
|
|
14
|
+
Requires-Dist: google-auth-httplib2
|
|
15
|
+
Requires-Dist: uni-curses
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# ggltasks
|
|
20
|
+
|
|
21
|
+
A lightning-fast, robust TUI for Google Tasks, built for terminal power users.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
* **Async UI**: Network requests are threaded. The app boots instantly and never freezes.
|
|
26
|
+
* **Native Sync**: Moves, completions, and deletions flawlessly sync with the official Google backend.
|
|
27
|
+
* **Smart Timestamps**: Due dates and "Last Updated" timestamps.
|
|
28
|
+
* **Vim Navigation**: Navigate entirely using `h/j/k/l`.
|
|
29
|
+
|
|
30
|
+
## Screenshots
|
|
31
|
+
|
|
32
|
+
<img width="1365" height="742" alt="image" src="https://github.com/user-attachments/assets/4c51a8ba-eac3-4a02-ab62-060d91150941" />
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Google requires you to generate a personal "Client Secret" to access your data securely.
|
|
37
|
+
|
|
38
|
+
### Step 1: Get Google API Credentials
|
|
39
|
+
1. Go to the [Google Cloud Console](https://console.cloud.google.com/).
|
|
40
|
+
2. Create a **New Project** (e.g., `ggltasks-cli`).
|
|
41
|
+
3. Search for **Google Tasks API** and click **Enable**.
|
|
42
|
+
4. Go to **APIs & Services → Credentials**.
|
|
43
|
+
5. Click **+ CREATE CREDENTIALS** → **OAuth client ID**. *(Choose "Desktop app" as the Application type).*
|
|
44
|
+
6. Download the JSON file and rename it exactly to `client_secrets.json`.
|
|
45
|
+
|
|
46
|
+
### Step 2: Install the App
|
|
47
|
+
1. Create a hidden config folder:
|
|
48
|
+
```bash
|
|
49
|
+
mkdir ~/.ggltasks
|
|
50
|
+
```
|
|
51
|
+
2. Move your `client_secrets.json` file into `~/.ggltasks`.
|
|
52
|
+
3. Install via pip:
|
|
53
|
+
```bash
|
|
54
|
+
pip install ggltasks
|
|
55
|
+
```
|
|
56
|
+
4. Run the app:
|
|
57
|
+
```bash
|
|
58
|
+
ggltasks
|
|
59
|
+
```
|
|
60
|
+
*Note: The first time you run it, a browser window will pop up asking you to log in to your Google Account. A local token is saved so you only have to do this once.*
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
| Key | Action |
|
|
65
|
+
| :------------- | :------------------------------------------ |
|
|
66
|
+
| `q` | Quit |
|
|
67
|
+
| `w` | Force Sync |
|
|
68
|
+
| `↑` / `k` | Navigate Up |
|
|
69
|
+
| `↓` / `j` | Navigate Down |
|
|
70
|
+
| `←` / `h` | Back / Switch Panels |
|
|
71
|
+
| `→` / `l` | Enter Subtasks |
|
|
72
|
+
| `Shift + K` | Move Task Up |
|
|
73
|
+
| `Shift + J` | Move Task Down |
|
|
74
|
+
| `o` | Add new task/list |
|
|
75
|
+
| `d` | Delete item |
|
|
76
|
+
| `r` | Rename item |
|
|
77
|
+
| `c` | Toggle task completion |
|
|
78
|
+
| `Shift + X` | Clear Completed Tasks |
|
|
79
|
+
| `a` | Set Due Date |
|
|
80
|
+
| `i` | Edit Notes |
|
|
81
|
+
| `p` | Paste deleted task |
|
|
82
|
+
| `?` | Toggle Help Menu |
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
### Task Status Symbols
|
|
86
|
+
|
|
87
|
+
* `[ ]` : Pending
|
|
88
|
+
* `[X]` : Completed
|
|
89
|
+
* `*` : Has text Note
|
|
90
|
+
* `(N)` : Number of direct subtasks
|
|
91
|
+
* `[Upd: MM-DD HH:MM]` : Local time the task was last updated
|
|
92
|
+
|
|
93
|
+
## Known Limitations (Google Tasks API)
|
|
94
|
+
|
|
95
|
+
These are inherit limitations of the official Google Tasks API backend, not this app:
|
|
96
|
+
|
|
97
|
+
* **No Times on Due Dates**: The API strips specific hours/minutes off due dates, forcing them to midnight UTC. You can assign a day, but not a time.
|
|
98
|
+
* **No Recurring Tasks**: The API does not allow 3rd party apps to create or manage recurring/repeating tasks.
|
|
99
|
+
* **Subtask Display Limits**: The API allows infinitely deep subtasks (and this CLI app fully supports them). However, Google's official web/mobile apps will only display 1 level deep.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
MIT License.
|
ggltasks-0.1.7/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ggltasks
|
|
2
|
+
|
|
3
|
+
A lightning-fast, robust TUI for Google Tasks, built for terminal power users.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
* **Async UI**: Network requests are threaded. The app boots instantly and never freezes.
|
|
8
|
+
* **Native Sync**: Moves, completions, and deletions flawlessly sync with the official Google backend.
|
|
9
|
+
* **Smart Timestamps**: Due dates and "Last Updated" timestamps.
|
|
10
|
+
* **Vim Navigation**: Navigate entirely using `h/j/k/l`.
|
|
11
|
+
|
|
12
|
+
## Screenshots
|
|
13
|
+
|
|
14
|
+
<img width="1365" height="742" alt="image" src="https://github.com/user-attachments/assets/4c51a8ba-eac3-4a02-ab62-060d91150941" />
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Google requires you to generate a personal "Client Secret" to access your data securely.
|
|
19
|
+
|
|
20
|
+
### Step 1: Get Google API Credentials
|
|
21
|
+
1. Go to the [Google Cloud Console](https://console.cloud.google.com/).
|
|
22
|
+
2. Create a **New Project** (e.g., `ggltasks-cli`).
|
|
23
|
+
3. Search for **Google Tasks API** and click **Enable**.
|
|
24
|
+
4. Go to **APIs & Services → Credentials**.
|
|
25
|
+
5. Click **+ CREATE CREDENTIALS** → **OAuth client ID**. *(Choose "Desktop app" as the Application type).*
|
|
26
|
+
6. Download the JSON file and rename it exactly to `client_secrets.json`.
|
|
27
|
+
|
|
28
|
+
### Step 2: Install the App
|
|
29
|
+
1. Create a hidden config folder:
|
|
30
|
+
```bash
|
|
31
|
+
mkdir ~/.ggltasks
|
|
32
|
+
```
|
|
33
|
+
2. Move your `client_secrets.json` file into `~/.ggltasks`.
|
|
34
|
+
3. Install via pip:
|
|
35
|
+
```bash
|
|
36
|
+
pip install ggltasks
|
|
37
|
+
```
|
|
38
|
+
4. Run the app:
|
|
39
|
+
```bash
|
|
40
|
+
ggltasks
|
|
41
|
+
```
|
|
42
|
+
*Note: The first time you run it, a browser window will pop up asking you to log in to your Google Account. A local token is saved so you only have to do this once.*
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
| Key | Action |
|
|
47
|
+
| :------------- | :------------------------------------------ |
|
|
48
|
+
| `q` | Quit |
|
|
49
|
+
| `w` | Force Sync |
|
|
50
|
+
| `↑` / `k` | Navigate Up |
|
|
51
|
+
| `↓` / `j` | Navigate Down |
|
|
52
|
+
| `←` / `h` | Back / Switch Panels |
|
|
53
|
+
| `→` / `l` | Enter Subtasks |
|
|
54
|
+
| `Shift + K` | Move Task Up |
|
|
55
|
+
| `Shift + J` | Move Task Down |
|
|
56
|
+
| `o` | Add new task/list |
|
|
57
|
+
| `d` | Delete item |
|
|
58
|
+
| `r` | Rename item |
|
|
59
|
+
| `c` | Toggle task completion |
|
|
60
|
+
| `Shift + X` | Clear Completed Tasks |
|
|
61
|
+
| `a` | Set Due Date |
|
|
62
|
+
| `i` | Edit Notes |
|
|
63
|
+
| `p` | Paste deleted task |
|
|
64
|
+
| `?` | Toggle Help Menu |
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Task Status Symbols
|
|
68
|
+
|
|
69
|
+
* `[ ]` : Pending
|
|
70
|
+
* `[X]` : Completed
|
|
71
|
+
* `*` : Has text Note
|
|
72
|
+
* `(N)` : Number of direct subtasks
|
|
73
|
+
* `[Upd: MM-DD HH:MM]` : Local time the task was last updated
|
|
74
|
+
|
|
75
|
+
## Known Limitations (Google Tasks API)
|
|
76
|
+
|
|
77
|
+
These are inherit limitations of the official Google Tasks API backend, not this app:
|
|
78
|
+
|
|
79
|
+
* **No Times on Due Dates**: The API strips specific hours/minutes off due dates, forcing them to midnight UTC. You can assign a day, but not a time.
|
|
80
|
+
* **No Recurring Tasks**: The API does not allow 3rd party apps to create or manage recurring/repeating tasks.
|
|
81
|
+
* **Subtask Display Limits**: The API allows infinitely deep subtasks (and this CLI app fully supports them). However, Google's official web/mobile apps will only display 1 level deep.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
MIT License.
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import google.oauth2.credentials
|
|
3
|
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
4
|
+
from google.auth.transport.requests import Request
|
|
5
|
+
|
|
6
|
+
SCOPES = ['https://www.googleapis.com/auth/tasks']
|
|
7
|
+
HOME_DIR = os.path.expanduser('~')
|
|
8
|
+
GGLTASKS_DIR = os.path.join(HOME_DIR, '.ggltasks')
|
|
9
|
+
TOKEN_PATH = os.path.join(GGLTASKS_DIR, 'token.json')
|
|
10
|
+
CLIENT_SECRETS_PATH = os.path.join(GGLTASKS_DIR, 'client_secrets.json')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ensure_dir_exists():
|
|
14
|
+
os.makedirs(GGLTASKS_DIR, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_credentials():
|
|
18
|
+
_ensure_dir_exists()
|
|
19
|
+
|
|
20
|
+
if not os.path.exists(CLIENT_SECRETS_PATH):
|
|
21
|
+
raise FileNotFoundError(
|
|
22
|
+
f"\n\n Google API credentials not found!\n"
|
|
23
|
+
f" Expected location: {CLIENT_SECRETS_PATH}\n\n"
|
|
24
|
+
f" Steps to fix:\n"
|
|
25
|
+
f" 1. Go to https://console.cloud.google.com/\n"
|
|
26
|
+
f" 2. APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID\n"
|
|
27
|
+
f" 3. Application type: Desktop App\n"
|
|
28
|
+
f" 4. Download the JSON file, rename it to 'client_secrets.json'\n"
|
|
29
|
+
f" 5. Move it to: {CLIENT_SECRETS_PATH}\n"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
creds = None
|
|
33
|
+
if os.path.exists(TOKEN_PATH):
|
|
34
|
+
try:
|
|
35
|
+
creds = google.oauth2.credentials.Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error loading token.json: {e}. Initiating full re-authentication.")
|
|
38
|
+
creds = None
|
|
39
|
+
|
|
40
|
+
if not creds or not creds.valid:
|
|
41
|
+
if creds and creds.expired and creds.refresh_token:
|
|
42
|
+
try:
|
|
43
|
+
creds.refresh(Request())
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"Token refresh failed: {e}. Initiating full re-authentication.")
|
|
46
|
+
creds = None
|
|
47
|
+
if not creds:
|
|
48
|
+
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_PATH, SCOPES)
|
|
49
|
+
creds = flow.run_local_server(port=0)
|
|
50
|
+
with open(TOKEN_PATH, 'w') as token:
|
|
51
|
+
token.write(creds.to_json())
|
|
52
|
+
|
|
53
|
+
return creds
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
HOME_DIR = os.path.expanduser('~')
|
|
5
|
+
GGLTASKS_DIR = os.path.join(HOME_DIR, '.ggltasks')
|
|
6
|
+
STORAGE_FILE = os.path.join(GGLTASKS_DIR, 'local_tasks.json')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _ensure_dir_exists():
|
|
10
|
+
os.makedirs(GGLTASKS_DIR, exist_ok=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_data():
|
|
14
|
+
_ensure_dir_exists()
|
|
15
|
+
if not os.path.exists(STORAGE_FILE):
|
|
16
|
+
return {'task_lists': [], 'tasks': {}}
|
|
17
|
+
try:
|
|
18
|
+
with open(STORAGE_FILE, 'r') as f:
|
|
19
|
+
return json.load(f)
|
|
20
|
+
except (json.JSONDecodeError, IOError):
|
|
21
|
+
return {'task_lists': [], 'tasks': {}}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_data(data):
|
|
25
|
+
_ensure_dir_exists()
|
|
26
|
+
tmp_path = STORAGE_FILE + '.tmp'
|
|
27
|
+
try:
|
|
28
|
+
with open(tmp_path, 'w') as f:
|
|
29
|
+
json.dump(data, f, indent=4)
|
|
30
|
+
os.replace(tmp_path, STORAGE_FILE)
|
|
31
|
+
return True
|
|
32
|
+
except IOError:
|
|
33
|
+
try:
|
|
34
|
+
os.remove(tmp_path)
|
|
35
|
+
except OSError:
|
|
36
|
+
pass
|
|
37
|
+
return False
|