zoraai 1.0.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.
zoraai-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: zoraai
3
+ Version: 1.0.0
4
+ Summary: Zora AI - Autonomous Voice & Everyday Work Assistant
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: livekit-agents>=1.6.10
8
+ Requires-Dist: livekit-plugins-google>=1.6.10
9
+ Requires-Dist: livekit-plugins-noise-cancellation>=0.3.0
10
+ Requires-Dist: google-genai>=2.18.0
11
+ Requires-Dist: python-dotenv
12
+ Requires-Dist: yt-dlp
13
+ Requires-Dist: pygame
14
+ Requires-Dist: imageio-ffmpeg
15
+ Requires-Dist: requests
16
+ Requires-Dist: PyAutoGUI
17
+ Requires-Dist: pynput
18
+ Requires-Dist: duckduckgo-search
19
+ Requires-Dist: fuzzywuzzy
20
+ Requires-Dist: python-Levenshtein
21
+ Requires-Dist: pywin32
22
+ Requires-Dist: mss
23
+ Requires-Dist: pillow
24
+ Requires-Dist: langchain
25
+ Requires-Dist: langchain-google-genai
26
+ Requires-Dist: langchain-openai
27
+ Requires-Dist: google-api-python-client
28
+ Requires-Dist: google-auth-httplib2
29
+ Requires-Dist: google-auth-oauthlib
30
+ Requires-Dist: playwright
31
+ Requires-Dist: beautifulsoup4
32
+
33
+ # Zora AI — Autonomous Voice & Everyday Assistant
34
+
35
+ Developed by **Neon**.
36
+
37
+ Zora AI is an intelligent, voice-first autonomous desktop companion designed for daily assistance, system automation, and personalized conversational interactions.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install zoraai
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ Start Zora in background:
48
+ ```bash
49
+ zora start
50
+ ```
51
+
52
+ Stop Zora:
53
+ ```bash
54
+ zora stop
55
+ ```
56
+
57
+ Check status:
58
+ ```bash
59
+ zora status
60
+ ```
61
+
62
+ Test directly with PC microphone and speakers:
63
+ ```bash
64
+ zora console
65
+ ```
66
+
67
+ Auto-update to the latest version:
68
+ ```bash
69
+ zora update
70
+ ```
71
+
72
+ ## Creator
73
+ Created and engineered by **Neon**.
zoraai-1.0.0/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Zora AI — Autonomous Voice & Everyday Assistant
2
+
3
+ Developed by **Neon**.
4
+
5
+ Zora AI is an intelligent, voice-first autonomous desktop companion designed for daily assistance, system automation, and personalized conversational interactions.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install zoraai
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ Start Zora in background:
16
+ ```bash
17
+ zora start
18
+ ```
19
+
20
+ Stop Zora:
21
+ ```bash
22
+ zora stop
23
+ ```
24
+
25
+ Check status:
26
+ ```bash
27
+ zora status
28
+ ```
29
+
30
+ Test directly with PC microphone and speakers:
31
+ ```bash
32
+ zora console
33
+ ```
34
+
35
+ Auto-update to the latest version:
36
+ ```bash
37
+ zora update
38
+ ```
39
+
40
+ ## Creator
41
+ Created and engineered by **Neon**.
zoraai-1.0.0/agent.py ADDED
@@ -0,0 +1,437 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ from pathlib import Path
5
+
6
+ # ── Load saved config FIRST (before any livekit imports that need env vars) ──
7
+ _CONFIG_DIR = Path.home() / ".zoraai"
8
+ _CONFIG_FILE = _CONFIG_DIR / "config.json"
9
+
10
+ def _load_config_into_env():
11
+ """Load API keys from ~/.zoraai/config.json into os.environ."""
12
+ if _CONFIG_FILE.exists():
13
+ try:
14
+ cfg = json.loads(_CONFIG_FILE.read_text(encoding="utf-8"))
15
+ for k, v in cfg.items():
16
+ if v and not os.environ.get(k):
17
+ os.environ[k] = v
18
+ except Exception:
19
+ pass
20
+
21
+ def _save_config(cfg: dict):
22
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
23
+ _CONFIG_FILE.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
24
+
25
+ def _first_time_setup() -> bool:
26
+ """
27
+ Runs interactively on first install.
28
+ Asks for API keys and saves them to ~/.zoraai/config.json.
29
+ Returns True if setup completed successfully.
30
+ """
31
+ print()
32
+ print("=" * 60)
33
+ print(" ZORA AI — FIRST TIME SETUP")
34
+ print("=" * 60)
35
+ print(" This only runs ONCE. Your keys are saved securely.")
36
+ print(" After this, just type 'zora' to start anytime.")
37
+ print("=" * 60)
38
+ print()
39
+ print(" You need:")
40
+ print(" 1. A LiveKit Cloud account (free): https://cloud.livekit.io")
41
+ print(" 2. A Google Gemini API key: https://aistudio.google.com/apikey")
42
+ print()
43
+
44
+ try:
45
+ livekit_url = input(" LiveKit URL (wss://your-project.livekit.cloud) > ").strip()
46
+ if not livekit_url:
47
+ print(" [ERROR] LiveKit URL cannot be empty.")
48
+ return False
49
+
50
+ livekit_api_key = input(" LiveKit API Key (APIxxxx...) > ").strip()
51
+ if not livekit_api_key:
52
+ print(" [ERROR] LiveKit API Key cannot be empty.")
53
+ return False
54
+
55
+ livekit_api_secret = input(" LiveKit Secret (long string...) > ").strip()
56
+ if not livekit_api_secret:
57
+ print(" [ERROR] LiveKit Secret cannot be empty.")
58
+ return False
59
+
60
+ google_api_key = input(" Google API Key (AIzaSy...) > ").strip()
61
+ if not google_api_key:
62
+ print(" [ERROR] Google API Key cannot be empty.")
63
+ return False
64
+
65
+ except (KeyboardInterrupt, EOFError):
66
+ print("\n\n Setup cancelled.")
67
+ sys.exit(0)
68
+
69
+ cfg = {
70
+ "LIVEKIT_URL": livekit_url,
71
+ "LIVEKIT_API_KEY": livekit_api_key,
72
+ "LIVEKIT_API_SECRET": livekit_api_secret,
73
+ "GOOGLE_API_KEY": google_api_key,
74
+ }
75
+ _save_config(cfg)
76
+
77
+ # Load into current process env immediately
78
+ for k, v in cfg.items():
79
+ os.environ[k] = v
80
+
81
+ print()
82
+ print(" [OK] Setup complete! Keys saved to:", str(_CONFIG_FILE))
83
+ print(" Starting Zora AI...")
84
+ print()
85
+ return True
86
+
87
+ def _check_setup_needed() -> bool:
88
+ """Returns True if we need to run first-time setup."""
89
+ _load_config_into_env()
90
+ required = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET", "GOOGLE_API_KEY"]
91
+ return any(not os.environ.get(k) for k in required)
92
+
93
+ # Load keys before importing livekit (it reads env at import time)
94
+ _load_config_into_env()
95
+
96
+ # ── Now import everything ──────────────────────────────────────────────────
97
+ import asyncio
98
+ import mss
99
+ from livekit import rtc, agents
100
+ from livekit.agents import AgentSession, Agent, ChatContext, ChatMessage
101
+ from livekit.plugins import google
102
+
103
+ from nion_prompts import instructions_prompt
104
+ from nion_google_search import google_search, get_current_datetime
105
+ from nion_get_whether import get_weather
106
+ from nion_window_CTRL import (
107
+ open_app, close_app, folder_file,
108
+ send_whatsapp_message, google_maps_tool, gmail_send_tool, restart_mj,
109
+ shutdown_pc, restart_pc, lock_pc, cancel_shutdown
110
+ )
111
+ from nion_file_opner import Play_file
112
+ from nion_music_player import play_music, stop_music
113
+ from nion_keyboard_mouse_CTRL import (
114
+ move_cursor_tool, mouse_click_tool, scroll_cursor_tool,
115
+ type_text_tool, press_key_tool, swipe_gesture_tool,
116
+ press_hotkey_tool, control_volume_tool
117
+ )
118
+ from nion_memory_loop import MemoryExtractor
119
+ from nion_vision import see_screen
120
+ from nion_gmail_reader import gmail_search_tool
121
+ from nion_web_creator import create_basic_website
122
+ from nion_minecraft_setup import setup_minecraft_server
123
+ from nion_memory_store import ConversationMemory
124
+ from nion_scheduler import set_reminder_tool, get_reminders_tool, background_reminder_loop
125
+ from nion_browser_controller import open_isolated_browser, search_in_browser
126
+ from nion_social_messenger import send_whatsapp_web, send_messenger_message, send_facebook_post
127
+ from nion_continuous_vision import get_live_screen_context, background_screen_monitor
128
+ from nion_emotion_engine import adapt_voice_emotion
129
+
130
+
131
+ class Assistant:
132
+ def __init__(self, chat_ctx) -> None:
133
+ self.model = google.beta.realtime.RealtimeModel(
134
+ model="gemini-3.1-flash-live-preview", voice="Kore"
135
+ )
136
+ self.agent = Agent(
137
+ instructions=instructions_prompt,
138
+ chat_ctx=chat_ctx,
139
+ tools=[
140
+ google_search, get_current_datetime, get_weather, open_app,
141
+ close_app, folder_file, Play_file, play_music, stop_music,
142
+ move_cursor_tool, mouse_click_tool, scroll_cursor_tool,
143
+ type_text_tool, press_key_tool, swipe_gesture_tool,
144
+ press_hotkey_tool, control_volume_tool, see_screen,
145
+ send_whatsapp_message, google_maps_tool, gmail_send_tool,
146
+ gmail_search_tool, create_basic_website, setup_minecraft_server,
147
+ restart_mj, shutdown_pc, restart_pc, lock_pc, cancel_shutdown,
148
+ set_reminder_tool, get_reminders_tool, open_isolated_browser, search_in_browser,
149
+ send_whatsapp_web, send_messenger_message, send_facebook_post,
150
+ get_live_screen_context, adapt_voice_emotion
151
+ ]
152
+ )
153
+
154
+
155
+ async def entrypoint(ctx: agents.JobContext):
156
+ await ctx.connect()
157
+
158
+ # Initialize persistent memory and load past conversations
159
+ memory = ConversationMemory(user_id="Neon")
160
+ past_messages = memory.get_recent_context(max_messages=20)
161
+
162
+ chat_ctx = ChatContext()
163
+ message_count = 0
164
+ for msg in past_messages:
165
+ role = msg.get("role", "user")
166
+ content = msg.get("content", "")
167
+ if role == "user":
168
+ chat_ctx.append(message=ChatMessage(role="user", text=content))
169
+ message_count += 1
170
+ elif role == "assistant":
171
+ chat_ctx.append(message=ChatMessage(role="assistant", text=content))
172
+ message_count += 1
173
+
174
+ print(f"[Zora] Loaded {message_count} messages from past conversation.")
175
+
176
+ async def manage_session():
177
+ import time
178
+ while True:
179
+ try:
180
+ zora_agent = Assistant(chat_ctx=chat_ctx)
181
+ session = AgentSession(llm=zora_agent.model)
182
+
183
+ session_alive = [True]
184
+ start_time = time.time()
185
+
186
+ @session.on("error")
187
+ def on_session_error(*args, **kwargs):
188
+ print("AgentSession error detected. Flagging for restart...")
189
+ session_alive[0] = False
190
+
191
+ await session.start(room=ctx.room, agent=zora_agent.agent)
192
+
193
+ # Speak startup greeting on first connect
194
+ try:
195
+ from nion_prompts import get_startup_greeting
196
+ greeting = get_startup_greeting()
197
+ session.generate_reply(
198
+ user_input=f"[System Instructions: Greet Boss right now with this startup greeting: {greeting}]"
199
+ )
200
+ except Exception as greet_err:
201
+ print(f"[Zora] Greeting error: {greet_err}")
202
+ print("Zora session is LIVE and listening!")
203
+
204
+ while True:
205
+ await asyncio.sleep(1)
206
+ time_since_start = time.time() - start_time
207
+
208
+ if not session_alive[0]:
209
+ print("Error detected. Restarting session...")
210
+ break
211
+
212
+ if ctx.room.connection_state == rtc.ConnectionState.CONN_DISCONNECTED:
213
+ print("Room disconnected. Restarting session...")
214
+ break
215
+
216
+ try:
217
+ await session.aclose()
218
+ except Exception:
219
+ pass
220
+
221
+ await asyncio.sleep(2)
222
+ print("Restarting Zora session now...")
223
+
224
+ except Exception as e:
225
+ print(f"Session error: {e}")
226
+ await asyncio.sleep(3)
227
+
228
+ asyncio.create_task(manage_session())
229
+ conv_ctx = MemoryExtractor()
230
+ asyncio.create_task(conv_ctx.run(chat_ctx))
231
+ asyncio.create_task(background_reminder_loop())
232
+ asyncio.create_task(background_screen_monitor())
233
+
234
+ # Keep the entrypoint alive
235
+ while True:
236
+ await asyncio.sleep(60)
237
+
238
+
239
+ # ── PID / background helpers ──────────────────────────────────────────────
240
+ _PID_FILE = _CONFIG_DIR / "zora.pid"
241
+ _LOG_FILE = _CONFIG_DIR / "zora.log"
242
+
243
+ def _get_running_pid():
244
+ if not _PID_FILE.exists():
245
+ return None
246
+ try:
247
+ pid = int(_PID_FILE.read_text().strip())
248
+ import psutil
249
+ if psutil.pid_exists(pid):
250
+ p = psutil.Process(pid)
251
+ if p.status() != "zombie":
252
+ return pid
253
+ except Exception:
254
+ pass
255
+ try:
256
+ _PID_FILE.unlink()
257
+ except Exception:
258
+ pass
259
+ return None
260
+
261
+ def _do_stop():
262
+ pid = _get_running_pid()
263
+ if not pid:
264
+ print("\n [Zora] Not running in background.\n")
265
+ return
266
+ try:
267
+ import psutil
268
+ psutil.Process(pid).terminate()
269
+ except Exception:
270
+ try:
271
+ import signal
272
+ os.kill(pid, signal.SIGTERM)
273
+ except Exception:
274
+ pass
275
+ try:
276
+ _PID_FILE.unlink()
277
+ except Exception:
278
+ pass
279
+ print(f"\n [Zora] Stopped (PID {pid}).\n")
280
+
281
+ def _do_status():
282
+ pid = _get_running_pid()
283
+ print()
284
+ if pid:
285
+ print(f" [Zora] RUNNING in background (PID {pid})")
286
+ print(f" [Zora] Log: {_LOG_FILE}")
287
+ print(f" [Zora] Run 'zora stop' to stop it.")
288
+ else:
289
+ print(" [Zora] NOT running.")
290
+ print(" [Zora] Run 'zora start' to start in background.")
291
+ print(" [Zora] Run 'zora console' to test with PC mic & speakers.")
292
+ print(" [Zora] Run 'zora update' to update Zora to the latest version.")
293
+ print()
294
+
295
+ def _do_start_background():
296
+ pid = _get_running_pid()
297
+ if pid:
298
+ print(f"\n [Zora] Already running (PID {pid}). Use 'zora stop' first.\n")
299
+ return
300
+
301
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
302
+ log_h = open(_LOG_FILE, "a", encoding="utf-8")
303
+
304
+ import subprocess
305
+ si = subprocess.STARTUPINFO()
306
+ si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
307
+ si.wShowWindow = 0 # SW_HIDE: runs completely invisible
308
+
309
+ script = (
310
+ "import os,json,sys;"
311
+ "from pathlib import Path;"
312
+ "_c=Path.home()/'.zoraai'/'config.json';"
313
+ "[os.environ.update(json.loads(_c.read_text())) for _ in [1] if _c.exists()];"
314
+ "sys.argv=['zora','console'];"
315
+ "from agent import cli_main;"
316
+ "cli_main()"
317
+ )
318
+
319
+ proc = subprocess.Popen(
320
+ [sys.executable, "-u", "-c", script],
321
+ startupinfo=si,
322
+ creationflags=subprocess.CREATE_NEW_CONSOLE,
323
+ stdout=log_h, stderr=log_h, close_fds=True,
324
+ )
325
+ _PID_FILE.write_text(str(proc.pid))
326
+
327
+ print()
328
+ print(" +--------------------------------------+")
329
+ print(" | ZORA AI -- Started in Background |")
330
+ print(f" | PID : {str(proc.pid):<29}|")
331
+ print(" | Audio: PC Mic & Speakers (Active) |")
332
+ print(f" | Log : {str(_LOG_FILE)[:29]:<29}|")
333
+ print(" +--------------------------------------+")
334
+ print(" | You can close CMD -- Zora is active |")
335
+ print(" | Type 'zora stop' anytime to stop her|")
336
+ print(" +--------------------------------------+")
337
+ print()
338
+
339
+
340
+ def _do_update():
341
+ print()
342
+ print("=" * 60)
343
+ print(" ZORA AI — AUTO UPDATER")
344
+ print("=" * 60)
345
+ print(" Checking for updates from creator (Neon)...")
346
+
347
+ # 1. Stop background Zora if running so files aren't locked
348
+ pid = _get_running_pid()
349
+ if pid:
350
+ print(" Stopping background Zora before updating...")
351
+ _do_stop()
352
+
353
+ import subprocess
354
+ print(" Downloading and applying latest update...")
355
+
356
+ # Run pip upgrade for zoraai
357
+ cmd = [sys.executable, "-m", "pip", "install", "--upgrade", "zoraai"]
358
+ try:
359
+ proc = subprocess.run(cmd, capture_output=True, text=True)
360
+ if proc.returncode == 0:
361
+ print()
362
+ print(" +--------------------------------------------------+")
363
+ print(" | [SUCCESS] Zora AI updated to latest version! |")
364
+ print(" | Your license and config were safely preserved! |")
365
+ print(" +--------------------------------------------------+")
366
+ print(" Type 'zora start' to start her up! ❤️")
367
+ print()
368
+ else:
369
+ print(f"\n [ERROR] Update failed: {proc.stderr or proc.stdout}")
370
+ except Exception as e:
371
+ print(f"\n [ERROR] Update error: {e}")
372
+
373
+
374
+ def cli_main():
375
+ """
376
+ zora console - test directly with PC mic & speakers (Zora speaks immediately!)
377
+ zora - start in background (PC mic & speakers)
378
+ zora start - start in background, can close CMD
379
+ zora stop - stop background Zora
380
+ zora status - check if Zora is running
381
+ zora update - update Zora to the latest version
382
+ zora dev - start in foreground with visible logs
383
+ """
384
+ cmd = sys.argv[1] if len(sys.argv) > 1 else "start"
385
+
386
+ # stop / status / update don't need initial auth prompt
387
+ if cmd == "stop":
388
+ _do_stop()
389
+ return
390
+ if cmd == "status":
391
+ _do_status()
392
+ return
393
+ if cmd == "update":
394
+ _do_update()
395
+ return
396
+
397
+ # All other commands need setup + auth
398
+ if _check_setup_needed():
399
+ ok = _first_time_setup()
400
+ if not ok:
401
+ sys.exit(1)
402
+
403
+ try:
404
+ from nion_auth import verify_or_prompt_user
405
+ verify_or_prompt_user()
406
+ except Exception as e:
407
+ print(f"[Auth] {e}")
408
+
409
+ if cmd == "start":
410
+ _do_start_background()
411
+
412
+ elif cmd == "dev":
413
+ print()
414
+ print("=" * 60)
415
+ print(" ZORA AI -- Dev Worker Mode (Ctrl+C to stop)")
416
+ print(" Waiting for room connection from browser or phone...")
417
+ print("=" * 60)
418
+ print()
419
+ sys.argv = ["zora", "start"]
420
+ agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
421
+
422
+ elif cmd == "console":
423
+ print()
424
+ print("=" * 60)
425
+ print(" ZORA AI -- Console Test Mode (PC Mic & Speakers)")
426
+ print(" Speak into your microphone. Press Ctrl+C to stop.")
427
+ print("=" * 60)
428
+ print()
429
+ agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
430
+
431
+ else:
432
+ # fallback: pass to livekit CLI
433
+ agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
434
+
435
+
436
+ if __name__ == "__main__":
437
+ cli_main()