realtimex-deeptutor 0.5.0.post6__py3-none-any.whl → 0.5.0.post8__py3-none-any.whl
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.
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/METADATA +1 -1
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/RECORD +7 -7
- scripts/start_web.py +199 -43
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/WHEEL +0 -0
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/entry_points.txt +0 -0
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/licenses/LICENSE +0 -0
- {realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/top_level.txt +0 -0
{realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/RECORD
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
realtimex_deeptutor/__init__.py,sha256=sSfuCLjJa6BnayszcU4azNl_sr1OzuKgLP10BAtdoh8,1567
|
|
2
|
-
realtimex_deeptutor-0.5.0.
|
|
2
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
3
3
|
scripts/__init__.py,sha256=mxMsCbci-Qon3qWU1JIi93-tYlHAy0NIUbDRmAPVcg0,54
|
|
4
4
|
scripts/audit_prompts.py,sha256=Ltuk7tvsjpKhiobVbYq1volgVFKiVLgSTaE_Is4MGaM,5651
|
|
5
5
|
scripts/check_install.py,sha256=GbApEcDLJ6r0QmYrCVHAFCOK4wolpSLwL3eBRmmD3og,13929
|
|
@@ -7,7 +7,7 @@ scripts/generate_roster.py,sha256=COsJ12bvZ5W9TI-wAvKpknKBgHr9uQTvJ_JCz2gVMVo,12
|
|
|
7
7
|
scripts/install_all.py,sha256=u-A3eLhk1ua_KCjz8WZMkrVNJN6QdYs7NhGOcsm-Mks,23875
|
|
8
8
|
scripts/migrate_kb.py,sha256=uyJgplkJag35rT2RrwSiT37__gpB4TiA0xh5uVcWIa4,19667
|
|
9
9
|
scripts/start.py,sha256=EYbyjryor0DN_WcxQMSkKWCboM9UjMkv61fWhLyv63I,30300
|
|
10
|
-
scripts/start_web.py,sha256=
|
|
10
|
+
scripts/start_web.py,sha256=ZND4cNGYKpd52hygA2HFJFFiB1IGJoFAgP1mBXUNURA,34796
|
|
11
11
|
scripts/sync_prompts_from_en.py,sha256=TkBSFilYSwnwo0a3cgRnJ84i02zByAIW12N3ePzBwE8,4677
|
|
12
12
|
src/__init__.py,sha256=UNw3C20mbskiQF3rK3HhjglrG8snhfuiVthc5UsoHX0,1046
|
|
13
13
|
src/agents/__init__.py,sha256=IPhP4RZnCH2kcUDBkdKHO_ciVdyWnuHUCG2flG5Ydcw,885
|
|
@@ -289,8 +289,8 @@ src/utils/error_utils.py,sha256=ME_9q-DlmxFl-Xvv3ETPZE_iP705x6MXiuAREgWYsjM,2262
|
|
|
289
289
|
src/utils/json_parser.py,sha256=M_KfrsrNvQPSiFvpKHQV79Aj85_MEcLVc6hnKzvTV58,3243
|
|
290
290
|
src/utils/realtimex.py,sha256=vs7fAEnJJ4zpAyyBn-7vUmGWiiQvpTWQCRgax1MLTDw,9769
|
|
291
291
|
src/utils/network/circuit_breaker.py,sha256=BtjogK5R3tG8fuJniS5-PJKZMtwD5P2SkP2JFiQ9sRA,2722
|
|
292
|
-
realtimex_deeptutor-0.5.0.
|
|
293
|
-
realtimex_deeptutor-0.5.0.
|
|
294
|
-
realtimex_deeptutor-0.5.0.
|
|
295
|
-
realtimex_deeptutor-0.5.0.
|
|
296
|
-
realtimex_deeptutor-0.5.0.
|
|
292
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/METADATA,sha256=UZu0J6h0x9m5V-Rn1_dOZYA0FPdgWSm2Na8eKkmeUxQ,58304
|
|
293
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
294
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/entry_points.txt,sha256=slNAzwRLUpqiMtDRZBQIkXbU2vGMHL_om6-o19gYdh8,134
|
|
295
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/top_level.txt,sha256=zUAd6V7jDYhdL7bvg2S38YCM-gVhvd36WqkjxrT-02I,32
|
|
296
|
+
realtimex_deeptutor-0.5.0.post8.dist-info/RECORD,,
|
scripts/start_web.py
CHANGED
|
@@ -20,6 +20,80 @@ if hasattr(sys.stdout, "reconfigure"):
|
|
|
20
20
|
sys.stdout.reconfigure(line_buffering=True)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
class ExecutableResolver:
|
|
24
|
+
"""
|
|
25
|
+
Resolves bundled executables (uvx, uv, npx) to their actual paths.
|
|
26
|
+
Ported from ExecutableResolver.js
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._resources_root = None
|
|
31
|
+
|
|
32
|
+
def get_resources_root(self) -> Path | None:
|
|
33
|
+
"""
|
|
34
|
+
Get the resources root directory (~/.realtimex.ai)
|
|
35
|
+
Returns path to resources root or None if not found
|
|
36
|
+
"""
|
|
37
|
+
if self._resources_root:
|
|
38
|
+
return self._resources_root
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
home = Path.home()
|
|
42
|
+
self._resources_root = home / ".realtimex.ai"
|
|
43
|
+
return self._resources_root
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print_flush(f"⚠️ [ExecutableResolver] Failed to resolve resources directory: {e}")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def resolve_from_candidates(
|
|
49
|
+
self, env_var: str | None, candidates: list[Path | str]
|
|
50
|
+
) -> str | None:
|
|
51
|
+
"""
|
|
52
|
+
Find a valid executable from a list of candidate paths
|
|
53
|
+
"""
|
|
54
|
+
search_paths = []
|
|
55
|
+
if env_var:
|
|
56
|
+
search_paths.append(Path(env_var))
|
|
57
|
+
|
|
58
|
+
search_paths.extend([Path(c) for c in candidates])
|
|
59
|
+
|
|
60
|
+
for candidate in search_paths:
|
|
61
|
+
if not candidate:
|
|
62
|
+
continue
|
|
63
|
+
try:
|
|
64
|
+
if candidate.exists() and candidate.is_file():
|
|
65
|
+
return str(candidate)
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def resolve_npx(self) -> str | None:
|
|
71
|
+
"""
|
|
72
|
+
Resolve npx to bundled executable
|
|
73
|
+
"""
|
|
74
|
+
resources_dir = self.get_resources_root()
|
|
75
|
+
if not resources_dir:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
node_version = os.environ.get("REALTIMEX_NPX_NODE_VERSION", "v22.16.0")
|
|
79
|
+
home = Path.home()
|
|
80
|
+
|
|
81
|
+
candidates = [
|
|
82
|
+
# User's NVM installation (most common case)
|
|
83
|
+
home / ".nvm" / "versions" / "node" / node_version / "bin" / "npx",
|
|
84
|
+
# NVM-installed node in resources dir (bundled)
|
|
85
|
+
resources_dir / ".nvm" / "versions" / "node" / node_version / "bin" / "npx",
|
|
86
|
+
# Windows NVM
|
|
87
|
+
Path("C:/nvm") / node_version / "npx.cmd",
|
|
88
|
+
# Bundled in Resources
|
|
89
|
+
resources_dir / "Resources" / "envs" / "Scripts" / "npx.cmd",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
return self.resolve_from_candidates(
|
|
93
|
+
env_var=os.environ.get("REALTIMEX_NPX_PATH"), candidates=candidates
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
23
97
|
def print_flush(*args, **kwargs):
|
|
24
98
|
"""Print with flush=True by default"""
|
|
25
99
|
kwargs.setdefault("flush", True)
|
|
@@ -69,6 +143,103 @@ else:
|
|
|
69
143
|
return False
|
|
70
144
|
|
|
71
145
|
|
|
146
|
+
def find_pid_by_port(port: int) -> int | None:
|
|
147
|
+
"""
|
|
148
|
+
Find PID using a port with multiple fallback methods.
|
|
149
|
+
Returns PID or None if not found.
|
|
150
|
+
"""
|
|
151
|
+
if os.name == "nt":
|
|
152
|
+
# Windows: use netstat
|
|
153
|
+
try:
|
|
154
|
+
result = subprocess.run(
|
|
155
|
+
["netstat", "-ano"],
|
|
156
|
+
capture_output=True,
|
|
157
|
+
text=True,
|
|
158
|
+
timeout=5,
|
|
159
|
+
)
|
|
160
|
+
for line in result.stdout.splitlines():
|
|
161
|
+
if f":{port}" in line and "LISTENING" in line:
|
|
162
|
+
parts = line.split()
|
|
163
|
+
if parts:
|
|
164
|
+
try:
|
|
165
|
+
return int(parts[-1])
|
|
166
|
+
except ValueError:
|
|
167
|
+
pass
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# Unix: Try strategies in order: lsof -> ss -> netstat
|
|
173
|
+
|
|
174
|
+
# Strategy 1: lsof (standard)
|
|
175
|
+
try:
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
["lsof", "-ti", f":{port}"],
|
|
178
|
+
capture_output=True,
|
|
179
|
+
text=True,
|
|
180
|
+
timeout=5,
|
|
181
|
+
)
|
|
182
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
183
|
+
try:
|
|
184
|
+
# May return multiple PIDs, take the first one
|
|
185
|
+
return int(result.stdout.strip().split()[0])
|
|
186
|
+
except (ValueError, IndexError):
|
|
187
|
+
pass
|
|
188
|
+
except FileNotFoundError:
|
|
189
|
+
pass # lsof not installed
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
# Strategy 2: ss (modern Linux)
|
|
194
|
+
try:
|
|
195
|
+
# ss -lptn 'sport = :8001'
|
|
196
|
+
result = subprocess.run(
|
|
197
|
+
["ss", "-lptn", f"sport = :{port}"],
|
|
198
|
+
capture_output=True,
|
|
199
|
+
text=True,
|
|
200
|
+
timeout=5,
|
|
201
|
+
)
|
|
202
|
+
if result.returncode == 0:
|
|
203
|
+
# Output format: Users:(("python",pid=1234,fd=3))
|
|
204
|
+
output = result.stdout
|
|
205
|
+
if f":{port}" in output and "pid=" in output:
|
|
206
|
+
import re
|
|
207
|
+
|
|
208
|
+
match = re.search(r"pid=(\d+)", output)
|
|
209
|
+
if match:
|
|
210
|
+
return int(match.group(1))
|
|
211
|
+
except FileNotFoundError:
|
|
212
|
+
pass # ss not installed
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
# Strategy 3: netstat (legacy Unix)
|
|
217
|
+
try:
|
|
218
|
+
# netstat -nlp | grep :8001
|
|
219
|
+
p1 = subprocess.Popen(
|
|
220
|
+
["netstat", "-nlp"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
|
|
221
|
+
)
|
|
222
|
+
p2 = subprocess.Popen(
|
|
223
|
+
["grep", f":{port}"], stdin=p1.stdout, stdout=subprocess.PIPE, text=True
|
|
224
|
+
)
|
|
225
|
+
if p1.stdout:
|
|
226
|
+
p1.stdout.close()
|
|
227
|
+
output, _ = p2.communicate(timeout=5)
|
|
228
|
+
|
|
229
|
+
if output:
|
|
230
|
+
# Expected: tcp 0 0 0.0.0.0:8001 0.0.0.0:* LISTEN 1234/python
|
|
231
|
+
parts = output.split()
|
|
232
|
+
for part in parts:
|
|
233
|
+
if "/" in part and part.split("/")[0].isdigit():
|
|
234
|
+
return int(part.split("/")[0])
|
|
235
|
+
except FileNotFoundError:
|
|
236
|
+
pass
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
72
243
|
def check_port_in_use(port: int) -> tuple[bool, int | None]:
|
|
73
244
|
"""
|
|
74
245
|
Check if a port is in use and return the PID of the process using it.
|
|
@@ -103,42 +274,7 @@ def check_port_in_use(port: int) -> tuple[bool, int | None]:
|
|
|
103
274
|
pass
|
|
104
275
|
|
|
105
276
|
# Port is in use (connection succeeded), try to find the PID
|
|
106
|
-
pid =
|
|
107
|
-
try:
|
|
108
|
-
if os.name == "nt":
|
|
109
|
-
# Windows: use netstat
|
|
110
|
-
result = subprocess.run(
|
|
111
|
-
["netstat", "-ano"],
|
|
112
|
-
capture_output=True,
|
|
113
|
-
text=True,
|
|
114
|
-
timeout=5,
|
|
115
|
-
)
|
|
116
|
-
for line in result.stdout.splitlines():
|
|
117
|
-
if f":{port}" in line and "LISTENING" in line:
|
|
118
|
-
parts = line.split()
|
|
119
|
-
if parts:
|
|
120
|
-
try:
|
|
121
|
-
pid = int(parts[-1])
|
|
122
|
-
break
|
|
123
|
-
except ValueError:
|
|
124
|
-
pass
|
|
125
|
-
else:
|
|
126
|
-
# Unix: use lsof
|
|
127
|
-
result = subprocess.run(
|
|
128
|
-
["lsof", "-ti", f":{port}"],
|
|
129
|
-
capture_output=True,
|
|
130
|
-
text=True,
|
|
131
|
-
timeout=5,
|
|
132
|
-
)
|
|
133
|
-
if result.returncode == 0 and result.stdout.strip():
|
|
134
|
-
# May return multiple PIDs, take the first one
|
|
135
|
-
try:
|
|
136
|
-
pid = int(result.stdout.strip().split()[0])
|
|
137
|
-
except (ValueError, IndexError):
|
|
138
|
-
pass
|
|
139
|
-
except Exception:
|
|
140
|
-
pass
|
|
141
|
-
|
|
277
|
+
pid = find_pid_by_port(port)
|
|
142
278
|
return True, pid
|
|
143
279
|
|
|
144
280
|
|
|
@@ -159,7 +295,11 @@ def kill_process_on_port(port: int, force: bool = False) -> bool:
|
|
|
159
295
|
|
|
160
296
|
if pid is None:
|
|
161
297
|
print_flush(f"⚠️ Port {port} is in use but couldn't identify the process")
|
|
162
|
-
|
|
298
|
+
# Retry detection once with slightly longer delay just in case
|
|
299
|
+
time.sleep(1)
|
|
300
|
+
_, pid = check_port_in_use(port)
|
|
301
|
+
if pid is None:
|
|
302
|
+
return False
|
|
163
303
|
|
|
164
304
|
print_flush(f" Stopping process {pid} on port {port}...")
|
|
165
305
|
|
|
@@ -167,21 +307,35 @@ def kill_process_on_port(port: int, force: bool = False) -> bool:
|
|
|
167
307
|
if os.name == "nt":
|
|
168
308
|
subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=True, capture_output=True)
|
|
169
309
|
else:
|
|
170
|
-
|
|
171
|
-
|
|
310
|
+
# Try to kill the process group first (handles child processes)
|
|
311
|
+
try:
|
|
312
|
+
pgid = os.getpgid(pid)
|
|
313
|
+
sig = signal.SIGKILL if force else signal.SIGTERM
|
|
314
|
+
os.killpg(pgid, sig)
|
|
315
|
+
except (ProcessLookupError, PermissionError):
|
|
316
|
+
# Fallbck to simple kill if PGID fails
|
|
317
|
+
sig = signal.SIGKILL if force else signal.SIGTERM
|
|
318
|
+
os.kill(pid, sig)
|
|
319
|
+
|
|
172
320
|
# Wait a moment for process to terminate
|
|
173
321
|
time.sleep(0.5)
|
|
174
322
|
# Check if still running, force kill if needed
|
|
175
323
|
if not force:
|
|
176
324
|
try:
|
|
177
325
|
os.kill(pid, 0) # Check if process exists
|
|
178
|
-
|
|
326
|
+
# If still alive, Force Kill
|
|
327
|
+
print_flush(f" Process {pid} still alive, force killing...")
|
|
328
|
+
try:
|
|
329
|
+
pgid = os.getpgid(pid)
|
|
330
|
+
os.killpg(pgid, signal.SIGKILL)
|
|
331
|
+
except:
|
|
332
|
+
os.kill(pid, signal.SIGKILL)
|
|
179
333
|
time.sleep(0.3)
|
|
180
334
|
except ProcessLookupError:
|
|
181
335
|
pass # Process already terminated
|
|
182
336
|
|
|
183
337
|
# Verify port is now free
|
|
184
|
-
time.sleep(0.
|
|
338
|
+
time.sleep(0.5)
|
|
185
339
|
in_use, _ = check_port_in_use(port)
|
|
186
340
|
if not in_use:
|
|
187
341
|
print_flush(f"✅ Port {port} is now free")
|
|
@@ -625,7 +779,9 @@ def _start_frontend_npx(frontend_port, backend_port):
|
|
|
625
779
|
"""Start frontend using published package via npx (production mode)"""
|
|
626
780
|
|
|
627
781
|
# Check if npx is available
|
|
628
|
-
|
|
782
|
+
resolver = ExecutableResolver()
|
|
783
|
+
npx_path = resolver.resolve_npx() or shutil.which("npx")
|
|
784
|
+
|
|
629
785
|
if not npx_path:
|
|
630
786
|
print_flush("❌ Error: 'npx' command not found!")
|
|
631
787
|
print_flush(" Please install Node.js and npm first.")
|
|
@@ -653,7 +809,7 @@ def _start_frontend_npx(frontend_port, backend_port):
|
|
|
653
809
|
if os.name == "nt":
|
|
654
810
|
env["PYTHONLEGACYWINDOWSSTDIO"] = "0"
|
|
655
811
|
|
|
656
|
-
npx_cmd =
|
|
812
|
+
npx_cmd = npx_path or "npx"
|
|
657
813
|
|
|
658
814
|
# Process group configuration
|
|
659
815
|
popen_kwargs = {
|
{realtimex_deeptutor-0.5.0.post6.dist-info → realtimex_deeptutor-0.5.0.post8.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|