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 +73 -0
- zoraai-1.0.0/README.md +41 -0
- zoraai-1.0.0/agent.py +437 -0
- zoraai-1.0.0/nion_auth.py +195 -0
- zoraai-1.0.0/nion_browser_controller.py +84 -0
- zoraai-1.0.0/nion_connect_mj_on_phone.py +46 -0
- zoraai-1.0.0/nion_continuous_vision.py +65 -0
- zoraai-1.0.0/nion_emotion_engine.py +53 -0
- zoraai-1.0.0/nion_file_opner.py +102 -0
- zoraai-1.0.0/nion_filesystem_agent.py +191 -0
- zoraai-1.0.0/nion_get_whether.py +79 -0
- zoraai-1.0.0/nion_gmail_reader.py +114 -0
- zoraai-1.0.0/nion_google_search.py +73 -0
- zoraai-1.0.0/nion_keyboard_mouse_CTRL.py +317 -0
- zoraai-1.0.0/nion_memory_loop.py +83 -0
- zoraai-1.0.0/nion_memory_store.py +152 -0
- zoraai-1.0.0/nion_minecraft_setup.py +88 -0
- zoraai-1.0.0/nion_music_player.py +105 -0
- zoraai-1.0.0/nion_prompts.py +227 -0
- zoraai-1.0.0/nion_scheduler.py +234 -0
- zoraai-1.0.0/nion_social_messenger.py +115 -0
- zoraai-1.0.0/nion_vision.py +40 -0
- zoraai-1.0.0/nion_web_creator.py +130 -0
- zoraai-1.0.0/nion_window_CTRL.py +451 -0
- zoraai-1.0.0/pyproject.toml +66 -0
- zoraai-1.0.0/setup.cfg +4 -0
- zoraai-1.0.0/zoraai.egg-info/PKG-INFO +73 -0
- zoraai-1.0.0/zoraai.egg-info/SOURCES.txt +30 -0
- zoraai-1.0.0/zoraai.egg-info/dependency_links.txt +1 -0
- zoraai-1.0.0/zoraai.egg-info/entry_points.txt +2 -0
- zoraai-1.0.0/zoraai.egg-info/requires.txt +25 -0
- zoraai-1.0.0/zoraai.egg-info/top_level.txt +22 -0
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()
|