edith-pc 0.3.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 (125) hide show
  1. edith_pc-0.3.0/LICENSE +21 -0
  2. edith_pc-0.3.0/PKG-INFO +75 -0
  3. edith_pc-0.3.0/PYPI.md +39 -0
  4. edith_pc-0.3.0/README.md +262 -0
  5. edith_pc-0.3.0/edith/__init__.py +13 -0
  6. edith_pc-0.3.0/edith/audio_io.py +130 -0
  7. edith_pc-0.3.0/edith/backup.py +260 -0
  8. edith_pc-0.3.0/edith/body/__init__.py +17 -0
  9. edith_pc-0.3.0/edith/body/__main__.py +116 -0
  10. edith_pc-0.3.0/edith/body/cli.py +224 -0
  11. edith_pc-0.3.0/edith/body/client.py +371 -0
  12. edith_pc-0.3.0/edith/body/desktop.py +478 -0
  13. edith_pc-0.3.0/edith/body/identity.py +10 -0
  14. edith_pc-0.3.0/edith/body/link.py +206 -0
  15. edith_pc-0.3.0/edith/body/pc_backup.py +347 -0
  16. edith_pc-0.3.0/edith/config.py +205 -0
  17. edith_pc-0.3.0/edith/gui.py +610 -0
  18. edith_pc-0.3.0/edith/gui_classic.py +352 -0
  19. edith_pc-0.3.0/edith/hud/__init__.py +11 -0
  20. edith_pc-0.3.0/edith/hud/easing.py +72 -0
  21. edith_pc-0.3.0/edith/hud/fonts.py +69 -0
  22. edith_pc-0.3.0/edith/hud/orb_renderer.py +66 -0
  23. edith_pc-0.3.0/edith/hud/textures.py +39 -0
  24. edith_pc-0.3.0/edith/legacy_migration.py +185 -0
  25. edith_pc-0.3.0/edith/logging_setup.py +53 -0
  26. edith_pc-0.3.0/edith/main.py +91 -0
  27. edith_pc-0.3.0/edith/pricing.py +143 -0
  28. edith_pc-0.3.0/edith/prompts.py +117 -0
  29. edith_pc-0.3.0/edith/realtime_client.py +685 -0
  30. edith_pc-0.3.0/edith/relay/__init__.py +12 -0
  31. edith_pc-0.3.0/edith/relay/__main__.py +4 -0
  32. edith_pc-0.3.0/edith/relay/bridge.py +333 -0
  33. edith_pc-0.3.0/edith/relay/crypto.py +186 -0
  34. edith_pc-0.3.0/edith/relay/server.py +147 -0
  35. edith_pc-0.3.0/edith/service/__init__.py +18 -0
  36. edith_pc-0.3.0/edith/service/__main__.py +130 -0
  37. edith_pc-0.3.0/edith/service/app.py +1180 -0
  38. edith_pc-0.3.0/edith/service/approvals.py +119 -0
  39. edith_pc-0.3.0/edith/service/brain.py +371 -0
  40. edith_pc-0.3.0/edith/service/devicebook.py +212 -0
  41. edith_pc-0.3.0/edith/service/devices.py +200 -0
  42. edith_pc-0.3.0/edith/service/phone_agent.py +266 -0
  43. edith_pc-0.3.0/edith/service/protocol.py +99 -0
  44. edith_pc-0.3.0/edith/service/tokens.py +32 -0
  45. edith_pc-0.3.0/edith/service/totp.py +61 -0
  46. edith_pc-0.3.0/edith/service/webauth.py +238 -0
  47. edith_pc-0.3.0/edith/session_manager.py +99 -0
  48. edith_pc-0.3.0/edith/tools/__init__.py +1 -0
  49. edith_pc-0.3.0/edith/tools/briefing.py +115 -0
  50. edith_pc-0.3.0/edith/tools/clock.py +13 -0
  51. edith_pc-0.3.0/edith/tools/computer_agent.py +419 -0
  52. edith_pc-0.3.0/edith/tools/delegate.py +99 -0
  53. edith_pc-0.3.0/edith/tools/documents.py +76 -0
  54. edith_pc-0.3.0/edith/tools/external_cost.py +36 -0
  55. edith_pc-0.3.0/edith/tools/file_ops.py +189 -0
  56. edith_pc-0.3.0/edith/tools/google_workspace.py +205 -0
  57. edith_pc-0.3.0/edith/tools/health_watch.py +70 -0
  58. edith_pc-0.3.0/edith/tools/home_assistant.py +142 -0
  59. edith_pc-0.3.0/edith/tools/llm.py +28 -0
  60. edith_pc-0.3.0/edith/tools/memory.py +205 -0
  61. edith_pc-0.3.0/edith/tools/news_finance.py +137 -0
  62. edith_pc-0.3.0/edith/tools/notes.py +50 -0
  63. edith_pc-0.3.0/edith/tools/pc_control.py +474 -0
  64. edith_pc-0.3.0/edith/tools/registry.py +942 -0
  65. edith_pc-0.3.0/edith/tools/reminders.py +129 -0
  66. edith_pc-0.3.0/edith/tools/spotify_music.py +141 -0
  67. edith_pc-0.3.0/edith/tools/system_control.py +197 -0
  68. edith_pc-0.3.0/edith/tools/system_toggles.py +261 -0
  69. edith_pc-0.3.0/edith/tools/task_manager.py +137 -0
  70. edith_pc-0.3.0/edith/tools/telegram_notify.py +36 -0
  71. edith_pc-0.3.0/edith/tools/vision.py +109 -0
  72. edith_pc-0.3.0/edith/tools/weather.py +57 -0
  73. edith_pc-0.3.0/edith/tools/web_search.py +65 -0
  74. edith_pc-0.3.0/edith/tools/whatsapp.py +97 -0
  75. edith_pc-0.3.0/edith/tools/window_control.py +146 -0
  76. edith_pc-0.3.0/edith/vault.py +395 -0
  77. edith_pc-0.3.0/edith/wake_word.py +159 -0
  78. edith_pc-0.3.0/edith_pc.egg-info/PKG-INFO +75 -0
  79. edith_pc-0.3.0/edith_pc.egg-info/SOURCES.txt +123 -0
  80. edith_pc-0.3.0/edith_pc.egg-info/dependency_links.txt +1 -0
  81. edith_pc-0.3.0/edith_pc.egg-info/entry_points.txt +5 -0
  82. edith_pc-0.3.0/edith_pc.egg-info/requires.txt +22 -0
  83. edith_pc-0.3.0/edith_pc.egg-info/top_level.txt +1 -0
  84. edith_pc-0.3.0/pyproject.toml +68 -0
  85. edith_pc-0.3.0/setup.cfg +4 -0
  86. edith_pc-0.3.0/tests/test_backup.py +178 -0
  87. edith_pc-0.3.0/tests/test_body.py +434 -0
  88. edith_pc-0.3.0/tests/test_briefing.py +63 -0
  89. edith_pc-0.3.0/tests/test_computer_agent.py +179 -0
  90. edith_pc-0.3.0/tests/test_delegate.py +67 -0
  91. edith_pc-0.3.0/tests/test_documents.py +55 -0
  92. edith_pc-0.3.0/tests/test_external_cost.py +41 -0
  93. edith_pc-0.3.0/tests/test_file_ops.py +94 -0
  94. edith_pc-0.3.0/tests/test_google.py +51 -0
  95. edith_pc-0.3.0/tests/test_home_assistant.py +51 -0
  96. edith_pc-0.3.0/tests/test_idle.py +53 -0
  97. edith_pc-0.3.0/tests/test_legacy_migration.py +184 -0
  98. edith_pc-0.3.0/tests/test_memory.py +107 -0
  99. edith_pc-0.3.0/tests/test_news_finance.py +61 -0
  100. edith_pc-0.3.0/tests/test_notes.py +29 -0
  101. edith_pc-0.3.0/tests/test_packaging.py +162 -0
  102. edith_pc-0.3.0/tests/test_pc_link.py +479 -0
  103. edith_pc-0.3.0/tests/test_phone_agent.py +152 -0
  104. edith_pc-0.3.0/tests/test_pricing.py +95 -0
  105. edith_pc-0.3.0/tests/test_registry.py +56 -0
  106. edith_pc-0.3.0/tests/test_relay.py +547 -0
  107. edith_pc-0.3.0/tests/test_run_command.py +73 -0
  108. edith_pc-0.3.0/tests/test_search.py +32 -0
  109. edith_pc-0.3.0/tests/test_service_app.py +177 -0
  110. edith_pc-0.3.0/tests/test_service_brain.py +177 -0
  111. edith_pc-0.3.0/tests/test_service_devices.py +94 -0
  112. edith_pc-0.3.0/tests/test_service_protocol.py +45 -0
  113. edith_pc-0.3.0/tests/test_service_transfer.py +201 -0
  114. edith_pc-0.3.0/tests/test_spotify.py +41 -0
  115. edith_pc-0.3.0/tests/test_system_toggles.py +118 -0
  116. edith_pc-0.3.0/tests/test_task_manager.py +94 -0
  117. edith_pc-0.3.0/tests/test_telegram.py +33 -0
  118. edith_pc-0.3.0/tests/test_vault.py +241 -0
  119. edith_pc-0.3.0/tests/test_voice_confirm.py +176 -0
  120. edith_pc-0.3.0/tests/test_voice_hud.py +262 -0
  121. edith_pc-0.3.0/tests/test_wake_word.py +56 -0
  122. edith_pc-0.3.0/tests/test_web_hud.py +221 -0
  123. edith_pc-0.3.0/tests/test_webauth.py +262 -0
  124. edith_pc-0.3.0/tests/test_whatsapp.py +36 -0
  125. edith_pc-0.3.0/tests/test_window_control.py +97 -0
