dori 0.1.0__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 (60) hide show
  1. dori-0.1.0/LICENSE +21 -0
  2. dori-0.1.0/MANIFEST.in +4 -0
  3. dori-0.1.0/PKG-INFO +248 -0
  4. dori-0.1.0/README.md +213 -0
  5. dori-0.1.0/dori/__init__.py +1 -0
  6. dori-0.1.0/dori/boilerplate/DORI.md +11 -0
  7. dori-0.1.0/dori/boilerplate/assets/dori.png +0 -0
  8. dori-0.1.0/dori/boilerplate/presets/reminders/dbus.md +21 -0
  9. dori-0.1.0/dori/boilerplate/presets/reminders/dbus.py +121 -0
  10. dori-0.1.0/dori/boilerplate/presets/reminders/template.md +16 -0
  11. dori-0.1.0/dori/boilerplate/presets/reminders/template.py +25 -0
  12. dori-0.1.0/dori/boilerplate/presets/search/ddgs.md +22 -0
  13. dori-0.1.0/dori/boilerplate/presets/search/ddgs.py +168 -0
  14. dori-0.1.0/dori/boilerplate/presets/search/tavily.md +22 -0
  15. dori-0.1.0/dori/boilerplate/presets/search/tavily.py +121 -0
  16. dori-0.1.0/dori/boilerplate/scripts/_commit_workflow.py +831 -0
  17. dori-0.1.0/dori/boilerplate/scripts/_web_common.py +69 -0
  18. dori-0.1.0/dori/boilerplate/scripts/analyze-folder.py +251 -0
  19. dori-0.1.0/dori/boilerplate/scripts/calendar.py +32 -0
  20. dori-0.1.0/dori/boilerplate/scripts/commit.py +22 -0
  21. dori-0.1.0/dori/boilerplate/scripts/docker.py +109 -0
  22. dori-0.1.0/dori/boilerplate/scripts/git.py +267 -0
  23. dori-0.1.0/dori/boilerplate/skills/analyze-folder.md +25 -0
  24. dori-0.1.0/dori/boilerplate/skills/calendar.md +18 -0
  25. dori-0.1.0/dori/boilerplate/skills/devtools/_index.md +5 -0
  26. dori-0.1.0/dori/boilerplate/skills/devtools/commit.md +20 -0
  27. dori-0.1.0/dori/boilerplate/skills/devtools/docker.md +18 -0
  28. dori-0.1.0/dori/boilerplate/skills/devtools/git.md +32 -0
  29. dori-0.1.0/dori/chat.py +561 -0
  30. dori-0.1.0/dori/commands.py +620 -0
  31. dori-0.1.0/dori/loader.py +129 -0
  32. dori-0.1.0/dori/main.py +160 -0
  33. dori-0.1.0/dori/models.py +29 -0
  34. dori-0.1.0/dori/schemas.py +112 -0
  35. dori-0.1.0/dori/script.py +222 -0
  36. dori-0.1.0/dori/tui.py +649 -0
  37. dori-0.1.0/dori/tui.tcss +82 -0
  38. dori-0.1.0/dori/workflow.py +606 -0
  39. dori-0.1.0/dori.egg-info/PKG-INFO +248 -0
  40. dori-0.1.0/dori.egg-info/SOURCES.txt +58 -0
  41. dori-0.1.0/dori.egg-info/dependency_links.txt +1 -0
  42. dori-0.1.0/dori.egg-info/entry_points.txt +2 -0
  43. dori-0.1.0/dori.egg-info/requires.txt +11 -0
  44. dori-0.1.0/dori.egg-info/top_level.txt +1 -0
  45. dori-0.1.0/pyproject.toml +94 -0
  46. dori-0.1.0/setup.cfg +4 -0
  47. dori-0.1.0/tests/test_boilerplate.py +398 -0
  48. dori-0.1.0/tests/test_chat.py +611 -0
  49. dori-0.1.0/tests/test_cli_dispatch.py +121 -0
  50. dori-0.1.0/tests/test_commit_workflow.py +948 -0
  51. dori-0.1.0/tests/test_git_expert_script.py +286 -0
  52. dori-0.1.0/tests/test_init_presets.py +447 -0
  53. dori-0.1.0/tests/test_loader.py +10 -0
  54. dori-0.1.0/tests/test_ollama_integration.py +112 -0
  55. dori-0.1.0/tests/test_script_api.py +418 -0
  56. dori-0.1.0/tests/test_tui.py +756 -0
  57. dori-0.1.0/tests/test_web_common.py +91 -0
  58. dori-0.1.0/tests/test_web_ddgs.py +267 -0
  59. dori-0.1.0/tests/test_web_tavily.py +175 -0
  60. dori-0.1.0/tests/test_workflow.py +711 -0
