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.
- dori-0.1.0/LICENSE +21 -0
- dori-0.1.0/MANIFEST.in +4 -0
- dori-0.1.0/PKG-INFO +248 -0
- dori-0.1.0/README.md +213 -0
- dori-0.1.0/dori/__init__.py +1 -0
- dori-0.1.0/dori/boilerplate/DORI.md +11 -0
- dori-0.1.0/dori/boilerplate/assets/dori.png +0 -0
- dori-0.1.0/dori/boilerplate/presets/reminders/dbus.md +21 -0
- dori-0.1.0/dori/boilerplate/presets/reminders/dbus.py +121 -0
- dori-0.1.0/dori/boilerplate/presets/reminders/template.md +16 -0
- dori-0.1.0/dori/boilerplate/presets/reminders/template.py +25 -0
- dori-0.1.0/dori/boilerplate/presets/search/ddgs.md +22 -0
- dori-0.1.0/dori/boilerplate/presets/search/ddgs.py +168 -0
- dori-0.1.0/dori/boilerplate/presets/search/tavily.md +22 -0
- dori-0.1.0/dori/boilerplate/presets/search/tavily.py +121 -0
- dori-0.1.0/dori/boilerplate/scripts/_commit_workflow.py +831 -0
- dori-0.1.0/dori/boilerplate/scripts/_web_common.py +69 -0
- dori-0.1.0/dori/boilerplate/scripts/analyze-folder.py +251 -0
- dori-0.1.0/dori/boilerplate/scripts/calendar.py +32 -0
- dori-0.1.0/dori/boilerplate/scripts/commit.py +22 -0
- dori-0.1.0/dori/boilerplate/scripts/docker.py +109 -0
- dori-0.1.0/dori/boilerplate/scripts/git.py +267 -0
- dori-0.1.0/dori/boilerplate/skills/analyze-folder.md +25 -0
- dori-0.1.0/dori/boilerplate/skills/calendar.md +18 -0
- dori-0.1.0/dori/boilerplate/skills/devtools/_index.md +5 -0
- dori-0.1.0/dori/boilerplate/skills/devtools/commit.md +20 -0
- dori-0.1.0/dori/boilerplate/skills/devtools/docker.md +18 -0
- dori-0.1.0/dori/boilerplate/skills/devtools/git.md +32 -0
- dori-0.1.0/dori/chat.py +561 -0
- dori-0.1.0/dori/commands.py +620 -0
- dori-0.1.0/dori/loader.py +129 -0
- dori-0.1.0/dori/main.py +160 -0
- dori-0.1.0/dori/models.py +29 -0
- dori-0.1.0/dori/schemas.py +112 -0
- dori-0.1.0/dori/script.py +222 -0
- dori-0.1.0/dori/tui.py +649 -0
- dori-0.1.0/dori/tui.tcss +82 -0
- dori-0.1.0/dori/workflow.py +606 -0
- dori-0.1.0/dori.egg-info/PKG-INFO +248 -0
- dori-0.1.0/dori.egg-info/SOURCES.txt +58 -0
- dori-0.1.0/dori.egg-info/dependency_links.txt +1 -0
- dori-0.1.0/dori.egg-info/entry_points.txt +2 -0
- dori-0.1.0/dori.egg-info/requires.txt +11 -0
- dori-0.1.0/dori.egg-info/top_level.txt +1 -0
- dori-0.1.0/pyproject.toml +94 -0
- dori-0.1.0/setup.cfg +4 -0
- dori-0.1.0/tests/test_boilerplate.py +398 -0
- dori-0.1.0/tests/test_chat.py +611 -0
- dori-0.1.0/tests/test_cli_dispatch.py +121 -0
- dori-0.1.0/tests/test_commit_workflow.py +948 -0
- dori-0.1.0/tests/test_git_expert_script.py +286 -0
- dori-0.1.0/tests/test_init_presets.py +447 -0
- dori-0.1.0/tests/test_loader.py +10 -0
- dori-0.1.0/tests/test_ollama_integration.py +112 -0
- dori-0.1.0/tests/test_script_api.py +418 -0
- dori-0.1.0/tests/test_tui.py +756 -0
- dori-0.1.0/tests/test_web_common.py +91 -0
- dori-0.1.0/tests/test_web_ddgs.py +267 -0
- dori-0.1.0/tests/test_web_tavily.py +175 -0
- 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
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.
|
|
Binary file
|
|
@@ -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()
|