edith_pc-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shuvo
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,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: edith-pc
3
+ Version: 0.3.0
4
+ Summary: EDITH on this PC: a body of your EDITH server, linked with a Google Authenticator code
5
+ Author: Shuvo
6
+ License-Expression: MIT
7
+ Keywords: assistant,voice assistant,automation,windows,openai
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: Microsoft :: Windows
10
+ Classifier: Environment :: Win32 (MS Windows)
11
+ Classifier: Topic :: Desktop Environment
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: openai>=1.70.0
16
+ Requires-Dist: websockets>=15.0
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: python-dotenv>=1.0
19
+ Requires-Dist: requests>=2.31
20
+ Requires-Dist: psutil>=5.9
21
+ Requires-Dist: APScheduler<4.0,>=3.10
22
+ Requires-Dist: SQLAlchemy>=2.0
23
+ Requires-Dist: pycaw>=20240210; sys_platform == "win32"
24
+ Requires-Dist: comtypes>=1.2; sys_platform == "win32"
25
+ Requires-Dist: pynput>=1.7
26
+ Requires-Dist: screen-brightness-control>=0.22
27
+ Requires-Dist: pyautogui>=0.9.54
28
+ Requires-Dist: pyperclip>=1.8
29
+ Requires-Dist: AppOpener>=1.7; sys_platform == "win32"
30
+ Requires-Dist: mss>=9.0
31
+ Requires-Dist: windows-toasts>=1.1; sys_platform == "win32"
32
+ Requires-Dist: pypdf>=4.0
33
+ Requires-Dist: Pillow>=10
34
+ Requires-Dist: customtkinter>=5.2
35
+ Dynamic: license-file
36
+
37
+ # EDITH for your PC
38
+
39
+ `edith-pc` makes a Windows PC a body of your **EDITH** server: the server's brain can use this
40
+ PC's tools (apps, windows, files, the screen, the keyboard, the shell) when you talk to EDITH
41
+ from your phone or the browser, from anywhere.
42
+
43
+ You need an EDITH server (the brain) with Google Authenticator sign-in set up. This package is
44
+ only the PC side.
45
+
46
+ ## Install and link
47
+
48
+ ```
49
+ pip install edith-pc
50
+ edith link
51
+ ```
52
+
53
+ `edith link` asks for your server's address and the 6-digit code Google Authenticator shows
54
+ for EDITH. That's all: no token or key to copy. The PC is linked, EDITH runs in the
55
+ background, and it starts with Windows from then on.
56
+
57
+ ## Commands
58
+
59
+ | Command | What it does |
60
+ | --- | --- |
61
+ | `edith status` | what this PC is linked to, and whether EDITH is running |
62
+ | `edith start` / `edith stop` | start or stop the background body |
63
+ | `edith app` | open the EDITH window |
64
+ | `edith run` | run the body in the terminal (Ctrl+C stops it) |
65
+ | `edith update` | install the newest version and restart |
66
+ | `edith unlink` | unlink this PC and stop starting with Windows |
67
+
68
+ ## What the PC can and can't do
69
+
70
+ The server gives this PC a key of its own when you link it. The key only lets the PC answer
71
+ the brain's tool calls and send model calls through your server, so no OpenAI key lives on
72
+ the PC. It can't send commands, approve actions or read the server's files. Remove the PC in
73
+ EDITH's HUD (Bodies, PCs) and its key stops working at once.
74
+
75
+ Windows only. Python 3.10 or newer.
edith_pc-0.3.0/PYPI.md ADDED
@@ -0,0 +1,39 @@
1
+ # EDITH for your PC
2
+
3
+ `edith-pc` makes a Windows PC a body of your **EDITH** server: the server's brain can use this
4
+ PC's tools (apps, windows, files, the screen, the keyboard, the shell) when you talk to EDITH
5
+ from your phone or the browser, from anywhere.
6
+
7
+ You need an EDITH server (the brain) with Google Authenticator sign-in set up. This package is
8
+ only the PC side.
9
+
10
+ ## Install and link
11
+
12
+ ```
13
+ pip install edith-pc
14
+ edith link
15
+ ```
16
+
17
+ `edith link` asks for your server's address and the 6-digit code Google Authenticator shows
18
+ for EDITH. That's all: no token or key to copy. The PC is linked, EDITH runs in the
19
+ background, and it starts with Windows from then on.
20
+
21
+ ## Commands
22
+
23
+ | Command | What it does |
24
+ | --- | --- |
25
+ | `edith status` | what this PC is linked to, and whether EDITH is running |
26
+ | `edith start` / `edith stop` | start or stop the background body |
27
+ | `edith app` | open the EDITH window |
28
+ | `edith run` | run the body in the terminal (Ctrl+C stops it) |
29
+ | `edith update` | install the newest version and restart |
30
+ | `edith unlink` | unlink this PC and stop starting with Windows |
31
+
32
+ ## What the PC can and can't do
33
+
34
+ The server gives this PC a key of its own when you link it. The key only lets the PC answer
35
+ the brain's tool calls and send model calls through your server, so no OpenAI key lives on
36
+ the PC. It can't send commands, approve actions or read the server's files. Remove the PC in
37
+ EDITH's HUD (Bodies, PCs) and its key stops working at once.
38
+
39
+ Windows only. Python 3.10 or newer.
@@ -0,0 +1,262 @@
1
+ # 🤖 EDITH — a real-time desktop voice assistant
2
+
3
+ > Talk to your computer like Tony Stark. A Python desktop assistant built on the **OpenAI Realtime API** (`gpt-realtime-2.1-mini` by default, `gpt-realtime-2.1` for demos) — a sleek, tactical AI that wakes to a hotword, holds a natural, continuous spoken conversation, and actually **does things**: delegates hard questions to a stronger reasoning model, autonomously drives your mouse and keyboard through multi-step tasks, plays Spotify, reads your email and documents aloud, runs your smart home, and controls your PC.
4
+
5
+ <!-- TODO: record a 30–60s demo and drop it here. A GIF (<10MB) renders inline on GitHub and beats a YouTube link. -->
6
+ <!-- ![EDITH demo](docs/demo.gif) -->
7
+
8
+ [![CI](https://github.com/sohanurislamshuvo/edith/actions/workflows/ci.yml/badge.svg)](https://github.com/sohanurislamshuvo/edith/actions/workflows/ci.yml)
9
+ ![Python](https://img.shields.io/badge/python-3.10+-blue)
10
+ ![Platform](https://img.shields.io/badge/platform-Windows-0078D6)
11
+ ![License](https://img.shields.io/badge/license-MIT-green)
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - 🎙️ **Speech-to-speech, real time** — OpenAI Realtime API over WebSocket, sub-second responses. **Barge-in** (interrupt it mid-sentence and it stops to listen) works with headphones; on laptop speakers it runs half-duplex by default so it can't hear itself (`EDITH_HALF_DUPLEX`).
18
+ - 🗣️ **Wake word + continuous conversation** — keyless, fully on-device hotword (default **"Edith"**) via Vosk — `pip install vosk` then `python scripts/setup_wake_word.py`, no account or cost (see [docs/WAKE_WORD.md](docs/WAKE_WORD.md)); after each reply she keeps listening, so follow-ups need no wake word until it idle-sleeps (`EDITH_IDLE_TIMEOUT_SECONDS`). Without setup she falls back to a Press-Enter prompt.
19
+ - 🧠 **Hybrid brain** — the voice model handles the conversation; genuinely hard questions (research, math, debugging, planning) are **delegated to a stronger reasoning model with live web access** and the answer is spoken back (`delegate_task`).
20
+ - 🖱️ **Computer autonomy** — *"Open Notepad and write a shopping list"* actually happens: a bounded **see → decide → act → verify** loop screenshots the screen, plans one action at a time, and drives the mouse/keyboard — confirm-gated, cost-capped, audited, with ESC and mouse-corner kill switches.
21
+ - 🧰 **70 function-calling tools** — it doesn't just talk, it acts:
22
+ - 🌐 Live **web search**, 📰 **news headlines**, 📈 **stock quotes**, 💱 **currency conversion** (all keyless)
23
+ - 🌦️ **Weather** (Open-Meteo, no API key) & 🕐 time/date
24
+ - ⏰ **Reminders & timers** (persistent, SQLite) with desktop notifications, plus a spoken **morning briefing** on the day's first wake
25
+ - 🧠 **Semantic long-term memory** — "remember my wifi password"; recall works by meaning, not exact words
26
+ - 👁️ **Screen vision & diagnosis** — *"What's wrong on my screen?"*; EDITH captures the screen, **analyzes** it, and reads out a **step-by-step fix plan** (then it can act with the mouse/keyboard).
27
+ - 🎵 **Spotify** play-by-name + now-playing, 🎬 YouTube
28
+ - 📧 **Gmail & Google Calendar** — unread summaries, send (confirm-gated), list/create events
29
+ - 🏠 **Home Assistant** — lights/switches/scenes by their spoken names
30
+ - 📄 **Document reading** — *"summarize the PDF on my desktop"* (PDF/txt/md/code)
31
+ - 📱 **Telegram push-to-phone** and **WhatsApp** (message prefilled, you press send)
32
+ - 📝 **Voice notes** — dictate to per-topic Markdown files and read them back
33
+ - 💻 **PC control**: open/close apps, system stats, volume, **screen brightness**, media keys, screenshots, **type & hotkeys & mouse**, **files/folders & clipboard**, **window & power** (lock/sleep/shutdown), and **shell commands** — with spoken confirmation required for anything destructive, plus an ambient **health watchdog** (battery/disk/CPU toasts).
34
+ - 💸 **Cost-guarded by design** — split-prefix prompt caching, trimmed tool outputs, a live token/$ budget counter that also meters every delegated model call, idle-close, and a hard auto-disconnect timer so a forgotten session can't drain your credit.
35
+ - 🖥️ **Desktop HUD ("Halo Reactor")** — a frameless, glass-panelled interface built around a real Gaussian-bloomed arc-reactor orb that breathes, pulses with your voice and crossfades colour by state, with a holographic ring assembly, speech ripples, a live transcript, a status readout and a running cost meter (`python -m edith.gui`).
36
+
37
+ ## 🏗️ Architecture
38
+
39
+ ```mermaid
40
+ flowchart LR
41
+ A[🎤 Mic] -->|PCM16 24kHz| B[audio_io]
42
+ W[👂 Wake word<br/>'Edith'] -->|gate| C
43
+ B -->|base64 frames| C[realtime_client<br/>WebSocket]
44
+ C <-->|speech-to-speech| D[(OpenAI Realtime API)]
45
+ D -->|function_call| E[tools/registry]
46
+ E --> F[web · weather · news · stocks · reminders<br/>delegate_task · computer agent · memory<br/>Spotify · Gmail/Calendar · Home Assistant<br/>screen vision · documents · pc_control]
47
+ F -->|trimmed JSON| C
48
+ D -->|audio deltas| B
49
+ B -->|cedar voice| G[🔊 Speaker]
50
+ C -.usage.-> H[session_manager<br/>budget guard + hard timer]
51
+ ```
52
+
53
+ **Why it's structured this way:** the transport (`realtime_client`), audio (`audio_io`), tools (`tools/`), and cost guardrails (`session_manager`) are decoupled, so any layer — model, voice, or a new tool — can be swapped without touching the others.
54
+
55
+ ## 🚀 Quickstart
56
+
57
+ ```powershell
58
+ # 1. Clone & enter
59
+ git clone <your-repo-url> edith ; cd edith
60
+
61
+ # 2. Create a virtual env and install
62
+ python -m venv .venv ; .\.venv\Scripts\Activate.ps1
63
+ pip install -r requirements.txt
64
+ pip install -r requirements-dev.txt # optional: pytest — then `python -m pytest`
65
+
66
+ # 3. Add your keys
67
+ Copy-Item .env.example .env # then edit .env: add your OPENAI_API_KEY
68
+
69
+ # 4. Sanity-check your mic & speakers (records 3s, plays it back)
70
+ python scripts/test_audio.py
71
+
72
+ # 5. Verify the tools work with ZERO API cost (no voice yet)
73
+ python scripts/test_tools_text.py
74
+
75
+ # 6. Talk to EDITH — pick one:
76
+ python -m edith.gui # Halo Reactor HUD: bloom orb + glass transcript + cost meter
77
+ python -m edith.gui_classic # the original HUD (guaranteed-working fallback)
78
+ python -m edith.main # terminal / CLI
79
+ ```
80
+
81
+ Say **"Edith"** (the default hotword, keyless via Vosk — run `python scripts/setup_wake_word.py` once to enable it), wait for the greeting, then try: *"What's my CPU and battery?"*, *"Play Bohemian Rhapsody."*, *"What's Apple stock at?"*, *"What's 100 dollars in taka?"*, *"Research the best budget mechanical keyboards for me."* (delegates to the reasoning model), or *"Open Notepad and write a shopping list."* (autonomous computer task — confirm it, hands off, ESC aborts). Follow-ups need no wake word; it goes back to sleep after ~30s of silence.
82
+
83
+ **Optional integrations** (each degrades gracefully when unconfigured — see `.env.example`): Spotify (2-minute free dev app), Telegram (BotFather token), Home Assistant (long-lived token), Gmail/Calendar (`python scripts/setup_google.py` after a one-time Google Cloud setup; Testing-mode tokens re-consent weekly).
84
+
85
+ ## 📱 Phone app (Android)
86
+
87
+ EDITH's second body. The Android app in [`android/`](android/) connects to the PC over Wi-Fi,
88
+ Tailscale or your own relay, and both directions work: control the PC from your phone, and let EDITH control the
89
+ phone — 47 tools in 11 access groups you can switch off one by one: apps, notifications
90
+ (WhatsApp/Telegram/Messenger/Gmail), the SMS inbox and call log, calls, contacts, calendar,
91
+ weather, location, media playback, brightness, ringer, torch, DND, files (both ways between
92
+ phone and PC), Home Assistant, and **any app's screen** through the accessibility service
93
+ (`phone_drive_screen`). She can **look** through the camera or at the screen, remembers facts
94
+ (synced with the PC's memory), runs your **routines**, delivers a **morning briefing** and
95
+ keeps an **activity log** of everything she does on the phone. Confirm-gated actions show up as
96
+ **Yes / No** cards. The app has its own brain (your OpenAI key, encrypted on the phone), so chat,
97
+ **Live** voice (Realtime API), the **"Edith" wake word** (on-device Vosk, like the PC), the
98
+ Quick Settings tile, widget and assistant gesture, phone tasks and **auto-reply** keep working
99
+ with the PC off; linking the PC adds its tools and its budget. Share anything to EDITH from any
100
+ app's share sheet. The app is a port of the desktop Halo Reactor HUD:
101
+
102
+ <p align="center">
103
+ <img src="docs/phone-hud-idle.png" width="220" alt="EDITH phone HUD standing by">
104
+ <img src="docs/phone-hud-working.png" width="220" alt="EDITH phone HUD working">
105
+ <img src="docs/phone-hud-approval.png" width="220" alt="EDITH phone HUD approval card">
106
+ </p>
107
+
108
+ ```powershell
109
+ pip install -r requirements.txt # adds fastapi + uvicorn
110
+ python -m edith.service # prints the address + pairing token for the app
111
+ ```
112
+
113
+ To have it running whenever the PC is on, register it once as a hidden logon task that
114
+ restarts itself after a crash (its output then goes to `~/.edith.log`; the token is in
115
+ `~/.edith_service_token`):
116
+
117
+ ```powershell
118
+ powershell -ExecutionPolicy Bypass -File scripts\install_autostart.ps1 -Start # -Status / -Uninstall
119
+ ```
120
+
121
+ Then build/install the app (see [android/README.md](android/README.md)), enter the address
122
+ and token, tap Connect. Never port-forward the service. From outside your Wi-Fi, either run
123
+ your own relay, a small server both ends connect out to, end-to-end encrypted with the
124
+ pairing token ([docs/RELAY.md](docs/RELAY.md)), or use Tailscale
125
+ ([android/README.md → Reach the PC from anywhere](android/README.md#reach-the-pc-from-anywhere-tailscale)).
126
+ Design notes: [docs/MOBILE.md](docs/MOBILE.md).
127
+
128
+ **Or run the brain on your own server** ([docs/SERVER.md](docs/SERVER.md)): `python -m edith.service` with
129
+ `EDITH_ROLE=server` on a small Linux VPS behind Caddy gives you EDITH at your own domain, a browser HUD (sign in with Google Authenticator),
130
+ the phone working with the PC off, and the OpenAI key living only on the server. The PC links to it as a
131
+ body (`python -m edith.body`), so its tools work from anywhere while it is on. Any other PC needs no code at
132
+ all: install **EDITH for Windows** (`EDITH-Setup.exe` from a `pc-v*` release), or with Python
133
+ `pip install edith-pc`, and link it with a six-digit code from
134
+ Google Authenticator (`edith link`).
135
+
136
+ ## ⚙️ Configuration
137
+
138
+ All config lives in `.env` (see `.env.example`):
139
+
140
+ | Key | Default | What it does |
141
+ |---|---|---|
142
+ | `OPENAI_API_KEY` | — | **Required.** OpenAI key. |
143
+ | `PICOVOICE_ACCESS_KEY` | — | Optional legacy wake-word engine (Vosk is the keyless default; Picovoice now needs a paid plan). |
144
+ | `EDITH_MODEL` | `gpt-realtime-2.1-mini` | `gpt-realtime-2.1` (best) / `gpt-realtime-2.1-mini` (cheap, for dev). The older `gpt-realtime-mini` shuts down 2027-01-20. |
145
+ | `EDITH_VOICE` | `marin` | Spoken voice (EDITH default; `cedar` for the old deep voice). |
146
+ | `EDITH_SESSION_MAX_SECONDS` | `300` | Hard auto-disconnect per session. |
147
+ | `EDITH_IDLE_TIMEOUT_SECONDS` | `30` | Continuous conversation: seconds of silence before going back to sleep (0 = off). |
148
+ | `EDITH_DAILY_BUDGET_USD` | `2.0` | Hard stop once a day's spend crosses this (includes delegated model calls). |
149
+ | `EDITH_HALF_DUPLEX` | `true` | Mutes the mic while EDITH speaks (stops it hearing itself on laptop speakers). Set `false` + use headphones for true barge-in. |
150
+ | `EDITH_DELEGATE_MODEL` | `gpt-6-sol` | The reasoning model behind `delegate_task` (`gpt-6-luna` is ~20x cheaper, `gpt-6-astra` the flagship). |
151
+ | `EDITH_VISION_MODEL` / `EDITH_DOC_MODEL` / `EDITH_SEARCH_MODEL` | `gpt-6-luna` | The cheap vision/text model behind `see_screen`, `read_document` and `web_search`. |
152
+ | `EDITH_AGENT_*` | see `.env.example` | Computer-autonomy model, step/time/cost caps, monitor. |
153
+ | `EDITH_BRIEFING*` | see `.env.example` | Morning briefing (on/off, city, news, nudge time). |
154
+ | `EDITH_SERVICE_*` / `EDITH_PHONE_DENYLIST` / `EDITH_INBOX_DIR` | see `.env.example` | Phone link: host/port/pairing token, extra protected phone apps, where phone uploads land (default `~/Downloads/EDITH`). |
155
+ | `EDITH_RELAY_URL` | — | Your own relay, e.g. `wss://relay.example.com/?key=…`: the phone reaches the PC from anywhere ([docs/RELAY.md](docs/RELAY.md)). |
156
+ | Integration keys | see `.env.example` | Spotify, Telegram, Home Assistant, Google, WhatsApp, health watch. |
157
+
158
+ ### 🖥️ Desktop HUD options
159
+
160
+ The redesigned HUD (`python -m edith.gui`) is **frameless** by default (custom title bar, drag-to-move) and auto-scales to fit your screen. Tweak it with env vars:
161
+
162
+ | Env var | Default | What it does |
163
+ |---|---|---|
164
+ | `EDITH_NATIVE_FRAME` | `0` | Set `1` to keep the standard OS window (title bar, snap, minimize) instead of the frameless HUD. |
165
+ | `EDITH_TOPMOST` | `0` | Set `1` to float the HUD above other windows (desktop-widget mode). |
166
+
167
+ - **Fonts:** the wordmark/labels use bundled **Orbitron** + **Rajdhani** (SIL OFL, in `assets/fonts/`), registered at runtime — no system install needed. If they're missing, the HUD falls back to **Bahnschrift** / **Consolas** and still looks intentional.
168
+ - **Escape hatch:** if the new HUD ever misbehaves on a machine, `python -m edith.gui_classic` runs the original UI (same brain, identical behaviour).
169
+
170
+ ## 💸 Cost notes ($70 of credit goes a long way)
171
+
172
+ Realtime is billed by **audio tokens**, not flat per-minute. With prompt caching + trimmed tool outputs, expect **~$0.05–0.10/min → roughly 12–23 hours** of live conversation on $70. The built-in guardrails (`session_manager.py`) keep you there:
173
+
174
+ - The **system prompt + tool schemas are byte-stable** → prompt cache hits cut audio-input cost ~80%.
175
+ - Tool results are **trimmed** to only what the model needs before being sent back.
176
+ - A **live budget counter** reads each response's usage and hard-stops at `EDITH_DAILY_BUDGET_USD`.
177
+ - A **hard session timer** force-closes the socket (OpenAI does *not* auto-kill sessions).
178
+ - Develop against `gpt-realtime-2.1-mini`; switch to `gpt-realtime-2.1` only to record demos.
179
+ - Every model a tool can call has a row in `pricing.py`; an unknown id is metered at a mid-priced fallback.
180
+
181
+ > 🛡️ Backstop: also set a hard monthly spend limit in your OpenAI billing dashboard.
182
+
183
+ ## 📁 Project layout
184
+
185
+ ```
186
+ edith/
187
+ main.py entry: wake-word loop -> realtime session
188
+ config.py loads .env
189
+ legacy_migration.py one-time rename of pre-rebrand ~/.jarvis_* state files
190
+ prompts.py STABLE EDITH personality (kept byte-identical for caching)
191
+ pricing.py per-model token prices for the budget counter
192
+ realtime_client.py WebSocket transport, event loop, function-call dispatch
193
+ audio_io.py mic in / speaker out, barge-in flush, RMS for the orb
194
+ wake_word.py Vosk "Edith" listener (legacy Porcupine fallback)
195
+ session_manager.py hard timer + live token/$ budget guard
196
+ gui.py desktop HUD ("Halo Reactor"): bloom orb + glass UI + transcript + cost
197
+ gui_classic.py original HUD, kept as a guaranteed-working fallback
198
+ hud/ HUD render helpers: easing, PIL orb_renderer, textures, font loader
199
+ tools/
200
+ registry.py tool JSON schemas (cached prefix) + name->function dispatch
201
+ clock.py current time / date
202
+ weather.py Open-Meteo current conditions
203
+ web_search.py OpenAI web_search tool + DuckDuckGo fallback
204
+ delegate.py delegate_task: hard questions -> stronger reasoning model
205
+ computer_agent.py execute_computer_task: see->act->verify autonomy loop
206
+ memory.py semantic long-term memory (embeddings + cosine recall)
207
+ briefing.py morning briefing (weather + reminders + headlines)
208
+ news_finance.py news / stock quotes / currency (keyless APIs)
209
+ spotify_music.py play-by-name + now-playing
210
+ google_workspace.py Gmail + Calendar (one-time OAuth via scripts/setup_google.py)
211
+ home_assistant.py lights/switches/scenes by spoken name
212
+ documents.py read/summarize PDFs & text files aloud
213
+ notes.py dictated voice notes (per-topic Markdown)
214
+ telegram_notify.py push text to the user's phone
215
+ whatsapp.py prefilled WhatsApp messages (human presses send)
216
+ health_watch.py ambient battery/disk/CPU toasts
217
+ external_cost.py meters tool API spend into the session budget
218
+ reminders.py APScheduler reminders/timers + desktop notifications
219
+ system_control.py apps, volume, brightness, media keys, screenshots, YouTube
220
+ vision.py see_screen: capture + analyze + diagnose
221
+ pc_control.py keyboard/mouse, files/clipboard, windows, power, shell
222
+ window_control.py list/focus/minimize/maximize/close/snap windows by name
223
+ file_ops.py list, copy/move/rename, Recycle-Bin delete, zip/unzip
224
+ task_manager.py heaviest processes, kill by name (protected list)
225
+ system_toggles.py mic mute, monitors off, dark mode, recycle bin, network, wallpaper
226
+ llm.py shared Responses-API call helper (explicit reasoning effort)
227
+ service/ phone-app link (python -m edith.service): FastAPI + WebSocket,
228
+ text brain over ALL tools, approvals, phone_drive_screen agent
229
+ .github/workflows/ CI (tests on Windows + Linux, Android unit tests + debug APK), deploy of the
230
+ server brain on every green master, signed APK on every v* tag (docs/SERVER.md)
231
+ relay/ reach the PC from anywhere: the relay server (python -m edith.relay),
232
+ end-to-end encryption keyed by the pairing token, the PC-side bridge
233
+ web/static/ the browser HUD served by edith.service at / (chat, bodies, activity, approvals)
234
+ android/ the Android app (Kotlin): 47 phone tools, its own brain (works with the PC off),
235
+ live voice, wake word, accessibility agent, vision, auto-reply, PC link
236
+ scripts/ test_audio.py, test_tools_text.py, setup_google.py, install_autostart.ps1
237
+ ```
238
+
239
+ ## 🗺️ Roadmap
240
+
241
+ - [x] Persistent reminders across restarts (SQLite jobstore)
242
+ - [x] Conversation memory / long-term notes (semantic memory + voice notes)
243
+ - [x] Smart-home (Home Assistant) tool
244
+ - [x] Hybrid reasoning (delegate to a stronger model)
245
+ - [x] Autonomous computer use (see → act → verify)
246
+ - [ ] Cross-platform (macOS/Linux) PC-control backends
247
+ - [ ] Telegram remote-control bridge (text commands to EDITH from your phone)
248
+ - [x] Android app v0.1: full phone control + PC remote, one brain two bodies ([android/](android/), [docs/MOBILE.md](docs/MOBILE.md))
249
+ - [x] Android app v0.2: phone-local brain (API key on the phone), auto-reply through notifications, hands-free follow-ups ([android/README.md](android/README.md#works-with-the-pc-off))
250
+ - [x] Android app v0.3: live voice (Realtime API), "Edith" wake word, tile / widget / assistant / headset entry points, camera and screen vision, SMS inbox and call log, media and device controls, Home Assistant, memory sync, routines, morning briefing, share sheet, file transfer, access switches and an activity log ([android/README.md](android/README.md#what-edith-can-do-on-the-phone))
251
+ - [x] Android app v0.4: reach the PC from anywhere through your own end-to-end-encrypted relay ([docs/RELAY.md](docs/RELAY.md))
252
+ - [x] Brain on your own server with a browser HUD and Google Authenticator sign-in; the PC links to it as a body, so the phone reaches PC tools from anywhere, with real approvals ([docs/SERVER.md](docs/SERVER.md))
253
+ - [ ] Sync of notes, reminders and routines across server, PC and phone; optional fallback key per body
254
+ - [ ] Phone app: GPT-Live / WebRTC transport, on-phone document reading
255
+
256
+ ## 📝 License
257
+
258
+ MIT — see [LICENSE](LICENSE).
259
+
260
+ ---
261
+
262
+ *Built with the OpenAI Realtime API. Not affiliated with Marvel.* 🦾
@@ -0,0 +1,13 @@
1
+ """EDITH — a real-time desktop voice assistant built on the OpenAI Realtime API."""
2
+
3
+ import sys as _sys
4
+
5
+ # EDITH prints emoji/box-drawing status lines. On Windows those crash with
6
+ # UnicodeEncodeError when stdout is a non-UTF-8 pipe; force UTF-8 with replacement.
7
+ for _stream in (_sys.stdout, _sys.stderr):
8
+ try:
9
+ _stream.reconfigure(encoding="utf-8", errors="replace")
10
+ except Exception:
11
+ pass
12
+
13
+ __version__ = "0.3.0"
@@ -0,0 +1,130 @@
1
+ """Microphone capture and speaker playback for the Realtime stream.
2
+
3
+ Design notes:
4
+ - The Realtime API speaks PCM16 / 24 kHz / mono. We capture and play in exactly
5
+ that format so there is no resampling.
6
+ - Capture runs in a sounddevice callback (a separate thread); we hand frames to the
7
+ asyncio loop via ``call_soon_threadsafe`` so the event loop is never blocked.
8
+ - Playback uses a callback that drains an internal byte buffer. ``flush_playback()``
9
+ clears that buffer instantly — this is how barge-in works: when the user starts
10
+ talking, we drop whatever EDITH was about to say.
11
+ - ``input_level`` / ``output_level`` (0..1 RMS) drive the GUI orb.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import threading
17
+
18
+ import numpy as np
19
+ import sounddevice as sd
20
+
21
+ from .config import BLOCK_FRAMES, CHANNELS, SAMPLE_RATE, SAMPLE_WIDTH
22
+
23
+
24
+ def _rms(pcm: bytes) -> float:
25
+ samples = np.frombuffer(pcm, dtype=np.int16)
26
+ if samples.size == 0:
27
+ return 0.0
28
+ return float(np.sqrt(np.mean((samples.astype(np.float32) / 32768.0) ** 2)))
29
+
30
+
31
+ def _rms_arr(samples: np.ndarray) -> float:
32
+ if samples.size == 0:
33
+ return 0.0
34
+ return float(np.sqrt(np.mean((samples.astype(np.float32) / 32768.0) ** 2)))
35
+
36
+
37
+ class AudioIO:
38
+ def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
39
+ self._loop = loop
40
+ self.mic_queue: asyncio.Queue[bytes] = asyncio.Queue()
41
+ self._in_stream: sd.RawInputStream | None = None
42
+ self._out_stream: sd.RawOutputStream | None = None
43
+ self._play_buf = bytearray()
44
+ self._play_lock = threading.Lock()
45
+ self.muted = False
46
+ self.input_level = 0.0
47
+ self.output_level = 0.0
48
+ # latest int16 frames, kept for the GUI's live spectrum (read-only from GUI thread)
49
+ self._last_in: np.ndarray | None = None
50
+ self._last_out: np.ndarray | None = None
51
+
52
+ # --- microphone (input) ---------------------------------------------------
53
+ def _on_mic(self, indata, frames, time_info, status) -> None: # noqa: ANN001
54
+ data = bytes(indata)
55
+ samples = np.frombuffer(data, dtype=np.int16)
56
+ self._last_in = samples
57
+ self.input_level = _rms_arr(samples)
58
+ if self.muted:
59
+ return
60
+ try:
61
+ self._loop.call_soon_threadsafe(self.mic_queue.put_nowait, data)
62
+ except RuntimeError:
63
+ pass # event loop already closed
64
+
65
+ # --- speaker (output) -----------------------------------------------------
66
+ def _on_play(self, outdata, frames, time_info, status) -> None: # noqa: ANN001
67
+ needed = frames * CHANNELS * SAMPLE_WIDTH
68
+ with self._play_lock:
69
+ chunk = bytes(self._play_buf[:needed])
70
+ del self._play_buf[:needed]
71
+ if len(chunk) < needed: # underrun -> pad with silence
72
+ chunk += b"\x00" * (needed - len(chunk))
73
+ samples = np.frombuffer(chunk, dtype=np.int16)
74
+ self._last_out = samples
75
+ self.output_level = _rms_arr(samples)
76
+ outdata[:] = chunk
77
+
78
+ # --- live spectrum (for the GUI HUD) --------------------------------------
79
+ def latest_spectrum(self, bands: int = 24) -> np.ndarray:
80
+ """Peak-normalized voice-band FFT magnitudes (0..1) of the most active stream."""
81
+ src = self._last_out if self.output_level >= self.input_level else self._last_in
82
+ if src is None or src.size == 0:
83
+ return np.zeros(bands, dtype=np.float32)
84
+ x = src.astype(np.float32) / 32768.0
85
+ windowed = x * np.hanning(x.size)
86
+ mag = np.abs(np.fft.rfft(windowed))
87
+ usable = mag[: max(1, mag.size // 2)] # lower half ~ voice energy
88
+ if usable.size < bands:
89
+ usable = np.pad(usable, (0, bands - usable.size))
90
+ grouped = np.array([float(g.mean()) for g in np.array_split(usable, bands)], dtype=np.float32)
91
+ peak = grouped.max()
92
+ return grouped / peak if peak > 0 else grouped
93
+
94
+ def play(self, pcm: bytes) -> None:
95
+ """Queue assistant audio for playback."""
96
+ with self._play_lock:
97
+ self._play_buf.extend(pcm)
98
+
99
+ def flush_playback(self) -> None:
100
+ """Drop all pending playback — called on barge-in to interrupt EDITH."""
101
+ with self._play_lock:
102
+ self._play_buf.clear()
103
+ self.output_level = 0.0
104
+
105
+ def is_speaking(self) -> bool:
106
+ with self._play_lock:
107
+ return len(self._play_buf) > 0
108
+
109
+ # --- lifecycle ------------------------------------------------------------
110
+ def start(self) -> None:
111
+ self._out_stream = sd.RawOutputStream(
112
+ samplerate=SAMPLE_RATE, channels=CHANNELS, dtype="int16",
113
+ blocksize=BLOCK_FRAMES, callback=self._on_play,
114
+ )
115
+ self._in_stream = sd.RawInputStream(
116
+ samplerate=SAMPLE_RATE, channels=CHANNELS, dtype="int16",
117
+ blocksize=BLOCK_FRAMES, callback=self._on_mic,
118
+ )
119
+ self._out_stream.start()
120
+ self._in_stream.start()
121
+
122
+ def stop(self) -> None:
123
+ for stream in (self._in_stream, self._out_stream):
124
+ if stream is not None:
125
+ try:
126
+ stream.stop()
127
+ stream.close()
128
+ except Exception:
129
+ pass
130
+ self._in_stream = self._out_stream = None