dori-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mauricio
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.
dori-0.1.0/MANIFEST.in ADDED
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ include dori/tui.tcss
4
+ recursive-include dori/boilerplate *
dori-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: dori
3
+ Version: 0.1.0
4
+ Summary: Dori terminal assistant
5
+ Author: Mauricio
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mtdb/dori
8
+ Project-URL: Repository, https://github.com/mtdb/dori
9
+ Project-URL: Issues, https://github.com/mtdb/dori/issues
10
+ Keywords: cli,local-first,ollama,terminal,assistant
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: User Interfaces
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: <4.0,>=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: rich>=13.0.0
25
+ Requires-Dist: ollama>=0.2.0
26
+ Requires-Dist: textual>=0.52.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: ddgs<10,>=9.14.4
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: ruff; extra == "dev"
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: pre-commit; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Dori
37
+
38
+ Dori is a local-first terminal assistant designed to work well with small local models.
39
+
40
+ It is not trying to be "Claude Code, but local." The goal is narrower and more honest: help you from the terminal by routing clear requests into deterministic scripts, with behavior you can inspect, edit, and extend yourself.
41
+
42
+ ## What Dori Is
43
+
44
+ - A terminal assistant that runs locally
45
+ - A tool router that turns clear intents into structured JSON
46
+ - A script-backed system you can customize in `~/.dori`
47
+ - A better fit for 8B-class local models than open-ended autonomous agents
48
+
49
+ ## What Dori Is Not
50
+
51
+ - Not a full autonomous coding agent
52
+ - Not a replacement for larger hosted models on complex repo-wide tasks
53
+ - Not a hidden tool loop that edits files and runs commands without a clear contract
54
+ - Not magic: reliability matters more than agent theater
55
+
56
+ If you expect broad autonomous code editing, long tool loops, or strong performance on hard software tasks with a small local model, Dori will probably feel intentionally constrained.
57
+
58
+ ## Why This Project Exists
59
+
60
+ Most local assistants either:
61
+
62
+ - imitate larger coding agents and become unreliable on small models, or
63
+ - stay so simple that they are hard to extend into something genuinely useful.
64
+
65
+ Dori aims for the middle:
66
+
67
+ - local-first
68
+ - fast to start
69
+ - understandable
70
+ - extendable with plain Markdown and Python
71
+ - optimized for reliability over autonomy
72
+
73
+ The model decides when a request matches a skill and fills structured arguments. A deterministic script does the real work.
74
+
75
+ ## Good Fit
76
+
77
+ Dori is a good fit if you want:
78
+
79
+ - a personal terminal assistant powered by Ollama
80
+ - a system you can inspect and customize end to end
81
+ - practical local workflows such as web lookup, reminders, calendar actions, git help, docker help, or folder analysis
82
+ - predictable behavior from small local models
83
+
84
+ Dori is a poor fit if you want:
85
+
86
+ - a general-purpose coding agent with deep autonomy
87
+ - multi-step repo mutation driven mostly by the model
88
+ - the same capability level as Claude Code, Codex, or similar systems
89
+
90
+ ## How It Works
91
+
92
+ 1. Dori loads its persona from `~/.dori/DORI.md`.
93
+ 2. It discovers installed skills from `~/.dori/skills/`.
94
+ 3. The local model either answers normally or emits one JSON payload for a matching skill.
95
+ 4. Dori validates that payload and runs the matching script from `~/.dori/scripts/`.
96
+ 5. The script prints the result back to the user.
97
+
98
+ This separation is the core idea:
99
+
100
+ - skills teach the model when to act
101
+ - scripts perform the action deterministically
102
+
103
+ ## Current Product Shape
104
+
105
+ Today Dori includes:
106
+
107
+ - a Textual TUI
108
+ - one-off prompt mode with `dori --prompt`
109
+ - direct skill execution with `dori <skill-name>`
110
+ - local persona and skill loading from `~/.dori`
111
+ - bundled starter skills for tasks like reminders, calendar, web, git, docker, commit, and folder analysis
112
+ - update-safe boilerplate management through `dori init` and `dori update`
113
+
114
+ ## Requirements
115
+
116
+ - Python 3.11+
117
+ - Ollama running locally
118
+ - a local model available in Ollama
119
+
120
+ By default, Dori uses `llama3.1:8b`.
121
+
122
+ ## Quickstart
123
+
124
+ 1. Install:
125
+
126
+ ```bash
127
+ pip install -e .
128
+ ```
129
+
130
+ 2. Initialize runtime files:
131
+
132
+ ```bash
133
+ dori init
134
+ ```
135
+
136
+ 3. Start the assistant:
137
+
138
+ ```bash
139
+ dori
140
+ ```
141
+
142
+ 4. Or send a single prompt:
143
+
144
+ ```bash
145
+ dori --prompt "Summarize my open tasks"
146
+ ```
147
+
148
+ ## Global Install
149
+
150
+ For a global CLI install, prefer `pipx`:
151
+
152
+ ```bash
153
+ pipx install .
154
+ ```
155
+
156
+ To track local development changes:
157
+
158
+ ```bash
159
+ pipx install -e .
160
+ ```
161
+
162
+ Alternative with user-local `pip`:
163
+
164
+ ```bash
165
+ python3 -m pip install --user .
166
+ ```
167
+
168
+ ## Runtime Layout
169
+
170
+ Dori stores its runtime state in `~/.dori`:
171
+
172
+ ```text
173
+ ~/.dori/
174
+ |-- DORI.md
175
+ |-- .manifest.json
176
+ |-- .history
177
+ |-- skills/
178
+ `-- scripts/
179
+ ```
180
+
181
+ `dori init` copies the bundled boilerplate into that directory and asks you to choose:
182
+
183
+ - a reminders backend
184
+ - a search backend
185
+
186
+ `dori update` refreshes managed files that still match their last installed hash and preserves files you have edited locally.
187
+
188
+ The TUI stores the last 100 submitted messages in `.history` so you can recall them with `↑` and `↓` in new sessions.
189
+
190
+ ## Search Backends
191
+
192
+ Search defaults to DDGS for zero-key web answers.
193
+
194
+ For Tavily-backed search, export:
195
+
196
+ ```bash
197
+ export TAVILY_API_KEY="tvly-..."
198
+ ```
199
+
200
+ For DDGS-backed search, Dori uses a local Ollama model to synthesize an English answer from retrieved evidence. Override that model with:
201
+
202
+ ```bash
203
+ export DORI_WEB_MODEL="llama3.1:8b"
204
+ ```
205
+
206
+ Both search backends return a direct answer followed by `Sources:` and two or three URLs.
207
+
208
+ ## Better Prompts With Ctrl+T
209
+
210
+ Small local models often respond better to English prompts. The TUI includes a built-in translation shortcut:
211
+
212
+ - press `Ctrl+T` while writing a message
213
+ - Dori translates your draft into natural English before sending it
214
+
215
+ It tries to preserve code, commands, flags, file paths, URLs, identifiers, and quoted literals.
216
+
217
+ ## Extending Dori
218
+
219
+ The main customization story is intentionally simple:
220
+
221
+ 1. add a skill Markdown file under `~/.dori/skills/`
222
+ 2. add a same-named Python script under `~/.dori/scripts/`
223
+ 3. make the script read the JSON payload and print a clear result
224
+
225
+ This makes Dori easier to inspect and evolve than systems that hide everything behind a large prompt or a complex agent runtime.
226
+
227
+ ## Development Notes
228
+
229
+ - Public packaging and CLI surface are `dori`
230
+ - Internal modules live under the `dori` package
231
+ - Boilerplate and onboarding text should refer to Dori
232
+
233
+ ## Tests
234
+
235
+ Run the default test suite:
236
+
237
+ ```bash
238
+ poetry run pytest
239
+ ```
240
+
241
+ Integration tests that call Ollama are skipped by default. To run them, make sure Ollama is running and `llama3.1:8b` is installed:
242
+
243
+ ```bash
244
+ ollama pull llama3.1:8b
245
+ DORI_RUN_OLLAMA_INTEGRATION=1 poetry run pytest tests/test_ollama_integration.py
246
+ ```
247
+
248
+ The Ollama integration tests use `llama3.1:8b` with `seed: 42` and `temperature: 0` for predictable routing checks.
dori-0.1.0/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # Dori
2
+
3
+ Dori is a local-first terminal assistant designed to work well with small local models.
4
+
5
+ It is not trying to be "Claude Code, but local." The goal is narrower and more honest: help you from the terminal by routing clear requests into deterministic scripts, with behavior you can inspect, edit, and extend yourself.
6
+
7
+ ## What Dori Is
8
+
9
+ - A terminal assistant that runs locally
10
+ - A tool router that turns clear intents into structured JSON
11
+ - A script-backed system you can customize in `~/.dori`
12
+ - A better fit for 8B-class local models than open-ended autonomous agents
13
+
14
+ ## What Dori Is Not
15
+
16
+ - Not a full autonomous coding agent
17
+ - Not a replacement for larger hosted models on complex repo-wide tasks
18
+ - Not a hidden tool loop that edits files and runs commands without a clear contract
19
+ - Not magic: reliability matters more than agent theater
20
+
21
+ If you expect broad autonomous code editing, long tool loops, or strong performance on hard software tasks with a small local model, Dori will probably feel intentionally constrained.
22
+
23
+ ## Why This Project Exists
24
+
25
+ Most local assistants either:
26
+
27
+ - imitate larger coding agents and become unreliable on small models, or
28
+ - stay so simple that they are hard to extend into something genuinely useful.
29
+
30
+ Dori aims for the middle:
31
+
32
+ - local-first
33
+ - fast to start
34
+ - understandable
35
+ - extendable with plain Markdown and Python
36
+ - optimized for reliability over autonomy
37
+
38
+ The model decides when a request matches a skill and fills structured arguments. A deterministic script does the real work.
39
+
40
+ ## Good Fit
41
+
42
+ Dori is a good fit if you want:
43
+
44
+ - a personal terminal assistant powered by Ollama
45
+ - a system you can inspect and customize end to end
46
+ - practical local workflows such as web lookup, reminders, calendar actions, git help, docker help, or folder analysis
47
+ - predictable behavior from small local models
48
+
49
+ Dori is a poor fit if you want:
50
+
51
+ - a general-purpose coding agent with deep autonomy
52
+ - multi-step repo mutation driven mostly by the model
53
+ - the same capability level as Claude Code, Codex, or similar systems
54
+
55
+ ## How It Works
56
+
57
+ 1. Dori loads its persona from `~/.dori/DORI.md`.
58
+ 2. It discovers installed skills from `~/.dori/skills/`.
59
+ 3. The local model either answers normally or emits one JSON payload for a matching skill.
60
+ 4. Dori validates that payload and runs the matching script from `~/.dori/scripts/`.
61
+ 5. The script prints the result back to the user.
62
+
63
+ This separation is the core idea:
64
+
65
+ - skills teach the model when to act
66
+ - scripts perform the action deterministically
67
+
68
+ ## Current Product Shape
69
+
70
+ Today Dori includes:
71
+
72
+ - a Textual TUI
73
+ - one-off prompt mode with `dori --prompt`
74
+ - direct skill execution with `dori <skill-name>`
75
+ - local persona and skill loading from `~/.dori`
76
+ - bundled starter skills for tasks like reminders, calendar, web, git, docker, commit, and folder analysis
77
+ - update-safe boilerplate management through `dori init` and `dori update`
78
+
79
+ ## Requirements
80
+
81
+ - Python 3.11+
82
+ - Ollama running locally
83
+ - a local model available in Ollama
84
+
85
+ By default, Dori uses `llama3.1:8b`.
86
+
87
+ ## Quickstart
88
+
89
+ 1. Install:
90
+
91
+ ```bash
92
+ pip install -e .
93
+ ```
94
+
95
+ 2. Initialize runtime files:
96
+
97
+ ```bash
98
+ dori init
99
+ ```
100
+
101
+ 3. Start the assistant:
102
+
103
+ ```bash
104
+ dori
105
+ ```
106
+
107
+ 4. Or send a single prompt:
108
+
109
+ ```bash
110
+ dori --prompt "Summarize my open tasks"
111
+ ```
112
+
113
+ ## Global Install
114
+
115
+ For a global CLI install, prefer `pipx`:
116
+
117
+ ```bash
118
+ pipx install .
119
+ ```
120
+
121
+ To track local development changes:
122
+
123
+ ```bash
124
+ pipx install -e .
125
+ ```
126
+
127
+ Alternative with user-local `pip`:
128
+
129
+ ```bash
130
+ python3 -m pip install --user .
131
+ ```
132
+
133
+ ## Runtime Layout
134
+
135
+ Dori stores its runtime state in `~/.dori`:
136
+
137
+ ```text
138
+ ~/.dori/
139
+ |-- DORI.md
140
+ |-- .manifest.json
141
+ |-- .history
142
+ |-- skills/
143
+ `-- scripts/
144
+ ```
145
+
146
+ `dori init` copies the bundled boilerplate into that directory and asks you to choose:
147
+
148
+ - a reminders backend
149
+ - a search backend
150
+
151
+ `dori update` refreshes managed files that still match their last installed hash and preserves files you have edited locally.
152
+
153
+ The TUI stores the last 100 submitted messages in `.history` so you can recall them with `↑` and `↓` in new sessions.
154
+
155
+ ## Search Backends
156
+
157
+ Search defaults to DDGS for zero-key web answers.
158
+
159
+ For Tavily-backed search, export:
160
+
161
+ ```bash
162
+ export TAVILY_API_KEY="tvly-..."
163
+ ```
164
+
165
+ For DDGS-backed search, Dori uses a local Ollama model to synthesize an English answer from retrieved evidence. Override that model with:
166
+
167
+ ```bash
168
+ export DORI_WEB_MODEL="llama3.1:8b"
169
+ ```
170
+
171
+ Both search backends return a direct answer followed by `Sources:` and two or three URLs.
172
+
173
+ ## Better Prompts With Ctrl+T
174
+
175
+ Small local models often respond better to English prompts. The TUI includes a built-in translation shortcut:
176
+
177
+ - press `Ctrl+T` while writing a message
178
+ - Dori translates your draft into natural English before sending it
179
+
180
+ It tries to preserve code, commands, flags, file paths, URLs, identifiers, and quoted literals.
181
+
182
+ ## Extending Dori
183
+
184
+ The main customization story is intentionally simple:
185
+
186
+ 1. add a skill Markdown file under `~/.dori/skills/`
187
+ 2. add a same-named Python script under `~/.dori/scripts/`
188
+ 3. make the script read the JSON payload and print a clear result
189
+
190
+ This makes Dori easier to inspect and evolve than systems that hide everything behind a large prompt or a complex agent runtime.
191
+
192
+ ## Development Notes
193
+
194
+ - Public packaging and CLI surface are `dori`
195
+ - Internal modules live under the `dori` package
196
+ - Boilerplate and onboarding text should refer to Dori
197
+
198
+ ## Tests
199
+
200
+ Run the default test suite:
201
+
202
+ ```bash
203
+ poetry run pytest
204
+ ```
205
+
206
+ Integration tests that call Ollama are skipped by default. To run them, make sure Ollama is running and `llama3.1:8b` is installed:
207
+
208
+ ```bash
209
+ ollama pull llama3.1:8b
210
+ DORI_RUN_OLLAMA_INTEGRATION=1 poetry run pytest tests/test_ollama_integration.py
211
+ ```
212
+
213
+ The Ollama integration tests use `llama3.1:8b` with `seed: 42` and `temperature: 0` for predictable routing checks.
@@ -0,0 +1 @@
1
+ """Dori terminal assistant."""
@@ -0,0 +1,11 @@
1
+ You are Dori, a locally running personal assistant in the terminal.
2
+
3
+ - Be direct and concise.
4
+ - When a user's intent clearly matches an available skill, respond with the required JSON payload only, with no preamble, explanation, or markdown.
5
+ - Only express uncertainty when the task is genuinely ambiguous or the answer is factual and you are not confident.
6
+ - If the request is ambiguous, ask a short follow-up question instead of guessing.
7
+ - Otherwise, respond in plain text.
8
+
9
+ # Language Flexibility
10
+
11
+ - You may think and respond in English even if the user's question is in another language, especially if it helps you reason or provide a more accurate answer.
@@ -0,0 +1,21 @@
1
+ # D-Bus Reminders Preset
2
+
3
+ **Intent**: Use when the user wants a local desktop reminder through D-Bus notifications.
4
+
5
+ **Runtime guidance:**
6
+ - Requires `notify-send` for real reminder scheduling.
7
+ - Supports only relative times in seconds, minutes, or hours.
8
+ - Use `dry_run` only for tests or previews where no desktop notification should be scheduled.
9
+
10
+ **Field guidance:**
11
+ - `message`: The task only — strip all time expressions and filler words (e.g. "drink water", not "remind me to drink water")
12
+ - `when`: A supported relative time (e.g. "in 20 minutes", "in 2 hours", "in 30 seconds")
13
+ - `confidence`: Numeric confidence from 0.0 to 1.0
14
+ - `raw_text`: Copy the user's original message verbatim
15
+
16
+ **Examples:**
17
+ User: Remind me to drink water in 20 minutes
18
+ Assistant: {"skill": "reminders", "confidence": 0.95, "message": "drink water", "when": "in 20 minutes", "raw_text": "Remind me to drink water in 20 minutes"}
19
+
20
+ User: Set a timer to stretch in 2 hours
21
+ Assistant: {"skill": "reminders", "confidence": 0.94, "message": "stretch", "when": "in 2 hours", "raw_text": "Set a timer to stretch in 2 hours"}
@@ -0,0 +1,121 @@
1
+ import json
2
+ import re
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ RELATIVE_TIME_PATTERN = re.compile(
9
+ r"^\s*in\s+(\d+)\s+(seconds?|minutes?|hours?)\s*$",
10
+ re.IGNORECASE,
11
+ )
12
+
13
+
14
+ def parse_relative_seconds(when: str) -> int | None:
15
+ match = RELATIVE_TIME_PATTERN.match(when)
16
+ if not match:
17
+ return None
18
+
19
+ amount = int(match.group(1))
20
+ unit = match.group(2).lower()
21
+ if unit.startswith("second"):
22
+ return amount
23
+ if unit.startswith("minute"):
24
+ return amount * 60
25
+ if unit.startswith("hour"):
26
+ return amount * 60 * 60
27
+ return None
28
+
29
+
30
+ def _notification_icon_path() -> Path | None:
31
+ script_dir = Path(__file__).resolve().parent
32
+ candidates = [
33
+ script_dir.parent / "assets" / "dori.png",
34
+ script_dir.parent.parent / "assets" / "dori.png",
35
+ ]
36
+ for candidate in candidates:
37
+ if candidate.is_file():
38
+ return candidate
39
+ return None
40
+
41
+
42
+ def _notify_send_command(message: str) -> list[str]:
43
+ command = ["notify-send"]
44
+ icon_path = _notification_icon_path()
45
+ if icon_path is not None:
46
+ command.extend(["-i", str(icon_path)])
47
+ command.extend(["Dori reminder", message])
48
+ return command
49
+
50
+
51
+ def schedule_notification(message: str, delay_seconds: int) -> None:
52
+ icon_path = _notification_icon_path()
53
+ if icon_path is None:
54
+ code = (
55
+ "import subprocess, sys, time; "
56
+ "time.sleep(int(sys.argv[1])); "
57
+ 'subprocess.run(["notify-send", "Dori reminder", sys.argv[2]], check=False)'
58
+ )
59
+ args = [sys.executable, "-c", code, str(delay_seconds), message]
60
+ else:
61
+ code = (
62
+ "import subprocess, sys, time; "
63
+ "time.sleep(int(sys.argv[1])); "
64
+ 'subprocess.run(["notify-send", "-i", sys.argv[3], "Dori reminder", sys.argv[2]], check=False)'
65
+ )
66
+ args = [
67
+ sys.executable,
68
+ "-c",
69
+ code,
70
+ str(delay_seconds),
71
+ message,
72
+ str(icon_path),
73
+ ]
74
+ subprocess.Popen(
75
+ args,
76
+ stdout=subprocess.DEVNULL,
77
+ stderr=subprocess.DEVNULL,
78
+ start_new_session=True,
79
+ )
80
+
81
+
82
+ def main() -> None:
83
+ if len(sys.argv) < 2:
84
+ print("Error: Missing JSON payload", file=sys.stderr)
85
+ sys.exit(1)
86
+
87
+ try:
88
+ payload = json.loads(sys.argv[1])
89
+ except json.JSONDecodeError:
90
+ print(
91
+ "Error: Invalid JSON payload provided to reminders script.",
92
+ file=sys.stderr,
93
+ )
94
+ sys.exit(1)
95
+
96
+ message = payload.get("message", "unknown")
97
+ when = payload.get("when", "unknown time")
98
+ seconds = parse_relative_seconds(when)
99
+ if seconds is None:
100
+ print(
101
+ "Error: Unsupported reminder time. Use a relative time like "
102
+ "'in 20 minutes', 'in 2 hours', or 'in 30 seconds'.",
103
+ file=sys.stderr,
104
+ )
105
+ sys.exit(1)
106
+
107
+ if not payload.get("dry_run", False):
108
+ if shutil.which("notify-send") is None:
109
+ print(
110
+ "Error: D-Bus reminders require notify-send to be installed.",
111
+ file=sys.stderr,
112
+ )
113
+ sys.exit(1)
114
+
115
+ schedule_notification(str(message), seconds)
116
+
117
+ print(f"[D-Bus]: Scheduled reminder for '{message}' {when}.")
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
@@ -0,0 +1,16 @@
1
+ # Reminders Skill
2
+
3
+ **Intent**: Use when the user wants to set a reminder, alarm, or timer.
4
+
5
+ **Field guidance:**
6
+ - `message`: The task only — strip all time expressions and filler words (e.g. "buy milk", not "remind me to buy milk tomorrow")
7
+ - `when`: The time expression as spoken (e.g. "tomorrow at 9am", "in 20 minutes")
8
+ - `confidence`: Numeric confidence from 0.0 to 1.0
9
+ - `raw_text`: Copy the user's original message verbatim
10
+
11
+ **Examples:**
12
+ User: Remind me to buy milk in 20 minutes
13
+ Assistant: {"skill": "reminders", "confidence": 0.95, "message": "buy milk", "when": "in 20 minutes", "raw_text": "Remind me to buy milk in 20 minutes"}
14
+
15
+ User: Set an alarm for 7am tomorrow to wake up
16
+ Assistant: {"skill": "reminders", "confidence": 0.94, "message": "wake up", "when": "7am tomorrow", "raw_text": "Set an alarm for 7am tomorrow to wake up"}
@@ -0,0 +1,25 @@
1
+ import json
2
+ import sys
3
+
4
+
5
+ def main():
6
+ if len(sys.argv) < 2:
7
+ print("Error: Missing JSON payload", file=sys.stderr)
8
+ sys.exit(1)
9
+
10
+ try:
11
+ payload = json.loads(sys.argv[1])
12
+ message = payload.get("message", "unknown")
13
+ when = payload.get("when", "unknown time")
14
+
15
+ # Deterministic output
16
+ print(f"⏰ [System]: I have scheduled a reminder for '{message}' at '{when}'.")
17
+ except json.JSONDecodeError:
18
+ print(
19
+ "Error: Invalid JSON payload provided to reminders script.", file=sys.stderr
20
+ )
21
+ sys.exit(1)
22
+
23
+
24
+ if __name__ == "__main__":
25
+ main()