alarmy-cli 1.0.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.
- alarmy_cli-1.0.1/PKG-INFO +151 -0
- alarmy_cli-1.0.1/README.md +138 -0
- alarmy_cli-1.0.1/alarm_clock/__init__.py +4 -0
- alarmy_cli-1.0.1/alarm_clock/audio.py +66 -0
- alarmy_cli-1.0.1/alarm_clock/cli.py +489 -0
- alarmy_cli-1.0.1/alarm_clock/logger.py +32 -0
- alarmy_cli-1.0.1/alarm_clock/models.py +164 -0
- alarmy_cli-1.0.1/alarm_clock/os_scheduler.py +186 -0
- alarmy_cli-1.0.1/alarm_clock/scheduler.py +305 -0
- alarmy_cli-1.0.1/alarm_clock/ui.py +161 -0
- alarmy_cli-1.0.1/alarmy_cli.egg-info/PKG-INFO +151 -0
- alarmy_cli-1.0.1/alarmy_cli.egg-info/SOURCES.txt +17 -0
- alarmy_cli-1.0.1/alarmy_cli.egg-info/dependency_links.txt +1 -0
- alarmy_cli-1.0.1/alarmy_cli.egg-info/entry_points.txt +2 -0
- alarmy_cli-1.0.1/alarmy_cli.egg-info/top_level.txt +2 -0
- alarmy_cli-1.0.1/setup.cfg +4 -0
- alarmy_cli-1.0.1/setup.py +22 -0
- alarmy_cli-1.0.1/tests/__init__.py +3 -0
- alarmy_cli-1.0.1/tests/test_scheduler.py +288 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alarmy-cli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A thread-safe, persistent, dual-mode CLI Alarm Clock
|
|
5
|
+
Author: Ramanshu Gawande
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: description
|
|
10
|
+
Dynamic: description-content-type
|
|
11
|
+
Dynamic: requires-python
|
|
12
|
+
Dynamic: summary
|
|
13
|
+
|
|
14
|
+
# â° Command-Line Interface (CLI) Alarm Clock
|
|
15
|
+
|
|
16
|
+
A professional, robust, and zero-dependency Command-Line Interface (CLI) Alarm Clock written in Python. It runs natively and works seamlessly on both **Windows** and **Linux**.
|
|
17
|
+
|
|
18
|
+
Designed with **Clean Architecture** and **Unix-style CLI daemon patterns**, the application supports:
|
|
19
|
+
1. **Interactive Shell Mode**: A fully interactive console screen with a live ticking clock header and dynamic command prompt.
|
|
20
|
+
2. **Daemon Mode**: Run the background sound and time monitor (`alarm-clock daemon`).
|
|
21
|
+
3. **OS-Native Task Mode (Zero Manual Daemon)**: Alarms automatically register with the operating system scheduler (Windows Task Scheduler via `schtasks` or Linux via `crontab`). When the time is reached, the OS automatically pops up a terminal instance to play sound and accept user inputs.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ⨠Features
|
|
26
|
+
|
|
27
|
+
- **Global CLI command integration**: Can be installed and executed globally as `alarm-clock` in the system shell.
|
|
28
|
+
- **Dual Sound Alert Modes**:
|
|
29
|
+
- Background Python daemon checks system time and plays sound.
|
|
30
|
+
- OS-native schedulers trigger short-lived processes to ring.
|
|
31
|
+
- **OS-Native Task Automation**:
|
|
32
|
+
- Windows: Creates user-level tasks using `schtasks` with automatic date fallback mechanisms for locale compatibility (`dd/mm/yyyy` vs `mm/dd/yyyy`).
|
|
33
|
+
- Linux: Appends entries programmatically to the user's `crontab`.
|
|
34
|
+
- **Atomic File Writing**: Prevents state corruption by writing changes to a temporary file before atomically swapping it with the target database file (`~/.cli_alarms.json`).
|
|
35
|
+
- **Thread-Safe Memory Lock**: Synchronizes all database reads and writes under a shared mutex lock to isolate background checks from user adjustments.
|
|
36
|
+
- **Automatic Encoding Fallback**: Prevents crashes on legacy Windows cmd consoles that default to non-Unicode codepages (e.g., CP1252) by automatically replacing emojis with safe fallback indicators.
|
|
37
|
+
- **Cross-Platform Audio alerts**:
|
|
38
|
+
- Windows: Uses native `winsound.Beep`.
|
|
39
|
+
- Linux/macOS: Uses terminal buzzer beeps (`\a`).
|
|
40
|
+
- **Flexible Alarm Operations**: `add`, `list`, `remove`, `snooze`, and `dismiss`.
|
|
41
|
+
- Supports setting an alarm-specific default snooze limit (`--snooze-minutes`) which is automatically respected if no snooze duration is entered during rings.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## đ ī¸ Architecture & Design Decisions
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
âââââââââââââââââââââââââ
|
|
49
|
+
â Local CLI Shell â
|
|
50
|
+
â (Direct Single-Cmds) â
|
|
51
|
+
âââââââââââââŦââââââââââââ
|
|
52
|
+
â Writes/Reads
|
|
53
|
+
âŧ
|
|
54
|
+
âââââââââââââââââââââââââ
|
|
55
|
+
â ~/.cli_alarms.json âââââ (Atomic State DB)
|
|
56
|
+
âââââââââââââââââââââââââ
|
|
57
|
+
â˛
|
|
58
|
+
â Reads/Updates State
|
|
59
|
+
âŧ
|
|
60
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
61
|
+
â Background Daemon Mode â
|
|
62
|
+
â â
|
|
63
|
+
â âââââââââââââââââââââââââ âââââââââââââââââââââââ â
|
|
64
|
+
â â Scheduler Thread ââââââââēâ Sound Loop Thread â â
|
|
65
|
+
â â (Monitors System Time)â â (Non-blocking Beep) â â
|
|
66
|
+
â âââââââââââââââââââââââââ âââââââââââââââââââââââ â
|
|
67
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
68
|
+
â˛
|
|
69
|
+
â Triggers Subprocess Ring Command
|
|
70
|
+
âŧ
|
|
71
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
72
|
+
â OS-Scheduled Task Mode â
|
|
73
|
+
â â
|
|
74
|
+
â ââââââââââââââââââââââââââ ââââââââââââââââââââââ â
|
|
75
|
+
â â Windows Task Scheduler ââââââââēâ alarm-clock ring â â
|
|
76
|
+
â â / Linux Cron â â (Terminal Pop-Up) â â
|
|
77
|
+
â ââââââââââââââââââââââââââ ââââââââââââââââââââââ â
|
|
78
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## đ Getting Started
|
|
84
|
+
|
|
85
|
+
### 1. Installation
|
|
86
|
+
To register the `alarm-clock` terminal command, install the package locally from the root workspace directory.
|
|
87
|
+
|
|
88
|
+
**On Windows (without requiring admin privileges):**
|
|
89
|
+
```powershell
|
|
90
|
+
pip install --user -e .
|
|
91
|
+
```
|
|
92
|
+
*Note: Make sure your user script folder (e.g. `C:\Users\<username>\AppData\Roaming\Python\Python312\Scripts`) is in your system's PATH. If it's not, you can run the executable directly by targeting its path, or use `python -m alarm_clock.cli`.*
|
|
93
|
+
|
|
94
|
+
**On Linux / macOS:**
|
|
95
|
+
```bash
|
|
96
|
+
pip install -e .
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Running Unit Tests
|
|
100
|
+
A comprehensive test suite is included in `/tests` covering the parser, models, scheduler, serialization, persistence, and OS scheduler triggers. Run it with:
|
|
101
|
+
```bash
|
|
102
|
+
python -m unittest discover -s tests
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## đšī¸ Command Reference
|
|
108
|
+
|
|
109
|
+
### Option A: OS-Native Mode (Recommended - Zero Manual Daemon)
|
|
110
|
+
When you add an alarm, it is registered automatically with the operating system.
|
|
111
|
+
|
|
112
|
+
1. **Add an alarm**:
|
|
113
|
+
```bash
|
|
114
|
+
alarm-clock add 07:30 "Wake Up" --snooze-minutes 8 --auto-dismiss 30
|
|
115
|
+
```
|
|
116
|
+
2. **List alarms**:
|
|
117
|
+
```bash
|
|
118
|
+
alarm-clock list
|
|
119
|
+
```
|
|
120
|
+
3. When the time is reached, the operating system launches a terminal window automatically, starts beep-beeping, and prompts you:
|
|
121
|
+
```
|
|
122
|
+
Press Enter to dismiss, or type 'snooze' to snooze:
|
|
123
|
+
```
|
|
124
|
+
*If you type `snooze`, it automatically snoozes for 8 minutes (respecting the custom snooze parameter).*
|
|
125
|
+
4. **Remove an alarm** (cleans it up from both disk and OS task registries):
|
|
126
|
+
```bash
|
|
127
|
+
alarm-clock remove 1
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Option B: Daemon Mode (Manual Persistent Process)
|
|
131
|
+
1. **Start the monitor daemon** (run this in a separate terminal window or pane to play sound when alarms go off):
|
|
132
|
+
```bash
|
|
133
|
+
alarm-clock daemon
|
|
134
|
+
```
|
|
135
|
+
2. **Snooze/Dismiss** from your main terminal:
|
|
136
|
+
```bash
|
|
137
|
+
alarm-clock snooze 1 10
|
|
138
|
+
alarm-clock dismiss 1
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Option C: Interactive Shell Mode
|
|
142
|
+
If you run `alarm-clock` without any arguments, it launches a persistent, interactive console session. It manages its own background timing thread and audio loops automatically in a single terminal.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
alarm-clock
|
|
146
|
+
```
|
|
147
|
+
Inside the interactive session, the prompt updates live and you can type sub-commands like `add`, `list`, `snooze`, `dismiss`, and `exit` directly:
|
|
148
|
+
```
|
|
149
|
+
(22:15:30) alarm-clock > add 07:30 Morning Workout
|
|
150
|
+
Success: Created Alarm 1 for 07:30 ('Morning Workout')
|
|
151
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# â° Command-Line Interface (CLI) Alarm Clock
|
|
2
|
+
|
|
3
|
+
A professional, robust, and zero-dependency Command-Line Interface (CLI) Alarm Clock written in Python. It runs natively and works seamlessly on both **Windows** and **Linux**.
|
|
4
|
+
|
|
5
|
+
Designed with **Clean Architecture** and **Unix-style CLI daemon patterns**, the application supports:
|
|
6
|
+
1. **Interactive Shell Mode**: A fully interactive console screen with a live ticking clock header and dynamic command prompt.
|
|
7
|
+
2. **Daemon Mode**: Run the background sound and time monitor (`alarm-clock daemon`).
|
|
8
|
+
3. **OS-Native Task Mode (Zero Manual Daemon)**: Alarms automatically register with the operating system scheduler (Windows Task Scheduler via `schtasks` or Linux via `crontab`). When the time is reached, the OS automatically pops up a terminal instance to play sound and accept user inputs.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⨠Features
|
|
13
|
+
|
|
14
|
+
- **Global CLI command integration**: Can be installed and executed globally as `alarm-clock` in the system shell.
|
|
15
|
+
- **Dual Sound Alert Modes**:
|
|
16
|
+
- Background Python daemon checks system time and plays sound.
|
|
17
|
+
- OS-native schedulers trigger short-lived processes to ring.
|
|
18
|
+
- **OS-Native Task Automation**:
|
|
19
|
+
- Windows: Creates user-level tasks using `schtasks` with automatic date fallback mechanisms for locale compatibility (`dd/mm/yyyy` vs `mm/dd/yyyy`).
|
|
20
|
+
- Linux: Appends entries programmatically to the user's `crontab`.
|
|
21
|
+
- **Atomic File Writing**: Prevents state corruption by writing changes to a temporary file before atomically swapping it with the target database file (`~/.cli_alarms.json`).
|
|
22
|
+
- **Thread-Safe Memory Lock**: Synchronizes all database reads and writes under a shared mutex lock to isolate background checks from user adjustments.
|
|
23
|
+
- **Automatic Encoding Fallback**: Prevents crashes on legacy Windows cmd consoles that default to non-Unicode codepages (e.g., CP1252) by automatically replacing emojis with safe fallback indicators.
|
|
24
|
+
- **Cross-Platform Audio alerts**:
|
|
25
|
+
- Windows: Uses native `winsound.Beep`.
|
|
26
|
+
- Linux/macOS: Uses terminal buzzer beeps (`\a`).
|
|
27
|
+
- **Flexible Alarm Operations**: `add`, `list`, `remove`, `snooze`, and `dismiss`.
|
|
28
|
+
- Supports setting an alarm-specific default snooze limit (`--snooze-minutes`) which is automatically respected if no snooze duration is entered during rings.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## đ ī¸ Architecture & Design Decisions
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
âââââââââââââââââââââââââ
|
|
36
|
+
â Local CLI Shell â
|
|
37
|
+
â (Direct Single-Cmds) â
|
|
38
|
+
âââââââââââââŦââââââââââââ
|
|
39
|
+
â Writes/Reads
|
|
40
|
+
âŧ
|
|
41
|
+
âââââââââââââââââââââââââ
|
|
42
|
+
â ~/.cli_alarms.json âââââ (Atomic State DB)
|
|
43
|
+
âââââââââââââââââââââââââ
|
|
44
|
+
â˛
|
|
45
|
+
â Reads/Updates State
|
|
46
|
+
âŧ
|
|
47
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
48
|
+
â Background Daemon Mode â
|
|
49
|
+
â â
|
|
50
|
+
â âââââââââââââââââââââââââ âââââââââââââââââââââââ â
|
|
51
|
+
â â Scheduler Thread ââââââââēâ Sound Loop Thread â â
|
|
52
|
+
â â (Monitors System Time)â â (Non-blocking Beep) â â
|
|
53
|
+
â âââââââââââââââââââââââââ âââââââââââââââââââââââ â
|
|
54
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
55
|
+
â˛
|
|
56
|
+
â Triggers Subprocess Ring Command
|
|
57
|
+
âŧ
|
|
58
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
59
|
+
â OS-Scheduled Task Mode â
|
|
60
|
+
â â
|
|
61
|
+
â ââââââââââââââââââââââââââ ââââââââââââââââââââââ â
|
|
62
|
+
â â Windows Task Scheduler ââââââââēâ alarm-clock ring â â
|
|
63
|
+
â â / Linux Cron â â (Terminal Pop-Up) â â
|
|
64
|
+
â ââââââââââââââââââââââââââ ââââââââââââââââââââââ â
|
|
65
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## đ Getting Started
|
|
71
|
+
|
|
72
|
+
### 1. Installation
|
|
73
|
+
To register the `alarm-clock` terminal command, install the package locally from the root workspace directory.
|
|
74
|
+
|
|
75
|
+
**On Windows (without requiring admin privileges):**
|
|
76
|
+
```powershell
|
|
77
|
+
pip install --user -e .
|
|
78
|
+
```
|
|
79
|
+
*Note: Make sure your user script folder (e.g. `C:\Users\<username>\AppData\Roaming\Python\Python312\Scripts`) is in your system's PATH. If it's not, you can run the executable directly by targeting its path, or use `python -m alarm_clock.cli`.*
|
|
80
|
+
|
|
81
|
+
**On Linux / macOS:**
|
|
82
|
+
```bash
|
|
83
|
+
pip install -e .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Running Unit Tests
|
|
87
|
+
A comprehensive test suite is included in `/tests` covering the parser, models, scheduler, serialization, persistence, and OS scheduler triggers. Run it with:
|
|
88
|
+
```bash
|
|
89
|
+
python -m unittest discover -s tests
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## đšī¸ Command Reference
|
|
95
|
+
|
|
96
|
+
### Option A: OS-Native Mode (Recommended - Zero Manual Daemon)
|
|
97
|
+
When you add an alarm, it is registered automatically with the operating system.
|
|
98
|
+
|
|
99
|
+
1. **Add an alarm**:
|
|
100
|
+
```bash
|
|
101
|
+
alarm-clock add 07:30 "Wake Up" --snooze-minutes 8 --auto-dismiss 30
|
|
102
|
+
```
|
|
103
|
+
2. **List alarms**:
|
|
104
|
+
```bash
|
|
105
|
+
alarm-clock list
|
|
106
|
+
```
|
|
107
|
+
3. When the time is reached, the operating system launches a terminal window automatically, starts beep-beeping, and prompts you:
|
|
108
|
+
```
|
|
109
|
+
Press Enter to dismiss, or type 'snooze' to snooze:
|
|
110
|
+
```
|
|
111
|
+
*If you type `snooze`, it automatically snoozes for 8 minutes (respecting the custom snooze parameter).*
|
|
112
|
+
4. **Remove an alarm** (cleans it up from both disk and OS task registries):
|
|
113
|
+
```bash
|
|
114
|
+
alarm-clock remove 1
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Option B: Daemon Mode (Manual Persistent Process)
|
|
118
|
+
1. **Start the monitor daemon** (run this in a separate terminal window or pane to play sound when alarms go off):
|
|
119
|
+
```bash
|
|
120
|
+
alarm-clock daemon
|
|
121
|
+
```
|
|
122
|
+
2. **Snooze/Dismiss** from your main terminal:
|
|
123
|
+
```bash
|
|
124
|
+
alarm-clock snooze 1 10
|
|
125
|
+
alarm-clock dismiss 1
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Option C: Interactive Shell Mode
|
|
129
|
+
If you run `alarm-clock` without any arguments, it launches a persistent, interactive console session. It manages its own background timing thread and audio loops automatically in a single terminal.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
alarm-clock
|
|
133
|
+
```
|
|
134
|
+
Inside the interactive session, the prompt updates live and you can type sub-commands like `add`, `list`, `snooze`, `dismiss`, and `exit` directly:
|
|
135
|
+
```
|
|
136
|
+
(22:15:30) alarm-clock > add 07:30 Morning Workout
|
|
137
|
+
Success: Created Alarm 1 for 07:30 ('Morning Workout')
|
|
138
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
import platform
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class AlarmSoundController:
|
|
10
|
+
"""
|
|
11
|
+
Manages non-blocking audio alerts across platforms.
|
|
12
|
+
On Windows, it uses the built-in winsound.Beep API.
|
|
13
|
+
On Linux, it uses the terminal bell character (\a) with a quiet sleep.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._stop_event = threading.Event()
|
|
17
|
+
self._thread: Optional[threading.Thread] = None
|
|
18
|
+
self._lock = threading.Lock()
|
|
19
|
+
|
|
20
|
+
def _beep_loop(self) -> None:
|
|
21
|
+
system = platform.system()
|
|
22
|
+
while not self._stop_event.is_set():
|
|
23
|
+
if system == "Windows":
|
|
24
|
+
try:
|
|
25
|
+
import winsound
|
|
26
|
+
# 1000 Hz frequency, 600 ms duration
|
|
27
|
+
winsound.Beep(1000, 600)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
# Fallback to ASCII bell if winsound fails
|
|
30
|
+
sys.stdout.write('\a')
|
|
31
|
+
sys.stdout.flush()
|
|
32
|
+
else:
|
|
33
|
+
# Linux/macOS fallback using ASCII bell character
|
|
34
|
+
sys.stdout.write('\a')
|
|
35
|
+
sys.stdout.flush()
|
|
36
|
+
|
|
37
|
+
# Sleep in small increments to respond quickly to stop events
|
|
38
|
+
for _ in range(10):
|
|
39
|
+
if self._stop_event.is_set():
|
|
40
|
+
break
|
|
41
|
+
time.sleep(0.1)
|
|
42
|
+
|
|
43
|
+
def start(self) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Starts the alarm beep loop in a background thread if not already running.
|
|
46
|
+
Returns True if a new thread was started, False otherwise.
|
|
47
|
+
"""
|
|
48
|
+
with self._lock:
|
|
49
|
+
if self._thread and self._thread.is_alive():
|
|
50
|
+
return False # Already running
|
|
51
|
+
|
|
52
|
+
self._stop_event.clear()
|
|
53
|
+
self._thread = threading.Thread(target=self._beep_loop, name="AlarmSoundThread", daemon=True)
|
|
54
|
+
self._thread.start()
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def stop(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Stops the alarm beep loop.
|
|
60
|
+
"""
|
|
61
|
+
with self._lock:
|
|
62
|
+
if self._thread and self._thread.is_alive():
|
|
63
|
+
self._stop_event.set()
|
|
64
|
+
# Wait briefly for the thread to stop, but don't block indefinitely
|
|
65
|
+
self._thread.join(timeout=1.5)
|
|
66
|
+
self._thread = None
|