neuro-simulator 0.1.2__py3-none-any.whl → 0.2.0__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.
Files changed (49) hide show
  1. neuro_simulator/__init__.py +1 -10
  2. neuro_simulator/agent/__init__.py +1 -8
  3. neuro_simulator/agent/base.py +43 -0
  4. neuro_simulator/agent/core.py +111 -374
  5. neuro_simulator/agent/factory.py +30 -0
  6. neuro_simulator/agent/llm.py +34 -31
  7. neuro_simulator/agent/memory/__init__.py +1 -4
  8. neuro_simulator/agent/memory/manager.py +64 -230
  9. neuro_simulator/agent/tools/__init__.py +1 -4
  10. neuro_simulator/agent/tools/core.py +8 -18
  11. neuro_simulator/api/__init__.py +1 -0
  12. neuro_simulator/api/agent.py +163 -0
  13. neuro_simulator/api/stream.py +55 -0
  14. neuro_simulator/api/system.py +90 -0
  15. neuro_simulator/cli.py +53 -142
  16. neuro_simulator/core/__init__.py +1 -0
  17. neuro_simulator/core/agent_factory.py +52 -0
  18. neuro_simulator/core/agent_interface.py +91 -0
  19. neuro_simulator/core/application.py +278 -0
  20. neuro_simulator/services/__init__.py +1 -0
  21. neuro_simulator/{chatbot.py → services/audience.py} +24 -24
  22. neuro_simulator/{audio_synthesis.py → services/audio.py} +18 -15
  23. neuro_simulator/services/builtin.py +87 -0
  24. neuro_simulator/services/letta.py +206 -0
  25. neuro_simulator/{stream_manager.py → services/stream.py} +39 -47
  26. neuro_simulator/utils/__init__.py +1 -0
  27. neuro_simulator/utils/logging.py +90 -0
  28. neuro_simulator/utils/process.py +67 -0
  29. neuro_simulator/{stream_chat.py → utils/queue.py} +17 -4
  30. neuro_simulator/utils/state.py +14 -0
  31. neuro_simulator/{websocket_manager.py → utils/websocket.py} +18 -14
  32. {neuro_simulator-0.1.2.dist-info → neuro_simulator-0.2.0.dist-info}/METADATA +176 -176
  33. neuro_simulator-0.2.0.dist-info/RECORD +37 -0
  34. neuro_simulator/agent/api.py +0 -737
  35. neuro_simulator/agent/memory.py +0 -137
  36. neuro_simulator/agent/tools.py +0 -69
  37. neuro_simulator/builtin_agent.py +0 -83
  38. neuro_simulator/config.yaml.example +0 -157
  39. neuro_simulator/letta.py +0 -164
  40. neuro_simulator/log_handler.py +0 -43
  41. neuro_simulator/main.py +0 -673
  42. neuro_simulator/media/neuro_start.mp4 +0 -0
  43. neuro_simulator/process_manager.py +0 -70
  44. neuro_simulator/shared_state.py +0 -11
  45. neuro_simulator-0.1.2.dist-info/RECORD +0 -31
  46. /neuro_simulator/{config.py → core/config.py} +0 -0
  47. {neuro_simulator-0.1.2.dist-info → neuro_simulator-0.2.0.dist-info}/WHEEL +0 -0
  48. {neuro_simulator-0.1.2.dist-info → neuro_simulator-0.2.0.dist-info}/entry_points.txt +0 -0
  49. {neuro_simulator-0.1.2.dist-info → neuro_simulator-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,14 @@
1
- # agent/tools/core.py
1
+ # neuro_simulator/agent/tools/core.py
2
2
  """
3
3
  Core tools for the Neuro Simulator Agent
4
4
  """
5
5
 
6
+ import logging
7
+ from pathlib import Path
6
8
  from typing import Dict, List, Any, Optional
7
- import os
8
- import sys
9
+
10
+ # Use a logger with a shortened, more readable name
11
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
9
12
 
10
13
  class ToolManager:
11
14
  """Manages all tools available to the agent"""
@@ -17,7 +20,6 @@ class ToolManager:
17
20
 
18
21
  def _register_tools(self):
19
22
  """Register all available tools"""
20
- # Memory management tools
21
23
  self.tools["get_core_memory_blocks"] = self._get_core_memory_blocks
22
24
  self.tools["get_core_memory_block"] = self._get_core_memory_block
23
25
  self.tools["create_core_memory_block"] = self._create_core_memory_block
@@ -26,8 +28,6 @@ class ToolManager:
26
28
  self.tools["add_to_core_memory_block"] = self._add_to_core_memory_block
27
29
  self.tools["remove_from_core_memory_block"] = self._remove_from_core_memory_block
28
30
  self.tools["add_temp_memory"] = self._add_temp_memory
29
-
30
- # Output tool
31
31
  self.tools["speak"] = self._speak
32
32
 
33
33
  def get_tool_descriptions(self) -> str:
@@ -67,46 +67,36 @@ class ToolManager:
67
67
 
68
68
  # Tool implementations
69
69
  async def _get_core_memory_blocks(self) -> Dict[str, Any]:
70
- """Get all core memory blocks"""
71
70
  return await self.memory_manager.get_core_memory_blocks()
72
71
 
73
72
  async def _get_core_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
74
- """Get a specific core memory block"""
75
73
  return await self.memory_manager.get_core_memory_block(block_id)
76
74
 
77
75
  async def _create_core_memory_block(self, title: str, description: str, content: List[str]) -> str:
78
- """Create a new core memory block with a generated ID"""
79
76
  block_id = await self.memory_manager.create_core_memory_block(title, description, content)
80
77
  return f"Created core memory block '{block_id}' with title '{title}'"
81
78
 
82
79
  async def _update_core_memory_block(self, block_id: str, title: str = None, description: str = None, content: List[str] = None) -> str:
83
- """Update a core memory block"""
84
80
  await self.memory_manager.update_core_memory_block(block_id, title, description, content)
85
81
  return f"Updated core memory block '{block_id}'"
86
82
 
87
83
  async def _delete_core_memory_block(self, block_id: str) -> str:
88
- """Delete a core memory block"""
89
84
  await self.memory_manager.delete_core_memory_block(block_id)
90
85
  return f"Deleted core memory block '{block_id}'"
91
86
 
92
87
  async def _add_to_core_memory_block(self, block_id: str, item: str) -> str:
93
- """Add an item to a core memory block"""
94
88
  await self.memory_manager.add_to_core_memory_block(block_id, item)
95
89
  return f"Added item to core memory block '{block_id}'"
96
90
 
97
91
  async def _remove_from_core_memory_block(self, block_id: str, index: int) -> str:
98
- """Remove an item from a core memory block by index"""
99
92
  await self.memory_manager.remove_from_core_memory_block(block_id, index)
100
93
  return f"Removed item from core memory block '{block_id}' at index {index}"
101
94
 
102
95
  async def _add_temp_memory(self, content: str, role: str = "user") -> str:
103
- """Add an item to temp memory"""
104
96
  await self.memory_manager.add_temp_memory(content, role)
105
97
  return f"Added item to temp memory with role '{role}'"
106
98
 
107
99
  async def _speak(self, text: str) -> str:
108
100
  """Output text - this is how the agent communicates with users"""
109
- print(f"Agent says: {text}")
110
- # Note: Context is now managed in the process_messages method
111
- # This tool only outputs the text, not stores it in memory
112
- return text
101
+ logger.info(f"Agent says: {text}")
102
+ return text
@@ -0,0 +1 @@
1
+ # This file makes the 'api' directory a Python package.
@@ -0,0 +1,163 @@
1
+ # neuro_simulator/api/agent.py
2
+ """Unified API endpoints for agent management, decoupled from implementation."""
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
5
+ from typing import Dict, Any, List, Optional
6
+ from pydantic import BaseModel
7
+
8
+ # Imports for the new structure
9
+ from ..core.config import config_manager
10
+ from ..core.agent_factory import create_agent
11
+ from ..core.agent_interface import BaseAgent
12
+
13
+ router = APIRouter(prefix="/api/agent", tags=["Agent Management"])
14
+
15
+ # Security dependency (remains the same)
16
+ async def get_api_token(request: Request):
17
+ password = config_manager.settings.server.panel_password
18
+ if not password:
19
+ return True
20
+ header_token = request.headers.get("X-API-Token")
21
+ if header_token and header_token == password:
22
+ return True
23
+ raise HTTPException(
24
+ status_code=status.HTTP_401_UNAUTHORIZED,
25
+ detail="Invalid API token",
26
+ headers={"WWW-Authenticate": "Bearer"},
27
+ )
28
+
29
+ # Pydantic models (remains the same)
30
+ class MessageItem(BaseModel):
31
+ username: str
32
+ text: str
33
+ role: str = "user"
34
+
35
+ class ToolExecutionRequest(BaseModel):
36
+ tool_name: str
37
+ params: Dict[str, Any]
38
+
39
+ class MemoryUpdateRequest(BaseModel):
40
+ title: Optional[str] = None
41
+ description: Optional[str] = None
42
+ content: Optional[List[str]] = None
43
+
44
+ class MemoryCreateRequest(BaseModel):
45
+ title: str
46
+ description: str
47
+ content: List[str]
48
+
49
+ class InitMemoryUpdateRequest(BaseModel):
50
+ memory: Dict[str, Any]
51
+
52
+ class TempMemoryItem(BaseModel):
53
+ content: str
54
+ role: str = "system"
55
+
56
+ # Dependency to get the agent instance, making endpoints cleaner
57
+ async def get_agent() -> BaseAgent:
58
+ return await create_agent()
59
+
60
+ # A single dependency for both auth and agent instance
61
+ class AgentDeps:
62
+ def __init__(self, token: bool = Depends(get_api_token), agent: BaseAgent = Depends(get_agent)):
63
+ self.agent = agent
64
+
65
+ # --- Refactored Agent Endpoints ---
66
+
67
+ @router.get("/messages", response_model=List[Dict[str, Any]])
68
+ async def get_agent_messages(deps: AgentDeps = Depends()):
69
+ """Get agent's detailed message processing history."""
70
+ return await deps.agent.get_message_history()
71
+
72
+ @router.get("/context", response_model=List[Dict[str, Any]])
73
+ async def get_agent_context(deps: AgentDeps = Depends()):
74
+ """Get agent's recent conversation context (last 20 entries)."""
75
+ return await deps.agent.get_message_history(limit=20)
76
+
77
+ @router.delete("/messages")
78
+ async def clear_agent_messages(deps: AgentDeps = Depends()):
79
+ """Clear agent's message history."""
80
+ await deps.agent.reset_memory()
81
+ return {"status": "success", "message": "Agent memory reset successfully"}
82
+
83
+ @router.post("/messages")
84
+ async def send_message_to_agent(message: MessageItem, deps: AgentDeps = Depends()):
85
+ """Send a message to the agent."""
86
+ response = await deps.agent.process_messages([message.dict()])
87
+ return {"response": response}
88
+
89
+ @router.get("/memory/init", response_model=Dict[str, Any])
90
+ async def get_init_memory(deps: AgentDeps = Depends()):
91
+ """Get initialization memory content."""
92
+ return await deps.agent.get_init_memory()
93
+
94
+ @router.put("/memory/init")
95
+ async def update_init_memory(request: InitMemoryUpdateRequest, deps: AgentDeps = Depends()):
96
+ """Update initialization memory content."""
97
+ await deps.agent.update_init_memory(request.memory)
98
+ return {"status": "success", "message": "Initialization memory updated"}
99
+
100
+ @router.get("/memory/temp", response_model=List[Dict[str, Any]])
101
+ async def get_temp_memory(deps: AgentDeps = Depends()):
102
+ """Get all temporary memory content."""
103
+ return await deps.agent.get_temp_memory()
104
+
105
+ @router.post("/memory/temp")
106
+ async def add_temp_memory_item(request: TempMemoryItem, deps: AgentDeps = Depends()):
107
+ """Add an item to temporary memory."""
108
+ await deps.agent.add_temp_memory(request.content, request.role)
109
+ return {"status": "success", "message": "Item added to temporary memory"}
110
+
111
+ @router.delete("/memory/temp")
112
+ async def clear_temp_memory(deps: AgentDeps = Depends()):
113
+ """Clear temporary memory."""
114
+ await deps.agent.clear_temp_memory()
115
+ return {"status": "success", "message": "Temporary memory cleared"}
116
+
117
+ @router.get("/memory/blocks", response_model=List[Dict[str, Any]])
118
+ async def get_memory_blocks(deps: AgentDeps = Depends()):
119
+ """Get all memory blocks."""
120
+ return await deps.agent.get_memory_blocks()
121
+
122
+ @router.get("/memory/blocks/{block_id}", response_model=Dict[str, Any])
123
+ async def get_memory_block(block_id: str, deps: AgentDeps = Depends()):
124
+ """Get a specific memory block."""
125
+ block = await deps.agent.get_memory_block(block_id)
126
+ if block is None:
127
+ raise HTTPException(status_code=404, detail="Memory block not found")
128
+ return block
129
+
130
+ @router.post("/memory/blocks", response_model=Dict[str, str])
131
+ async def create_memory_block(request: MemoryCreateRequest, deps: AgentDeps = Depends()):
132
+ """Create a new memory block."""
133
+ return await deps.agent.create_memory_block(request.title, request.description, request.content)
134
+
135
+ @router.put("/memory/blocks/{block_id}")
136
+ async def update_memory_block(block_id: str, request: MemoryUpdateRequest, deps: AgentDeps = Depends()):
137
+ """Update a memory block."""
138
+ await deps.agent.update_memory_block(block_id, request.title, request.description, request.content)
139
+ return {"status": "success"}
140
+
141
+ @router.delete("/memory/blocks/{block_id}")
142
+ async def delete_memory_block(block_id: str, deps: AgentDeps = Depends()):
143
+ """Delete a memory block."""
144
+ await deps.agent.delete_memory_block(block_id)
145
+ return {"status": "success"}
146
+
147
+ @router.post("/reset_memory")
148
+ async def reset_agent_memory(deps: AgentDeps = Depends()):
149
+ """Reset all agent memory types."""
150
+ await deps.agent.reset_memory()
151
+ return {"status": "success", "message": "Agent memory reset successfully"}
152
+
153
+ @router.get("/tools")
154
+ async def get_available_tools(deps: AgentDeps = Depends()):
155
+ """Get list of available tools."""
156
+ # Return in the format expected by the frontend
157
+ return {"tools": await deps.agent.get_available_tools()}
158
+
159
+ @router.post("/tools/execute")
160
+ async def execute_tool(request: ToolExecutionRequest, deps: AgentDeps = Depends()):
161
+ """Execute a tool with given parameters."""
162
+ result = await deps.agent.execute_tool(request.tool_name, request.params)
163
+ return {"result": result}
@@ -0,0 +1,55 @@
1
+ # neuro_simulator/api/stream.py
2
+ """API endpoints for controlling the live stream lifecycle."""
3
+
4
+ import asyncio
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ from fastapi import APIRouter, Depends
9
+
10
+ from ..core.agent_factory import create_agent
11
+ from ..utils.process import process_manager
12
+ from .agent import get_api_token # Re-using the auth dependency from agent API
13
+
14
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
15
+ router = APIRouter(prefix="/api/stream", tags=["Stream Control"])
16
+
17
+ @router.post("/start", dependencies=[Depends(get_api_token)])
18
+ async def start_stream():
19
+ """Starts the live stream processes."""
20
+ try:
21
+ agent = await create_agent()
22
+ await agent.reset_memory()
23
+ except Exception as e:
24
+ logger.error(f"Could not reset agent memory on stream start: {e}", exc_info=True)
25
+
26
+ if not process_manager.is_running:
27
+ process_manager.start_live_processes()
28
+ return {"status": "success", "message": "Stream started"}
29
+ else:
30
+ return {"status": "info", "message": "Stream is already running"}
31
+
32
+ @router.post("/stop", dependencies=[Depends(get_api_token)])
33
+ async def stop_stream():
34
+ """Stops the live stream processes."""
35
+ if process_manager.is_running:
36
+ process_manager.stop_live_processes()
37
+ return {"status": "success", "message": "Stream stopped"}
38
+ else:
39
+ return {"status": "info", "message": "Stream is not running"}
40
+
41
+ @router.post("/restart", dependencies=[Depends(get_api_token)])
42
+ async def restart_stream():
43
+ """Restarts the live stream processes."""
44
+ process_manager.stop_live_processes()
45
+ await asyncio.sleep(1) # Give time for tasks to cancel
46
+ process_manager.start_live_processes()
47
+ return {"status": "success", "message": "Stream restarted"}
48
+
49
+ @router.get("/status", dependencies=[Depends(get_api_token)])
50
+ async def get_stream_status():
51
+ """Gets the current status of the stream."""
52
+ return {
53
+ "is_running": process_manager.is_running,
54
+ "backend_status": "running" if process_manager.is_running else "stopped"
55
+ }
@@ -0,0 +1,90 @@
1
+ # neuro_simulator/api/system.py
2
+ """API endpoints for system, config, and utility functions."""
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException
5
+ from pydantic import BaseModel
6
+ import time
7
+
8
+ from ..core.config import config_manager, AppSettings
9
+ from ..services.audio import synthesize_audio_segment
10
+ from .agent import get_api_token # Re-using the auth dependency
11
+
12
+ router = APIRouter(tags=["System & Utilities"])
13
+
14
+ # --- TTS Endpoint ---
15
+
16
+ class SpeechRequest(BaseModel):
17
+ text: str
18
+ voice_name: str | None = None
19
+ pitch: float | None = None
20
+
21
+ @router.post("/api/tts/synthesize", dependencies=[Depends(get_api_token)])
22
+ async def synthesize_speech_endpoint(request: SpeechRequest):
23
+ """Synthesizes text to speech using the configured TTS service."""
24
+ try:
25
+ audio_base64, _ = await synthesize_audio_segment(
26
+ text=request.text, voice_name=request.voice_name, pitch=request.pitch
27
+ )
28
+ return {"audio_base64": audio_base64}
29
+ except Exception as e:
30
+ raise HTTPException(status_code=500, detail=str(e))
31
+
32
+ # --- Config Management Endpoints ---
33
+
34
+ # This helper can be moved to a more central location if needed
35
+ def filter_config_for_frontend(settings: AppSettings):
36
+ """Filters the settings to return only the fields safe for the frontend."""
37
+ # Using .model_dump() with include is a more robust Pydantic approach
38
+ return settings.model_dump(include={
39
+ 'stream_metadata': {'stream_title', 'stream_category', 'stream_tags'},
40
+ 'agent': {'agent_provider', 'agent_model'},
41
+ 'neuro_behavior': {'input_chat_sample_size', 'post_speech_cooldown_sec', 'initial_greeting'},
42
+ 'audience_simulation': {'llm_provider', 'gemini_model', 'openai_model', 'llm_temperature', 'chat_generation_interval_sec', 'chats_per_batch', 'max_output_tokens', 'username_blocklist', 'username_pool'},
43
+ 'performance': {'neuro_input_queue_max_size', 'audience_chat_buffer_max_size', 'initial_chat_backlog_limit'}
44
+ })
45
+
46
+ @router.get("/api/configs", dependencies=[Depends(get_api_token)])
47
+ async def get_configs():
48
+ """Gets the current, frontend-safe configuration."""
49
+ return filter_config_for_frontend(config_manager.settings)
50
+
51
+ @router.patch("/api/configs", dependencies=[Depends(get_api_token)])
52
+ async def update_configs(new_settings: dict):
53
+ """Updates the configuration with new values from the frontend."""
54
+ try:
55
+ await config_manager.update_settings(new_settings)
56
+ return filter_config_for_frontend(config_manager.settings)
57
+ except Exception as e:
58
+ raise HTTPException(status_code=500, detail=f"Failed to update settings: {str(e)}")
59
+
60
+ @router.post("/api/configs/reload", dependencies=[Depends(get_api_token)])
61
+ async def reload_configs():
62
+ """Triggers a reload of the configuration from the config.yaml file."""
63
+ try:
64
+ # Passing an empty dict forces a reload and triggers callbacks
65
+ await config_manager.update_settings({})
66
+ return {"status": "success", "message": "Configuration reloaded"}
67
+ except Exception as e:
68
+ raise HTTPException(status_code=500, detail=f"Failed to reload settings: {str(e)}")
69
+
70
+ # --- System Endpoints ---
71
+
72
+ @router.get("/api/system/health")
73
+ async def health_check():
74
+ """Provides a simple health check of the server."""
75
+ from ..utils.process import process_manager
76
+ return {
77
+ "status": "healthy",
78
+ "backend_running": True,
79
+ "process_manager_running": process_manager.is_running,
80
+ "timestamp": time.time()
81
+ }
82
+
83
+ @router.get("/")
84
+ async def root():
85
+ """Returns basic information about the API."""
86
+ return {
87
+ "message": "Neuro-Sama Simulator Backend",
88
+ "version": "2.0",
89
+ "api_docs": "/docs",
90
+ }
neuro_simulator/cli.py CHANGED
@@ -1,177 +1,88 @@
1
1
  #!/usr/bin/env python3
2
+ """Command-line interface for the Neuro-Simulator Server."""
2
3
 
3
4
  import argparse
5
+ import logging
4
6
  import os
5
- import sys
6
7
  import shutil
8
+ import sys
7
9
  from pathlib import Path
8
10
 
11
+ def copy_resource(package_name: str, resource_path: str, destination_path: Path, is_dir: bool = False):
12
+ """A helper function to copy a resource from the package to the working directory."""
13
+ if destination_path.exists():
14
+ return
15
+
16
+ try:
17
+ import pkg_resources
18
+ source_path_str = pkg_resources.resource_filename(package_name, resource_path)
19
+ source_path = Path(source_path_str)
20
+ except (ModuleNotFoundError, KeyError):
21
+ source_path = Path(__file__).parent / resource_path
22
+
23
+ if source_path.exists():
24
+ try:
25
+ if is_dir:
26
+ shutil.copytree(source_path, destination_path)
27
+ else:
28
+ shutil.copy(source_path, destination_path)
29
+ logging.info(f"Created '{destination_path}' from package resource.")
30
+ except Exception as e:
31
+ logging.warning(f"Could not copy resource '{resource_path}'. Error: {e}")
32
+ else:
33
+ logging.warning(f"Resource '{resource_path}' not found in package or development folder.")
9
34
 
10
35
  def main():
36
+ """Main entry point for the CLI."""
11
37
  parser = argparse.ArgumentParser(description="Neuro-Simulator Server")
12
- parser.add_argument("-D", "--dir", help="Working directory containing config.yaml")
38
+ parser.add_argument("-D", "--dir", help="Working directory for config and data")
13
39
  parser.add_argument("-H", "--host", help="Host to bind the server to")
14
40
  parser.add_argument("-P", "--port", type=int, help="Port to bind the server to")
15
41
 
16
42
  args = parser.parse_args()
17
43
 
18
- # Set working directory
44
+ # 1. Set working directory
19
45
  if args.dir:
20
46
  work_dir = Path(args.dir).resolve()
21
- # If the directory doesn't exist (and it's not the default), raise an error
22
47
  if not work_dir.exists():
23
- print(f"Error: Working directory '{work_dir}' does not exist. Please create it manually.")
48
+ logging.error(f"Working directory '{work_dir}' does not exist. Please create it first.")
24
49
  sys.exit(1)
25
50
  else:
26
51
  work_dir = Path.home() / ".config" / "neuro-simulator"
27
52
  work_dir.mkdir(parents=True, exist_ok=True)
28
53
 
29
- # Change to working directory
30
54
  os.chdir(work_dir)
31
-
32
- # Handle config.yaml.example
33
- settings_example_path = work_dir / "config.yaml.example"
34
- settings_path = work_dir / "config.yaml"
35
-
36
- # Copy config.yaml.example from package if it doesn't exist
37
- if not settings_example_path.exists():
38
- try:
39
- # Try pkg_resources first (for installed packages)
40
- try:
41
- import pkg_resources
42
- example_path = pkg_resources.resource_filename('neuro_simulator', 'config.yaml.example')
43
- if os.path.exists(example_path):
44
- shutil.copy(example_path, settings_example_path)
45
- print(f"Created {settings_example_path} from package example")
46
- else:
47
- # Fallback to relative path (for development mode)
48
- dev_example_path = Path(__file__).parent / "config.yaml.example"
49
- if dev_example_path.exists():
50
- shutil.copy(dev_example_path, settings_example_path)
51
- print(f"Created {settings_example_path} from development example")
52
- else:
53
- print("Warning: config.yaml.example not found in package or development folder")
54
- except Exception:
55
- # Fallback to relative path (for development mode)
56
- dev_example_path = Path(__file__).parent / "config.yaml.example"
57
- if dev_example_path.exists():
58
- shutil.copy(dev_example_path, settings_example_path)
59
- print(f"Created {settings_example_path} from development example")
60
- else:
61
- print("Warning: config.yaml.example not found in package or development folder")
62
- except Exception as e:
63
- print(f"Warning: Could not copy config.yaml.example from package: {e}")
64
-
65
- # Handle media folder
66
- media_dir = work_dir / "media"
67
- video_path = media_dir / "neuro_start.mp4"
68
-
69
- # Copy media folder from package if it doesn't exist or is invalid
70
- if not media_dir.exists() or not video_path.exists():
71
- # If media dir exists but video doesn't, remove the incomplete media dir
72
- if media_dir.exists():
73
- shutil.rmtree(media_dir)
74
-
75
- try:
76
- # Try pkg_resources first (for installed packages)
77
- try:
78
- import pkg_resources
79
- package_media_path = pkg_resources.resource_filename('neuro_simulator', 'media')
80
- if os.path.exists(package_media_path):
81
- shutil.copytree(package_media_path, media_dir)
82
- print(f"Created {media_dir} from package media")
83
- else:
84
- # Fallback to relative path (for development mode)
85
- dev_media_path = Path(__file__).parent / "media"
86
- if dev_media_path.exists():
87
- shutil.copytree(dev_media_path, media_dir)
88
- print(f"Created {media_dir} from development media")
89
- else:
90
- print("Warning: media folder not found in package or development folder")
91
- except Exception:
92
- # Fallback to relative path (for development mode)
93
- dev_media_path = Path(__file__).parent / "media"
94
- if dev_media_path.exists():
95
- shutil.copytree(dev_media_path, media_dir)
96
- print(f"Created {media_dir} from development media")
97
- else:
98
- print("Warning: media folder not found in package or development folder")
99
- except Exception as e:
100
- print(f"Warning: Could not copy media folder from package: {e}")
101
-
102
- # Handle agent/memory directory and example JSON files
55
+ logging.info(f"Using working directory: {work_dir}")
56
+
57
+ # 2. Ensure required assets and configs exist
58
+ copy_resource('neuro_simulator', 'core/config.yaml.example', work_dir / 'config.yaml.example')
59
+ copy_resource('neuro_simulator', 'assets', work_dir / 'assets', is_dir=True)
103
60
  agent_memory_dir = work_dir / "agent" / "memory"
104
61
  agent_memory_dir.mkdir(parents=True, exist_ok=True)
105
-
106
- # List of example JSON files to copy
107
- example_memory_files = [
108
- "context.json",
109
- "core_memory.json",
110
- "dialog_history.json",
111
- "init_memory.json"
112
- ]
113
-
114
- # Copy each example memory file if it doesn't exist
115
- for filename in example_memory_files:
116
- target_path = agent_memory_dir / filename
117
- if not target_path.exists():
118
- try:
119
- # Try pkg_resources first (for installed packages)
120
- try:
121
- import pkg_resources
122
- package_example_path = pkg_resources.resource_filename('neuro_simulator', f'agent/memory/{filename}')
123
- if os.path.exists(package_example_path):
124
- shutil.copy(package_example_path, target_path)
125
- print(f"Created {target_path} from package example")
126
- else:
127
- # Fallback to relative path (for development mode)
128
- dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
129
- if dev_example_path.exists():
130
- shutil.copy(dev_example_path, target_path)
131
- print(f"Created {target_path} from development example")
132
- else:
133
- print(f"Warning: {filename} not found in package or development folder")
134
- except Exception:
135
- # Fallback to relative path (for development mode)
136
- dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
137
- if dev_example_path.exists():
138
- shutil.copy(dev_example_path, target_path)
139
- print(f"Created {target_path} from development example")
140
- else:
141
- print(f"Warning: {filename} not found in package or development folder")
142
- except Exception as e:
143
- print(f"Warning: Could not copy {filename} from package: {e}")
144
-
145
- # Now check for required files and handle errors appropriately
62
+ for filename in ["context.json", "core_memory.json", "dialog_history.json", "init_memory.json"]:
63
+ copy_resource('neuro_simulator', f'agent/memory/{filename}', agent_memory_dir / filename)
64
+
65
+ # 3. Validate essential files
146
66
  errors = []
147
-
148
- # Check for config.yaml (required for running)
149
- if not settings_path.exists():
150
- if settings_example_path.exists():
151
- errors.append(f"Error: {settings_path} not found. Please copy {settings_example_path} to {settings_path} and configure it.")
152
- else:
153
- errors.append(f"Error: Neither {settings_path} nor {settings_example_path} found. Please ensure proper configuration.")
154
-
155
- # Check for required media files (required for running)
156
- if not media_dir.exists() or not video_path.exists():
157
- errors.append(f"Error: Required media files not found in {media_dir}.")
158
-
159
- # If there are any errors, print them and exit
67
+ if not (work_dir / "config.yaml").exists():
68
+ errors.append(f"'config.yaml' not found in '{work_dir}'. Please copy 'config.yaml.example' to 'config.yaml' and configure it.")
69
+ if not (work_dir / "assets" / "neuro_start.mp4").exists():
70
+ errors.append(f"Required file 'neuro_start.mp4' not found in '{work_dir / 'assets'}'.")
71
+
160
72
  if errors:
161
73
  for error in errors:
162
- print(error)
74
+ logging.error(error)
163
75
  sys.exit(1)
164
-
165
- # Import and run the main application
76
+
77
+ # 4. Import and run the server
166
78
  try:
167
- from neuro_simulator.main import run_server
79
+ from neuro_simulator.core.application import run_server
80
+ logging.info("Starting Neuro-Simulator server...")
81
+ # The full application logger will take over from here
168
82
  run_server(args.host, args.port)
169
- except ImportError:
170
- # Fallback for development mode
171
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
172
- from neuro_simulator.main import run_server
173
- run_server(args.host, args.port)
174
-
83
+ except ImportError as e:
84
+ logging.error(f"Could not import the application. Make sure the package is installed correctly. Details: {e}", exc_info=True)
85
+ sys.exit(1)
175
86
 
176
87
  if __name__ == "__main__":
177
88
  main()
@@ -0,0 +1 @@
1
+ # This file makes the 'core' directory a Python package.