Skip to content

Commit 4c368be

Browse files
committed
Add status command
1 parent 48ee150 commit 4c368be

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

src/actions/builtin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from src.actions.db_query import DBQueryAction
1010
from src.actions.job import ListJobsAction, GetJobResultAction, StopJobAction
1111
from src.actions.sync.immunefi import ImmunefiSyncAction
12+
from src.actions.status import StatusAction
1213

1314

1415
def get_builtin_actions() -> List[Type[BaseAction]]:
@@ -23,4 +24,5 @@ def get_builtin_actions() -> List[Type[BaseAction]]:
2324
GetJobResultAction,
2425
StopJobAction,
2526
ImmunefiSyncAction,
27+
StatusAction,
2628
]

src/actions/status.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from src.actions.base import BaseAction, ActionSpec
2+
from src.watchers.manager import WatcherManager
3+
from src.watchers.registry import WatcherRegistry
4+
from src.backend.database import DBSessionMixin
5+
from src.models.base import Asset, Project
6+
from sqlalchemy import select, func
7+
from src.util.logging import Logger
8+
from src.config.config import Config
9+
10+
11+
class StatusAction(BaseAction, DBSessionMixin):
12+
"""Action to show system status information"""
13+
14+
spec = ActionSpec(
15+
name="status",
16+
description="Show system status information",
17+
help_text="""Show current system status information.
18+
19+
Usage:
20+
/status
21+
22+
Shows:
23+
- Number of active jobs
24+
- List of registered watchers
25+
- List of currently running watchers
26+
- List of active extensions
27+
- Number of projects in database
28+
- Number of assets in database""",
29+
agent_hint="Use this command to check the current status of the system, including active jobs, watchers, extensions, and database statistics.",
30+
arguments=[],
31+
)
32+
33+
def __init__(self):
34+
DBSessionMixin.__init__(self)
35+
self.logger = Logger("StatusAction")
36+
37+
async def execute(self, *args, **kwargs) -> str:
38+
"""Execute the status action"""
39+
try:
40+
# Get watcher info
41+
watcher_registry = WatcherRegistry()
42+
watcher_registry.initialize()
43+
44+
# Get registered watchers
45+
registered_watchers = list(watcher_registry.watchers.keys())
46+
47+
# Get currently running watchers from WatcherManager singleton
48+
watcher_manager = WatcherManager.get_instance()
49+
running_watchers = []
50+
51+
self.logger.info(f"Watcher manager: {watcher_manager.watchers}")
52+
self.logger.info(f"Registered watchers: {registered_watchers}")
53+
for name, watcher in watcher_manager.watchers.items():
54+
# A watcher is running if its _watch_task exists and is not done
55+
if hasattr(watcher, "_watch_task") and watcher._watch_task and not watcher._watch_task.done():
56+
running_watchers.append(name)
57+
self.logger.info(f"Watcher {name} is running with task {watcher._watch_task}")
58+
else:
59+
self.logger.info(f"Watcher {name} is not running: task={getattr(watcher, '_watch_task', None)}")
60+
61+
# Get active extensions
62+
config = Config()
63+
active_extensions = config.get("active_extensions", [])
64+
65+
# Get database stats
66+
with self.get_session() as session:
67+
project_count = session.scalar(select(func.count()).select_from(Project))
68+
asset_count = session.scalar(select(func.count()).select_from(Asset))
69+
70+
# Format output
71+
lines = [
72+
"📊 System Status\n",
73+
"Watchers:",
74+
f" • Registered: {', '.join(registered_watchers)}",
75+
f" • Running: {', '.join(running_watchers) if running_watchers else 'None'}",
76+
"\nExtensions:",
77+
f" • Active: {', '.join(active_extensions) if active_extensions else 'None'}",
78+
"\nDatabase:",
79+
f" • Projects: {project_count:,}",
80+
f" • Assets: {asset_count:,}",
81+
]
82+
83+
return "\n".join(lines)
84+
85+
except Exception as e:
86+
self.logger.error(f"Failed to get system status: {str(e)}")
87+
return f"Error getting system status: {str(e)}"

src/server/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async def run(cls, interfaces: List[str]) -> None:
1818
"""Run the server with specified interfaces"""
1919
logger = Logger("Server")
2020
initializer = Initializer()
21-
watcher_manager = WatcherManager()
21+
watcher_manager = WatcherManager.get_instance()
2222
action_registry = ActionRegistry()
2323
job_manager = JobManager()
2424

src/watchers/manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@
1212
class WatcherManager:
1313
"""Manages watcher jobs and their lifecycle"""
1414

15+
_instance = None
16+
17+
@classmethod
18+
def get_instance(cls) -> "WatcherManager":
19+
"""Get the singleton instance"""
20+
if cls._instance is None:
21+
cls._instance = cls()
22+
return cls._instance
23+
1524
def __init__(self):
25+
if WatcherManager._instance is not None:
26+
raise RuntimeError("Use get_instance() instead")
1627
self.logger = Logger("WatcherManager")
1728
self.config = Config()
1829
self.watchers: Dict[str, WatcherJob] = {}
1930
self.webhook_server: Optional[WebhookServer] = None
31+
WatcherManager._instance = self
2032

2133
def _discover_watchers(self) -> Dict[str, Type[WatcherJob]]:
2234
"""Discover all available watcher classes"""

0 commit comments

Comments
 (0)