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.
- kei_typing_assistant-0.1.0/LICENSE +21 -0
- kei_typing_assistant-0.1.0/PKG-INFO +249 -0
- kei_typing_assistant-0.1.0/README.md +207 -0
- kei_typing_assistant-0.1.0/pyproject.toml +124 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/__init__.py +3 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/__init__.py +9 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/logging.py +25 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/config/settings.py +193 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/__init__.py +10 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/exceptions.py +7 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/models.py +42 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/context_fragments/services.py +87 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/__init__.py +0 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/exceptions.py +14 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/models.py +52 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/local_model/services.py +87 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/exceptions.py +15 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/models.py +49 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/domains/suggestion/services.py +140 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/__init__.py +10 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/_core.py +39 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/app.py +118 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/config.py +35 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/db.py +33 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/cli/model.py +55 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/ai_tab.py +204 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/app.py +113 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/appearance_tab.py +132 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/behaviour_tab.py +70 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/context_fragments_window.py +255 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/general_tab.py +51 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/ghost_text_edit.py +246 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/hotkey_listener.py +96 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/hotkeys_tab.py +47 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/local_llm_panel.py +194 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/overlay_window.py +439 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/settings_window.py +211 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/storage_tab.py +66 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/styles.py +242 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/suggestion_examples_window.py +321 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/tray.py +225 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/gui/widgets.py +74 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/workers/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/entry_points/workers/model_download_worker.py +46 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/__init__.py +10 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/ai_provider.py +25 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/embedding_provider.py +34 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/hybrid_provider.py +94 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/local_llm_provider.py +249 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/minimax_provider.py +36 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/clients/openrouter_provider.py +51 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/database.py +75 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/env.py +61 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/script.py.mako +26 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/versions/05b43b808e6b_add_search_and_shown_counts_to_.py +34 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/migrations/versions/14ec893d64ed_initial_schema.py +48 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/models.py +34 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/database/repositories.py +203 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/keychain/__init__.py +1 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/keychain/keychain.py +29 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/model_manager/__init__.py +0 -0
- kei_typing_assistant-0.1.0/src/kei_typing_assistant/infrastructure/model_manager/downloader.py +82 -0
- 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,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
|
+
)
|