emdash-cli 0.1.35__py3-none-any.whl → 0.1.67__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.
- emdash_cli/client.py +41 -22
- emdash_cli/clipboard.py +30 -61
- emdash_cli/commands/__init__.py +2 -2
- emdash_cli/commands/agent/__init__.py +14 -0
- emdash_cli/commands/agent/cli.py +100 -0
- emdash_cli/commands/agent/constants.py +63 -0
- emdash_cli/commands/agent/file_utils.py +178 -0
- emdash_cli/commands/agent/handlers/__init__.py +51 -0
- emdash_cli/commands/agent/handlers/agents.py +449 -0
- emdash_cli/commands/agent/handlers/auth.py +69 -0
- emdash_cli/commands/agent/handlers/doctor.py +319 -0
- emdash_cli/commands/agent/handlers/hooks.py +121 -0
- emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli/commands/agent/handlers/mcp.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +319 -0
- emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli/commands/agent/handlers/rules.py +411 -0
- emdash_cli/commands/agent/handlers/sessions.py +168 -0
- emdash_cli/commands/agent/handlers/setup.py +715 -0
- emdash_cli/commands/agent/handlers/skills.py +478 -0
- emdash_cli/commands/agent/handlers/telegram.py +475 -0
- emdash_cli/commands/agent/handlers/todos.py +119 -0
- emdash_cli/commands/agent/handlers/verify.py +653 -0
- emdash_cli/commands/agent/help.py +236 -0
- emdash_cli/commands/agent/interactive.py +842 -0
- emdash_cli/commands/agent/menus.py +760 -0
- emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli/commands/agent.py +7 -1321
- emdash_cli/commands/index.py +111 -13
- emdash_cli/commands/registry.py +635 -0
- emdash_cli/commands/server.py +99 -40
- emdash_cli/commands/skills.py +72 -6
- emdash_cli/design.py +328 -0
- emdash_cli/diff_renderer.py +438 -0
- emdash_cli/integrations/__init__.py +1 -0
- emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli/integrations/telegram/bridge.py +865 -0
- emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli/integrations/telegram/formatter.py +385 -0
- emdash_cli/main.py +52 -2
- emdash_cli/server_manager.py +70 -10
- emdash_cli/sse_renderer.py +659 -167
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -4
- emdash_cli-0.1.67.dist-info/RECORD +63 -0
- emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.35.dist-info/RECORD +0 -30
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
emdash_cli/server_manager.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Server lifecycle management for emdash-core."""
|
|
2
2
|
|
|
3
3
|
import atexit
|
|
4
|
+
import hashlib
|
|
4
5
|
import os
|
|
5
6
|
import signal
|
|
6
7
|
import socket
|
|
@@ -17,15 +18,15 @@ class ServerManager:
|
|
|
17
18
|
"""Manages FastAPI server lifecycle for CLI.
|
|
18
19
|
|
|
19
20
|
The ServerManager handles:
|
|
21
|
+
- Per-repo server instances (each repo gets its own server)
|
|
20
22
|
- Discovering running servers via port file
|
|
21
23
|
- Starting new servers when needed
|
|
22
24
|
- Health checking servers
|
|
23
25
|
- Graceful shutdown on CLI exit
|
|
26
|
+
- Cleanup of stale servers
|
|
24
27
|
"""
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
PORT_FILE = Path.home() / ".emdash" / "server.port"
|
|
28
|
-
PID_FILE = Path.home() / ".emdash" / "server.pid"
|
|
29
|
+
SERVERS_DIR = Path.home() / ".emdash" / "servers"
|
|
29
30
|
STARTUP_TIMEOUT = 30.0 # seconds
|
|
30
31
|
HEALTH_TIMEOUT = 2.0 # seconds
|
|
31
32
|
|
|
@@ -40,8 +41,35 @@ class ServerManager:
|
|
|
40
41
|
self.port: Optional[int] = None
|
|
41
42
|
self._started_by_us = False
|
|
42
43
|
|
|
44
|
+
# Create servers directory
|
|
45
|
+
self.SERVERS_DIR.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# Cleanup stale servers on init
|
|
48
|
+
self._cleanup_stale_servers()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def _repo_hash(self) -> str:
|
|
52
|
+
"""Get a short hash of the repo root path for unique file naming."""
|
|
53
|
+
path_str = str(self.repo_root.resolve())
|
|
54
|
+
return hashlib.sha256(path_str.encode()).hexdigest()[:12]
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def _port_file(self) -> Path:
|
|
58
|
+
"""Get the port file path for this repo."""
|
|
59
|
+
return self.SERVERS_DIR / f"{self._repo_hash}.port"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def _pid_file(self) -> Path:
|
|
63
|
+
"""Get the PID file path for this repo."""
|
|
64
|
+
return self.SERVERS_DIR / f"{self._repo_hash}.pid"
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def _repo_file(self) -> Path:
|
|
68
|
+
"""Get the repo path file for this repo (for debugging)."""
|
|
69
|
+
return self.SERVERS_DIR / f"{self._repo_hash}.repo"
|
|
70
|
+
|
|
43
71
|
def get_server_url(self) -> str:
|
|
44
|
-
"""Get URL of running server, starting one if needed.
|
|
72
|
+
"""Get URL of running server for this repo, starting one if needed.
|
|
45
73
|
|
|
46
74
|
Returns:
|
|
47
75
|
Base URL of the running server (e.g., "http://localhost:8765")
|
|
@@ -49,17 +77,19 @@ class ServerManager:
|
|
|
49
77
|
Raises:
|
|
50
78
|
RuntimeError: If server fails to start
|
|
51
79
|
"""
|
|
52
|
-
# Check if server already running
|
|
53
|
-
if self.
|
|
80
|
+
# Check if server already running for THIS repo
|
|
81
|
+
if self._port_file.exists():
|
|
54
82
|
try:
|
|
55
|
-
port = int(self.
|
|
83
|
+
port = int(self._port_file.read_text().strip())
|
|
56
84
|
if self._check_health(port):
|
|
57
85
|
self.port = port
|
|
58
86
|
return f"http://localhost:{port}"
|
|
59
87
|
except (ValueError, IOError):
|
|
60
88
|
pass
|
|
89
|
+
# Server not healthy, clean up stale files
|
|
90
|
+
self._cleanup_files()
|
|
61
91
|
|
|
62
|
-
# Start new server
|
|
92
|
+
# Start new server for this repo
|
|
63
93
|
self.port = self._find_free_port()
|
|
64
94
|
self._spawn_server()
|
|
65
95
|
return f"http://localhost:{self.port}"
|
|
@@ -133,6 +163,11 @@ class ServerManager:
|
|
|
133
163
|
|
|
134
164
|
self._started_by_us = True
|
|
135
165
|
|
|
166
|
+
# Write port, PID, and repo files
|
|
167
|
+
self._port_file.write_text(str(self.port))
|
|
168
|
+
self._pid_file.write_text(str(self.process.pid))
|
|
169
|
+
self._repo_file.write_text(str(self.repo_root))
|
|
170
|
+
|
|
136
171
|
# Register cleanup for normal exit
|
|
137
172
|
atexit.register(self.shutdown)
|
|
138
173
|
|
|
@@ -203,14 +238,39 @@ class ServerManager:
|
|
|
203
238
|
)
|
|
204
239
|
|
|
205
240
|
def _cleanup_files(self) -> None:
|
|
206
|
-
"""Clean up port and
|
|
207
|
-
for file in [self.
|
|
241
|
+
"""Clean up port, PID, and repo files for this repo."""
|
|
242
|
+
for file in [self._port_file, self._pid_file, self._repo_file]:
|
|
208
243
|
try:
|
|
209
244
|
if file.exists():
|
|
210
245
|
file.unlink()
|
|
211
246
|
except IOError:
|
|
212
247
|
pass
|
|
213
248
|
|
|
249
|
+
def _cleanup_stale_servers(self) -> None:
|
|
250
|
+
"""Clean up stale server files where process no longer exists."""
|
|
251
|
+
if not self.SERVERS_DIR.exists():
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
for pid_file in self.SERVERS_DIR.glob("*.pid"):
|
|
255
|
+
try:
|
|
256
|
+
pid = int(pid_file.read_text().strip())
|
|
257
|
+
# Check if process exists
|
|
258
|
+
try:
|
|
259
|
+
os.kill(pid, 0) # Signal 0 checks if process exists
|
|
260
|
+
except OSError:
|
|
261
|
+
# Process doesn't exist, clean up files
|
|
262
|
+
hash_prefix = pid_file.stem
|
|
263
|
+
for ext in [".port", ".pid", ".repo"]:
|
|
264
|
+
stale_file = self.SERVERS_DIR / f"{hash_prefix}{ext}"
|
|
265
|
+
if stale_file.exists():
|
|
266
|
+
stale_file.unlink()
|
|
267
|
+
except (ValueError, IOError):
|
|
268
|
+
# Invalid PID file, remove it
|
|
269
|
+
try:
|
|
270
|
+
pid_file.unlink()
|
|
271
|
+
except IOError:
|
|
272
|
+
pass
|
|
273
|
+
|
|
214
274
|
|
|
215
275
|
# Global singleton for CLI commands
|
|
216
276
|
_server_manager: Optional[ServerManager] = None
|