kei-typing-assistant 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 (70) hide show
  1. kei_typing_assistant-0.1.0/LICENSE +21 -0
  2. kei_typing_assistant-0.1.0/PKG-INFO +249 -0
  3. kei_typing_assistant-0.1.0/README.md +207 -0
  4. kei_typing_assistant-0.1.0/pyproject.toml +124 -0
  5. kei_typing_assistant-0.1.0/src/kei_typing_assistant/__init__.py +3 -0
  6. kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/__init__.py +9 -0
  7. kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/logging.py +25 -0
  8. kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/settings.py +193 -0
  9. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/__init__.py +10 -0
  10. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/__init__.py +1 -0
  11. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/exceptions.py +7 -0
  12. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/models.py +42 -0
  13. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/services.py +87 -0
  14. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/__init__.py +0 -0
  15. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/exceptions.py +14 -0
  16. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/models.py +52 -0
  17. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/services.py +87 -0
  18. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/__init__.py +1 -0
  19. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/exceptions.py +15 -0
  20. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/models.py +49 -0
  21. kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/services.py +140 -0
  22. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/__init__.py +10 -0
  23. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/__init__.py +1 -0
  24. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/_core.py +39 -0
  25. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/app.py +118 -0
  26. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/config.py +35 -0
  27. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/db.py +33 -0
  28. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/model.py +55 -0
  29. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/__init__.py +1 -0
  30. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/ai_tab.py +204 -0
  31. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/app.py +113 -0
  32. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/appearance_tab.py +132 -0
  33. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/behaviour_tab.py +70 -0
  34. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/context_fragments_window.py +255 -0
  35. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/general_tab.py +51 -0
  36. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/ghost_text_edit.py +246 -0
  37. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/hotkey_listener.py +96 -0
  38. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/hotkeys_tab.py +47 -0
  39. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/local_llm_panel.py +194 -0
  40. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/overlay_window.py +439 -0
  41. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/settings_window.py +211 -0
  42. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/storage_tab.py +66 -0
  43. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/styles.py +242 -0
  44. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/suggestion_examples_window.py +321 -0
  45. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/tray.py +225 -0
  46. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/widgets.py +74 -0
  47. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/workers/__init__.py +1 -0
  48. kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/workers/model_download_worker.py +46 -0
  49. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/__init__.py +10 -0
  50. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/__init__.py +1 -0
  51. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/ai_provider.py +25 -0
  52. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/embedding_provider.py +34 -0
  53. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/hybrid_provider.py +94 -0
  54. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/local_llm_provider.py +249 -0
  55. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/minimax_provider.py +36 -0
  56. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/openrouter_provider.py +51 -0
  57. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/__init__.py +1 -0
  58. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/database.py +75 -0
  59. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/__init__.py +1 -0
  60. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/env.py +61 -0
  61. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/script.py.mako +26 -0
  62. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/versions/05b43b808e6b_add_search_and_shown_counts_to_.py +34 -0
  63. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/versions/14ec893d64ed_initial_schema.py +48 -0
  64. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/models.py +34 -0
  65. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/repositories.py +203 -0
  66. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/keychain/__init__.py +1 -0
  67. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/keychain/keychain.py +29 -0
  68. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/model_manager/__init__.py +0 -0
  69. kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/model_manager/downloader.py +82 -0
  70. kei_typing_assistant-0.1.0/src/kei_typing_assistant/main.py +131 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sergei Konovalov
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,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: kei-typing-assistant
3
+ Version: 0.1.0
4
+ Summary: AI-powered desktop typing assistant with real-time ghost-text completions, personalized via local embeddings and semantic example retrieval
5
+ Keywords: typing-assistant,ai,llm,autocomplete,productivity,ghost-text
6
+ Author: Sergei Konovalov
7
+ Author-email: Sergei Konovalov <l0kifs91@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: MacOS X
12
+ Classifier: Environment :: Win32 (MS Windows)
13
+ Classifier: Environment :: X11 Applications :: Qt
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Text Editors :: Text Processing
20
+ Requires-Dist: anthropic>=0.112.0
21
+ Requires-Dist: openai>=2.44.0
22
+ Requires-Dist: llama-cpp-python>=0.3.0
23
+ Requires-Dist: huggingface-hub>=1.21.0
24
+ Requires-Dist: fastembed>=0.8.0
25
+ Requires-Dist: pyqt6>=6.11.0
26
+ Requires-Dist: pynput>=1.8.2
27
+ Requires-Dist: qasync>=0.28.0
28
+ Requires-Dist: typer>=0.9.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: sqlalchemy>=2.0.0
31
+ Requires-Dist: alembic>=1.18.5
32
+ Requires-Dist: pydantic-settings>=2.14.2
33
+ Requires-Dist: loguru>=0.7.3
34
+ Requires-Dist: pydantic>=2.13.4
35
+ Requires-Dist: keyring>=25.7.0
36
+ Requires-Python: >=3.12
37
+ Project-URL: Homepage, https://github.com/l0kifs/kei-typing-assistant
38
+ Project-URL: Repository, https://github.com/l0kifs/kei-typing-assistant
39
+ Project-URL: Issues, https://github.com/l0kifs/kei-typing-assistant/issues
40
+ Project-URL: Changelog, https://github.com/l0kifs/kei-typing-assistant/releases
41
+ Description-Content-Type: text/markdown
42
+
43
+ # Kei Typing Assistant
44
+
45
+ An AI-powered desktop typing assistant that provides real-time text completions as ghost text via a floating overlay window, accessible from any application. Learns your writing style over time for increasingly personalized suggestions.
46
+
47
+ ## Features
48
+
49
+ - Global hotkey opens a compact overlay window from any app; press it again to copy and close
50
+ - Streaming AI suggestions appear as faint ghost text while you type
51
+ - Accept a suggestion with `Tab`, dismiss with `Esc`
52
+ - Three AI providers: local LLM (llama.cpp, runs fully offline), MiniMax, and OpenRouter
53
+ - **Hybrid mode**: races the local model against a cloud provider and upgrades to the cloud result if it arrives first, within a configurable time window
54
+ - Model warm-up loads the local model in the background at startup so the first suggestion isn't slowed by a cold start
55
+ - Accepted suggestions are stored with semantic embeddings and retrieved as personalized few-shot context, with per-example search/shown counts tracked over time
56
+ - Manage stored examples (view, search, add, delete) from a dedicated GUI window
57
+ - Reusable, ordered **context fragments** — curated notes you can prepend to the AI system prompt — managed from their own GUI window
58
+ - Mid-word suppression skips AI requests while the cursor sits inside a word
59
+ - API keys stored securely in the system keychain
60
+ - SQLite storage with Alembic-managed schema migrations
61
+
62
+ ## Requirements
63
+
64
+ - Python 3.12+
65
+ - macOS 12+ (primary), Windows 10/11, Linux supported
66
+ - API key for [MiniMax](https://www.minimaxi.com) or [OpenRouter](https://openrouter.ai) (not required for the default local LLM provider)
67
+ - macOS: Accessibility permission required for global hotkey
68
+
69
+ ## Quick Start
70
+
71
+ ### 1. Install
72
+
73
+ ```bash
74
+ git clone https://github.com/l0kifs/kei-typing-assistant.git
75
+ cd kei-typing-assistant
76
+ uv sync
77
+ ```
78
+
79
+ ### 2. Configure (optional)
80
+
81
+ Create a `.env` file in the project root to override defaults:
82
+
83
+ ```env
84
+ KEI__AI_PROVIDER=local_llm # local_llm | minimax | openrouter
85
+ KEI__HOTKEY=ctrl+alt+space # global hotkey
86
+ KEI__FLAVOUR="Concise, formal" # personalization hint for AI
87
+ ```
88
+
89
+ All settings with defaults are documented in [src/kei_typing_assistant/config/settings.py](src/kei_typing_assistant/config/settings.py).
90
+
91
+ ### 3. Add your API key (cloud providers only)
92
+
93
+ If using MiniMax or OpenRouter, open Settings from the tray icon and enter your API key. Keys are stored in the system keychain — never in plaintext. The default local LLM provider needs no API key; the model is downloaded from Hugging Face on first run.
94
+
95
+ ### 4. Grant Accessibility permission (macOS)
96
+
97
+ **System Settings → Privacy & Security → Accessibility** — add the app or your terminal to allow global hotkey capture.
98
+
99
+ ### 5. Run
100
+
101
+ ```bash
102
+ uv run kei
103
+ ```
104
+
105
+ The app runs in the system tray. Press the hotkey (`Ctrl+Alt+Space` by default) to open the overlay.
106
+
107
+ ## Usage
108
+
109
+ | Action | Key |
110
+ |--------|-----|
111
+ | Open overlay | `Ctrl+Alt+Space` (configurable) |
112
+ | Accept suggestion | `Tab` |
113
+ | Copy text & close | `Ctrl+Alt+Space` again |
114
+ | Dismiss without copying | `Esc` |
115
+
116
+ ## AI Providers
117
+
118
+ | Provider | Setting value | Notes |
119
+ |----------|---------------|-------|
120
+ | Local LLM | `local_llm` (default) | Runs fully offline via llama.cpp; GGUF model auto-downloaded from Hugging Face; GPU-accelerated via Metal/CUDA where available |
121
+ | MiniMax | `minimax` | Cloud API, accessed via an Anthropic-compatible endpoint |
122
+ | OpenRouter | `openrouter` | Cloud API, accessed via an OpenAI-compatible endpoint; free-tier models available |
123
+ | Hybrid | enable `hybrid_mode` | Races the local model against a chosen cloud provider (MiniMax or OpenRouter) and swaps in the cloud result if it beats the local one within `hybrid_upgrade_window_s` |
124
+
125
+ ## Settings Window
126
+
127
+ | Tab | Contents |
128
+ |-----|----------|
129
+ | General | Writing-style ("flavour") hint injected into the AI system prompt |
130
+ | AI Model | Provider selection (Local LLM / MiniMax / OpenRouter / Hybrid) and per-provider configuration (model, API key, context window, GPU layers, sampling params) |
131
+ | Behaviour | Suggestion length, temperature, debounce delay, mid-word suppression, include-context-fragments toggle |
132
+ | Appearance | Theme, ghost-text opacity, font family/size, overlay position |
133
+ | Hotkeys | Global hotkey binding |
134
+ | Storage | Data directory location (models, database) |
135
+
136
+ Two standalone windows, opened from the tray/settings menu, manage personalization data directly:
137
+
138
+ - **Context Fragments** — add, edit, reorder, search, and delete reusable prompt snippets prepended to the AI system prompt when enabled
139
+ - **Suggestion Examples** — browse, search, add, and delete the accepted (input → suggestion) pairs used for semantic few-shot retrieval, including how often each has been searched/shown
140
+
141
+ ## Configuration Reference
142
+
143
+ | Setting | Default | Description |
144
+ |---------|---------|-------------|
145
+ | `KEI__HOTKEY` | `ctrl+alt+space` | Global trigger hotkey |
146
+ | `KEI__AI_PROVIDER` | `local_llm` | `local_llm`, `minimax`, or `openrouter` |
147
+ | `KEI__AI_MAX_TOKENS` | `60` | Max suggestion length |
148
+ | `KEI__AI_TEMPERATURE` | `0.8` | Sampling temperature |
149
+ | `KEI__AI_DEBOUNCE_MS` | `300` | Delay before AI request (ms) |
150
+ | `KEI__MID_WORD_SUPPRESSION` | `false` | Skip requests while the cursor is mid-word |
151
+ | `KEI__INCLUDE_CONTEXT_FRAGMENTS` | `false` | Prepend saved context fragments to the AI system prompt |
152
+ | `KEI__FLAVOUR` | `` | Writing style hint for AI |
153
+ | `KEI__THEME` | `system` | `system`, `dark`, or `light` |
154
+ | `KEI__HYBRID_MODE` | `false` | Race local LLM against a cloud provider |
155
+ | `KEI__HYBRID_UPGRADE_WINDOW_S` | `1.5` | Seconds to wait for the cloud provider before keeping the local result |
156
+ | `KEI__LOCAL_LLM_MODEL_ID` | `Qwen/Qwen2.5-3B-Instruct-GGUF` | Hugging Face repo for the local model |
157
+ | `KEI__LOCAL_LLM_N_GPU_LAYERS` | `-1` | GPU layers to offload (`-1` = all, `0` = CPU only) |
158
+ | `KEI__SEMANTIC_EXAMPLES_TOP_K` | `5` | Max similar accepted examples injected as few-shot context |
159
+ | `KEI__DATA_DIR` | `~/.kei_typing_assistant` | Base directory for models, database, and settings |
160
+
161
+ Full list of settings, defaults, and validation ranges: [src/kei_typing_assistant/config/settings.py](src/kei_typing_assistant/config/settings.py).
162
+
163
+ ## Development
164
+
165
+ ```bash
166
+ # Run tests
167
+ pytest
168
+
169
+ # Run tests with coverage
170
+ pytest --cov=src/kei_typing_assistant
171
+
172
+ # Lint & format
173
+ ruff check .
174
+ ruff format .
175
+ ```
176
+
177
+ ## CLI Reference
178
+
179
+ `kei-cli` provides a command-line interface for testing suggestions, managing the semantic example store, local models, database migrations, and configuration.
180
+
181
+ ```bash
182
+ uv run kei-cli [--verbose] COMMAND [ARGS]
183
+ ```
184
+
185
+ ### Suggestion commands
186
+
187
+ | Command | Description |
188
+ |---------|-------------|
189
+ | `suggest TEXT` | Stream an AI suggestion for the given text |
190
+ | `accept TEXT SUGGESTION` | Save an accepted (input, suggestion) pair to the semantic example store |
191
+ | `test [--text TEXT]` | Full E2E flow: suggest → save accepted example |
192
+
193
+ **Typical workflow:**
194
+
195
+ ```bash
196
+ # 1. Get a suggestion
197
+ uv run kei-cli suggest "The weather today is"
198
+ # → warm and sunny with clear skies
199
+
200
+ # 2. Accept it — embeds and saves for future retrieval
201
+ uv run kei-cli accept "The weather today is" "warm and sunny with clear skies"
202
+ # ✓ Saved: 'The weather today is' → 'warm and sunny with clear skies'
203
+
204
+ # 3. Next suggest call retrieves similar stored examples as context
205
+ uv run kei-cli suggest "The weather this morning is"
206
+ ```
207
+
208
+ `suggest` options: `--flavour TEXT`, `--max-tokens INT`
209
+ `test` options: `--text TEXT`, `--flavour TEXT`, `--max-tokens INT`
210
+
211
+ ### Local model
212
+
213
+ ```bash
214
+ uv run kei-cli model list # List downloaded models
215
+ uv run kei-cli model status MODEL_ID # Check if a model is downloaded
216
+ uv run kei-cli model delete MODEL_ID # Delete a downloaded model from disk
217
+ ```
218
+
219
+ ### Database
220
+
221
+ ```bash
222
+ uv run kei-cli db upgrade # Migrate the database to the latest schema revision
223
+ uv run kei-cli db current # Show the currently applied schema revision
224
+ ```
225
+
226
+ ### Configuration
227
+
228
+ ```bash
229
+ uv run kei-cli config show # Show current configuration
230
+ ```
231
+
232
+ ## Architecture
233
+
234
+ The project follows Domain-Driven Design. See [docs/ddd-architecture-rules.md](docs/ddd-architecture-rules.md) for conventions and [docs/BRD.md](docs/BRD.md) for the business requirements behind it.
235
+
236
+ ```
237
+ src/kei_typing_assistant/
238
+ ├── main.py # Composition root: wiring, bootstrap, launch
239
+ ├── config/ # Settings & logging
240
+ ├── domains/ # Pure business logic (suggestion, context_fragments, local_model)
241
+ ├── infrastructure/ # AI provider clients, SQLite database + Alembic migrations, keychain
242
+ └── entry_points/ # Qt GUI, hotkey listener, tray, CLI, background workers
243
+ ```
244
+
245
+ Database schema changes are managed with Alembic; migrations run automatically at startup (`kei-cli db upgrade` for manual control).
246
+
247
+ ## Publishing
248
+
249
+ See [docs/PUBLISHING.md](docs/PUBLISHING.md). Releases are published to PyPI automatically via GitHub Actions on tag push. See [CHANGELOG.md](CHANGELOG.md) for release history.
@@ -0,0 +1,207 @@
1
+ # Kei Typing Assistant
2
+
3
+ An AI-powered desktop typing assistant that provides real-time text completions as ghost text via a floating overlay window, accessible from any application. Learns your writing style over time for increasingly personalized suggestions.
4
+
5
+ ## Features
6
+
7
+ - Global hotkey opens a compact overlay window from any app; press it again to copy and close
8
+ - Streaming AI suggestions appear as faint ghost text while you type
9
+ - Accept a suggestion with `Tab`, dismiss with `Esc`
10
+ - Three AI providers: local LLM (llama.cpp, runs fully offline), MiniMax, and OpenRouter
11
+ - **Hybrid mode**: races the local model against a cloud provider and upgrades to the cloud result if it arrives first, within a configurable time window
12
+ - Model warm-up loads the local model in the background at startup so the first suggestion isn't slowed by a cold start
13
+ - Accepted suggestions are stored with semantic embeddings and retrieved as personalized few-shot context, with per-example search/shown counts tracked over time
14
+ - Manage stored examples (view, search, add, delete) from a dedicated GUI window
15
+ - Reusable, ordered **context fragments** — curated notes you can prepend to the AI system prompt — managed from their own GUI window
16
+ - Mid-word suppression skips AI requests while the cursor sits inside a word
17
+ - API keys stored securely in the system keychain
18
+ - SQLite storage with Alembic-managed schema migrations
19
+
20
+ ## Requirements
21
+
22
+ - Python 3.12+
23
+ - macOS 12+ (primary), Windows 10/11, Linux supported
24
+ - API key for [MiniMax](https://www.minimaxi.com) or [OpenRouter](https://openrouter.ai) (not required for the default local LLM provider)
25
+ - macOS: Accessibility permission required for global hotkey
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Install
30
+
31
+ ```bash
32
+ git clone https://github.com/l0kifs/kei-typing-assistant.git
33
+ cd kei-typing-assistant
34
+ uv sync
35
+ ```
36
+
37
+ ### 2. Configure (optional)
38
+
39
+ Create a `.env` file in the project root to override defaults:
40
+
41
+ ```env
42
+ KEI__AI_PROVIDER=local_llm # local_llm | minimax | openrouter
43
+ KEI__HOTKEY=ctrl+alt+space # global hotkey
44
+ KEI__FLAVOUR="Concise, formal" # personalization hint for AI
45
+ ```
46
+
47
+ All settings with defaults are documented in [src/kei_typing_assistant/config/settings.py](src/kei_typing_assistant/config/settings.py).
48
+
49
+ ### 3. Add your API key (cloud providers only)
50
+
51
+ If using MiniMax or OpenRouter, open Settings from the tray icon and enter your API key. Keys are stored in the system keychain — never in plaintext. The default local LLM provider needs no API key; the model is downloaded from Hugging Face on first run.
52
+
53
+ ### 4. Grant Accessibility permission (macOS)
54
+
55
+ **System Settings → Privacy & Security → Accessibility** — add the app or your terminal to allow global hotkey capture.
56
+
57
+ ### 5. Run
58
+
59
+ ```bash
60
+ uv run kei
61
+ ```
62
+
63
+ The app runs in the system tray. Press the hotkey (`Ctrl+Alt+Space` by default) to open the overlay.
64
+
65
+ ## Usage
66
+
67
+ | Action | Key |
68
+ |--------|-----|
69
+ | Open overlay | `Ctrl+Alt+Space` (configurable) |
70
+ | Accept suggestion | `Tab` |
71
+ | Copy text & close | `Ctrl+Alt+Space` again |
72
+ | Dismiss without copying | `Esc` |
73
+
74
+ ## AI Providers
75
+
76
+ | Provider | Setting value | Notes |
77
+ |----------|---------------|-------|
78
+ | Local LLM | `local_llm` (default) | Runs fully offline via llama.cpp; GGUF model auto-downloaded from Hugging Face; GPU-accelerated via Metal/CUDA where available |
79
+ | MiniMax | `minimax` | Cloud API, accessed via an Anthropic-compatible endpoint |
80
+ | OpenRouter | `openrouter` | Cloud API, accessed via an OpenAI-compatible endpoint; free-tier models available |
81
+ | Hybrid | enable `hybrid_mode` | Races the local model against a chosen cloud provider (MiniMax or OpenRouter) and swaps in the cloud result if it beats the local one within `hybrid_upgrade_window_s` |
82
+
83
+ ## Settings Window
84
+
85
+ | Tab | Contents |
86
+ |-----|----------|
87
+ | General | Writing-style ("flavour") hint injected into the AI system prompt |
88
+ | AI Model | Provider selection (Local LLM / MiniMax / OpenRouter / Hybrid) and per-provider configuration (model, API key, context window, GPU layers, sampling params) |
89
+ | Behaviour | Suggestion length, temperature, debounce delay, mid-word suppression, include-context-fragments toggle |
90
+ | Appearance | Theme, ghost-text opacity, font family/size, overlay position |
91
+ | Hotkeys | Global hotkey binding |
92
+ | Storage | Data directory location (models, database) |
93
+
94
+ Two standalone windows, opened from the tray/settings menu, manage personalization data directly:
95
+
96
+ - **Context Fragments** — add, edit, reorder, search, and delete reusable prompt snippets prepended to the AI system prompt when enabled
97
+ - **Suggestion Examples** — browse, search, add, and delete the accepted (input → suggestion) pairs used for semantic few-shot retrieval, including how often each has been searched/shown
98
+
99
+ ## Configuration Reference
100
+
101
+ | Setting | Default | Description |
102
+ |---------|---------|-------------|
103
+ | `KEI__HOTKEY` | `ctrl+alt+space` | Global trigger hotkey |
104
+ | `KEI__AI_PROVIDER` | `local_llm` | `local_llm`, `minimax`, or `openrouter` |
105
+ | `KEI__AI_MAX_TOKENS` | `60` | Max suggestion length |
106
+ | `KEI__AI_TEMPERATURE` | `0.8` | Sampling temperature |
107
+ | `KEI__AI_DEBOUNCE_MS` | `300` | Delay before AI request (ms) |
108
+ | `KEI__MID_WORD_SUPPRESSION` | `false` | Skip requests while the cursor is mid-word |
109
+ | `KEI__INCLUDE_CONTEXT_FRAGMENTS` | `false` | Prepend saved context fragments to the AI system prompt |
110
+ | `KEI__FLAVOUR` | `` | Writing style hint for AI |
111
+ | `KEI__THEME` | `system` | `system`, `dark`, or `light` |
112
+ | `KEI__HYBRID_MODE` | `false` | Race local LLM against a cloud provider |
113
+ | `KEI__HYBRID_UPGRADE_WINDOW_S` | `1.5` | Seconds to wait for the cloud provider before keeping the local result |
114
+ | `KEI__LOCAL_LLM_MODEL_ID` | `Qwen/Qwen2.5-3B-Instruct-GGUF` | Hugging Face repo for the local model |
115
+ | `KEI__LOCAL_LLM_N_GPU_LAYERS` | `-1` | GPU layers to offload (`-1` = all, `0` = CPU only) |
116
+ | `KEI__SEMANTIC_EXAMPLES_TOP_K` | `5` | Max similar accepted examples injected as few-shot context |
117
+ | `KEI__DATA_DIR` | `~/.kei_typing_assistant` | Base directory for models, database, and settings |
118
+
119
+ Full list of settings, defaults, and validation ranges: [src/kei_typing_assistant/config/settings.py](src/kei_typing_assistant/config/settings.py).
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ # Run tests
125
+ pytest
126
+
127
+ # Run tests with coverage
128
+ pytest --cov=src/kei_typing_assistant
129
+
130
+ # Lint & format
131
+ ruff check .
132
+ ruff format .
133
+ ```
134
+
135
+ ## CLI Reference
136
+
137
+ `kei-cli` provides a command-line interface for testing suggestions, managing the semantic example store, local models, database migrations, and configuration.
138
+
139
+ ```bash
140
+ uv run kei-cli [--verbose] COMMAND [ARGS]
141
+ ```
142
+
143
+ ### Suggestion commands
144
+
145
+ | Command | Description |
146
+ |---------|-------------|
147
+ | `suggest TEXT` | Stream an AI suggestion for the given text |
148
+ | `accept TEXT SUGGESTION` | Save an accepted (input, suggestion) pair to the semantic example store |
149
+ | `test [--text TEXT]` | Full E2E flow: suggest → save accepted example |
150
+
151
+ **Typical workflow:**
152
+
153
+ ```bash
154
+ # 1. Get a suggestion
155
+ uv run kei-cli suggest "The weather today is"
156
+ # → warm and sunny with clear skies
157
+
158
+ # 2. Accept it — embeds and saves for future retrieval
159
+ uv run kei-cli accept "The weather today is" "warm and sunny with clear skies"
160
+ # ✓ Saved: 'The weather today is' → 'warm and sunny with clear skies'
161
+
162
+ # 3. Next suggest call retrieves similar stored examples as context
163
+ uv run kei-cli suggest "The weather this morning is"
164
+ ```
165
+
166
+ `suggest` options: `--flavour TEXT`, `--max-tokens INT`
167
+ `test` options: `--text TEXT`, `--flavour TEXT`, `--max-tokens INT`
168
+
169
+ ### Local model
170
+
171
+ ```bash
172
+ uv run kei-cli model list # List downloaded models
173
+ uv run kei-cli model status MODEL_ID # Check if a model is downloaded
174
+ uv run kei-cli model delete MODEL_ID # Delete a downloaded model from disk
175
+ ```
176
+
177
+ ### Database
178
+
179
+ ```bash
180
+ uv run kei-cli db upgrade # Migrate the database to the latest schema revision
181
+ uv run kei-cli db current # Show the currently applied schema revision
182
+ ```
183
+
184
+ ### Configuration
185
+
186
+ ```bash
187
+ uv run kei-cli config show # Show current configuration
188
+ ```
189
+
190
+ ## Architecture
191
+
192
+ The project follows Domain-Driven Design. See [docs/ddd-architecture-rules.md](docs/ddd-architecture-rules.md) for conventions and [docs/BRD.md](docs/BRD.md) for the business requirements behind it.
193
+
194
+ ```
195
+ src/kei_typing_assistant/
196
+ ├── main.py # Composition root: wiring, bootstrap, launch
197
+ ├── config/ # Settings & logging
198
+ ├── domains/ # Pure business logic (suggestion, context_fragments, local_model)
199
+ ├── infrastructure/ # AI provider clients, SQLite database + Alembic migrations, keychain
200
+ └── entry_points/ # Qt GUI, hotkey listener, tray, CLI, background workers
201
+ ```
202
+
203
+ Database schema changes are managed with Alembic; migrations run automatically at startup (`kei-cli db upgrade` for manual control).
204
+
205
+ ## Publishing
206
+
207
+ See [docs/PUBLISHING.md](docs/PUBLISHING.md). Releases are published to PyPI automatically via GitHub Actions on tag push. See [CHANGELOG.md](CHANGELOG.md) for release history.
@@ -0,0 +1,124 @@
1
+ [project]
2
+ name = "kei-typing-assistant"
3
+ version = "0.1.0"
4
+ description = "AI-powered desktop typing assistant with real-time ghost-text completions, personalized via local embeddings and semantic example retrieval"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Sergei Konovalov", email = "l0kifs91@gmail.com" },
10
+ ]
11
+ keywords = ["typing-assistant", "ai", "llm", "autocomplete", "productivity", "ghost-text"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Environment :: MacOS X",
15
+ "Environment :: Win32 (MS Windows)",
16
+ "Environment :: X11 Applications :: Qt",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Operating System :: MacOS",
19
+ "Operating System :: Microsoft :: Windows",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Text Editors :: Text Processing",
23
+ ]
24
+ requires-python = ">=3.12"
25
+ dependencies = [
26
+ # LLM dependencies
27
+ "anthropic>=0.112.0",
28
+ "openai>=2.44.0",
29
+ "llama-cpp-python>=0.3.0",
30
+ "huggingface_hub>=1.21.0",
31
+ "fastembed>=0.8.0",
32
+ # GUI dependencies
33
+ "PyQt6>=6.11.0",
34
+ "pynput>=1.8.2",
35
+ "qasync>=0.28.0",
36
+ # CLI dependencies
37
+ "typer>=0.9.0",
38
+ "rich>=13.0.0",
39
+ # Database dependencies
40
+ "sqlalchemy>=2.0.0",
41
+ "alembic>=1.18.5",
42
+ # Other dependencies
43
+ "pydantic-settings>=2.14.2",
44
+ "loguru>=0.7.3",
45
+ "pydantic>=2.13.4",
46
+ "keyring>=25.7.0",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/l0kifs/kei-typing-assistant"
51
+ Repository = "https://github.com/l0kifs/kei-typing-assistant"
52
+ Issues = "https://github.com/l0kifs/kei-typing-assistant/issues"
53
+ Changelog = "https://github.com/l0kifs/kei-typing-assistant/releases"
54
+
55
+ [project.scripts]
56
+ kei = "kei_typing_assistant.main:main"
57
+ kei-cli = "kei_typing_assistant.entry_points.cli.app:cli"
58
+
59
+ [dependency-groups]
60
+ dev = [
61
+ {include-group = "test"},
62
+ {include-group = "lint"},
63
+ ]
64
+ test = [
65
+ "pytest>=9.0.3",
66
+ "pytest-asyncio>=0.25.0",
67
+ "pytest-xdist>=3.6.1",
68
+ "pytest-cov>=7.1.0",
69
+ ]
70
+ lint = [
71
+ "ruff>=0.15.11",
72
+ "ty>=0.0.32",
73
+ "vulture>=2.14",
74
+ ]
75
+
76
+ [tool.uv]
77
+ package = true
78
+
79
+ [build-system]
80
+ requires = ["uv_build>=0.11.25,<0.12.0"]
81
+ build-backend = "uv_build"
82
+
83
+ [tool.pytest.ini_options]
84
+ testpaths = ["tests"]
85
+ python_files = ["test_*.py"]
86
+ python_classes = ["Test*"]
87
+ python_functions = ["test_*"]
88
+ norecursedirs = [".venv", "__pycache__", ".git"]
89
+ asyncio_mode = "auto"
90
+ addopts = [
91
+ "--tb=short",
92
+ "-ra",
93
+ "--import-mode=importlib",
94
+ ]
95
+ markers = [
96
+ "unit: isolated tests with all external dependencies stubbed",
97
+ "integration: service-level tests with real collaborators and real files",
98
+ "e2e: end-to-end tests requiring a running Docker Compose stack",
99
+ ]
100
+
101
+ [tool.ruff]
102
+ line-length = 120
103
+ target-version = "py312"
104
+
105
+ [tool.ruff.lint]
106
+ select = [
107
+ "E", # pycodestyle errors
108
+ "F", # pyflakes
109
+ "W", # pycodestyle warnings
110
+ "I", # import sorting
111
+ "B", # flake8-bugbear
112
+ "UP", # pyupgrade
113
+ "RUF", # Ruff-specific rules
114
+ ]
115
+
116
+ [tool.vulture]
117
+ min_confidence = 80
118
+ exclude = [".venv/"]
119
+ ignore_names = ["file_secret_settings"]
120
+
121
+ [tool.ty]
122
+
123
+ [tool.ty.src]
124
+ exclude = [".venv/**"]
@@ -0,0 +1,3 @@
1
+ """
2
+ Project package root.
3
+ """
@@ -0,0 +1,9 @@
1
+ """
2
+ Project configuration package.
3
+
4
+ Rules:
5
+ - Contains application settings, constants, and logging configuration.
6
+ - No technical implementation details (database engines, clients, etc.).
7
+ - No business logic.
8
+ - Should not import from domains, infrastructure or entry_points.
9
+ """
@@ -0,0 +1,25 @@
1
+ import sys
2
+
3
+ from loguru import logger
4
+
5
+
6
+ def configure_logging(debug: bool = False, quiet: bool = False) -> None:
7
+ """Configure application logging.
8
+
9
+ quiet=True suppresses all output — intended for CLI use where stdout/stderr
10
+ must stay clean for machine-readable or streaming output.
11
+ """
12
+ logger.remove()
13
+ if quiet:
14
+ return
15
+ level = "DEBUG" if debug else "INFO"
16
+ logger.add(
17
+ sys.stderr,
18
+ level=level,
19
+ format=(
20
+ "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
21
+ "<level>{level: <8}</level> | "
22
+ "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
23
+ "<level>{message}</level>"
24
+ ),
25
+ )