signalwire-agents 1.0.10__py3-none-any.whl → 1.0.11__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.
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Copyright (c) 2025 SignalWire
4
+
5
+ This file is part of the SignalWire AI Agents SDK.
6
+
7
+ Licensed under the MIT License.
8
+ See LICENSE file in the project root for full license information.
9
+ """
10
+
11
+ """
12
+ Session Manager for MCP Gateway
13
+
14
+ Manages lifecycle of MCP server sessions tied to SignalWire call IDs.
15
+ Handles timeouts, cleanup, and resource limits.
16
+ """
17
+
18
+ import threading
19
+ import time
20
+ import logging
21
+ from typing import Dict, Optional, Any
22
+ from datetime import datetime, timedelta
23
+ from dataclasses import dataclass, field
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class Session:
30
+ """Represents an active MCP session"""
31
+ session_id: str
32
+ service_name: str
33
+ process: Any # MCPClient instance
34
+ created_at: datetime = field(default_factory=datetime.now)
35
+ last_accessed: datetime = field(default_factory=datetime.now)
36
+ timeout: int = 300 # seconds
37
+ metadata: Dict[str, Any] = field(default_factory=dict)
38
+
39
+ @property
40
+ def is_expired(self) -> bool:
41
+ """Check if session has expired based on timeout"""
42
+ return datetime.now() > self.last_accessed + timedelta(seconds=self.timeout)
43
+
44
+ @property
45
+ def is_alive(self) -> bool:
46
+ """Check if the underlying MCP client is still running"""
47
+ return self.process and self.process.process and self.process.process.poll() is None
48
+
49
+ def touch(self):
50
+ """Update last accessed time"""
51
+ self.last_accessed = datetime.now()
52
+
53
+
54
+ class SessionManager:
55
+ """Manages MCP server sessions with automatic cleanup"""
56
+
57
+ def __init__(self, config: Dict[str, Any]):
58
+ self.config = config
59
+ self.sessions: Dict[str, Session] = {}
60
+ self.lock = threading.RLock()
61
+ self.cleanup_interval = config.get('session', {}).get('cleanup_interval', 60)
62
+ self.max_sessions_per_service = config.get('session', {}).get('max_sessions_per_service', 100)
63
+ self.default_timeout = config.get('session', {}).get('default_timeout', 300)
64
+ self._shutdown = threading.Event()
65
+
66
+ # Start cleanup thread
67
+ self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
68
+ self.cleanup_thread.start()
69
+
70
+ logger.info(f"SessionManager initialized with cleanup_interval={self.cleanup_interval}s")
71
+
72
+ def create_session(self, session_id: str, service_name: str, process: Any,
73
+ timeout: Optional[int] = None, metadata: Optional[Dict[str, Any]] = None) -> Session:
74
+ """Create and register a new session"""
75
+ with self.lock:
76
+ # Check if session already exists
77
+ if session_id in self.sessions:
78
+ logger.warning(f"Session {session_id} already exists, closing old session")
79
+ self.close_session(session_id)
80
+
81
+ # Check service limits
82
+ service_count = sum(1 for s in self.sessions.values() if s.service_name == service_name)
83
+ if service_count >= self.max_sessions_per_service:
84
+ raise RuntimeError(f"Max sessions limit reached for service {service_name}")
85
+
86
+ # Create new session
87
+ session = Session(
88
+ session_id=session_id,
89
+ service_name=service_name,
90
+ process=process,
91
+ timeout=timeout or self.default_timeout,
92
+ metadata=metadata or {}
93
+ )
94
+
95
+ self.sessions[session_id] = session
96
+ logger.info(f"Created session {session_id} for service {service_name}")
97
+
98
+ return session
99
+
100
+ def get_session(self, session_id: str) -> Optional[Session]:
101
+ """Get an active session by ID"""
102
+ with self.lock:
103
+ session = self.sessions.get(session_id)
104
+
105
+ if session:
106
+ if not session.is_alive:
107
+ logger.warning(f"Session {session_id} process is dead, removing")
108
+ self.close_session(session_id)
109
+ return None
110
+
111
+ if session.is_expired:
112
+ logger.info(f"Session {session_id} has expired, removing")
113
+ self.close_session(session_id)
114
+ return None
115
+
116
+ # Update last accessed time
117
+ session.touch()
118
+
119
+ return session
120
+
121
+ def close_session(self, session_id: str) -> bool:
122
+ """Close and remove a session"""
123
+ with self.lock:
124
+ session = self.sessions.pop(session_id, None)
125
+
126
+ if not session:
127
+ logger.warning(f"Attempted to close non-existent session {session_id}")
128
+ return False
129
+
130
+ # Terminate the MCP client
131
+ if session.process:
132
+ try:
133
+ # Stop the MCP client
134
+ session.process.stop()
135
+ except Exception as e:
136
+ logger.error(f"Error stopping MCP client for session {session_id}: {e}")
137
+
138
+ logger.info(f"Closed session {session_id}")
139
+ return True
140
+
141
+ def list_sessions(self) -> Dict[str, Dict[str, Any]]:
142
+ """List all active sessions with their info"""
143
+ with self.lock:
144
+ result = {}
145
+
146
+ for session_id, session in list(self.sessions.items()):
147
+ # Check if still valid
148
+ if not session.is_alive or session.is_expired:
149
+ self.close_session(session_id)
150
+ continue
151
+
152
+ result[session_id] = {
153
+ 'service_name': session.service_name,
154
+ 'created_at': session.created_at.isoformat(),
155
+ 'last_accessed': session.last_accessed.isoformat(),
156
+ 'timeout': session.timeout,
157
+ 'metadata': session.metadata,
158
+ 'time_remaining': max(0, (session.last_accessed + timedelta(seconds=session.timeout) - datetime.now()).total_seconds())
159
+ }
160
+
161
+ return result
162
+
163
+ def get_service_session_count(self, service_name: str) -> int:
164
+ """Get number of active sessions for a service"""
165
+ with self.lock:
166
+ return sum(1 for s in self.sessions.values()
167
+ if s.service_name == service_name and s.is_alive and not s.is_expired)
168
+
169
+ def _cleanup_loop(self):
170
+ """Background thread that cleans up expired sessions"""
171
+ logger.info("Session cleanup thread started")
172
+
173
+ while not self._shutdown.is_set():
174
+ try:
175
+ # Wait with timeout so we can check shutdown flag
176
+ if self._shutdown.wait(timeout=self.cleanup_interval):
177
+ break
178
+
179
+ with self.lock:
180
+ expired_sessions = []
181
+
182
+ for session_id, session in self.sessions.items():
183
+ if session.is_expired or not session.is_alive:
184
+ expired_sessions.append(session_id)
185
+
186
+ for session_id in expired_sessions:
187
+ logger.info(f"Cleaning up expired session {session_id}")
188
+ self.close_session(session_id)
189
+
190
+ if expired_sessions:
191
+ logger.info(f"Cleaned up {len(expired_sessions)} expired sessions")
192
+
193
+ except Exception as e:
194
+ logger.error(f"Error in cleanup thread: {e}")
195
+
196
+ logger.info("Session cleanup thread stopped")
197
+
198
+ def shutdown(self):
199
+ """Shutdown all sessions and cleanup"""
200
+ logger.info("Shutting down SessionManager")
201
+
202
+ # Signal cleanup thread to stop
203
+ self._shutdown.set()
204
+
205
+ with self.lock:
206
+ # Close all sessions
207
+ session_ids = list(self.sessions.keys())
208
+ logger.info(f"Closing {len(session_ids)} active sessions")
209
+ for session_id in session_ids:
210
+ self.close_session(session_id)
211
+
212
+ # Wait for cleanup thread to finish (with timeout)
213
+ if self.cleanup_thread.is_alive():
214
+ self.cleanup_thread.join(timeout=2.0)
215
+ if self.cleanup_thread.is_alive():
216
+ logger.warning("Cleanup thread did not stop gracefully")
217
+
218
+ logger.info("SessionManager shutdown complete")
@@ -1,7 +1,7 @@
1
1
  .\" Man page for sw-agent-init
2
2
  .\" Copyright (c) 2025 SignalWire
3
3
  .\" Licensed under the MIT License
4
- .TH SW-AGENT-INIT 1 "November 2025" "SignalWire Agents SDK 1.0.6" "SignalWire Commands"
4
+ .TH SW-AGENT-INIT 1 "November 2025" "SignalWire Agents SDK 1.0.8" "SignalWire Commands"
5
5
  .SH NAME
6
6
  sw-agent-init \- create a new SignalWire AI agent project
7
7
  .SH SYNOPSIS
@@ -14,14 +14,14 @@ sw-agent-init \- create a new SignalWire AI agent project
14
14
  .SH DESCRIPTION
15
15
  .B sw-agent-init
16
16
  is an interactive command-line tool for creating new SignalWire AI agent
17
- projects. It generates a complete project structure with all the necessary
18
- files to get started building voice AI agents.
17
+ projects. It supports both local agent projects (FastAPI/uvicorn) and
18
+ cloud function deployments (AWS Lambda, Google Cloud Functions, Azure Functions).
19
19
  .PP
20
20
  When run without arguments, the tool enters interactive mode and prompts
21
- for project configuration. When given a project name, it runs in quick
22
- mode with sensible defaults.
21
+ for project configuration including platform selection. When given a project
22
+ name, it runs in quick mode with sensible defaults (local platform by default).
23
23
  .PP
24
- The generated project includes:
24
+ For local projects, the generated project includes:
25
25
  .IP \(bu 2
26
26
  Agent module with sample SWAIG function
27
27
  .IP \(bu 2
@@ -46,8 +46,36 @@ interactive mode and prompts for a name.
46
46
  .BR \-h ", " \-\-help
47
47
  Display help message and exit.
48
48
  .TP
49
+ .BI \-p ", " \-\-platform " PLATFORM"
50
+ Target deployment platform. Valid values:
51
+ .RS
52
+ .IP \(bu 2
53
+ .B local
54
+ \- Local agent with FastAPI/uvicorn server (default)
55
+ .IP \(bu 2
56
+ .B aws
57
+ \- AWS Lambda function with API Gateway
58
+ .IP \(bu 2
59
+ .B gcp
60
+ \- Google Cloud Function (Gen 2)
61
+ .IP \(bu 2
62
+ .B azure
63
+ \- Azure Function
64
+ .RE
65
+ .TP
66
+ .BI \-r ", " \-\-region " REGION"
67
+ Cloud region for deployment (only for cloud platforms). Default regions:
68
+ .RS
69
+ .IP \(bu 2
70
+ AWS: us-east-1
71
+ .IP \(bu 2
72
+ GCP: us-central1
73
+ .IP \(bu 2
74
+ Azure: eastus
75
+ .RE
76
+ .TP
49
77
  .BI \-\-type " TYPE"
50
- Agent type to create. Valid values:
78
+ Agent type to create (only for local platform). Valid values:
51
79
  .RS
52
80
  .IP \(bu 2
53
81
  .B basic
@@ -58,8 +86,8 @@ Agent type to create. Valid values:
58
86
  .RE
59
87
  .TP
60
88
  .B \-\-no\-venv
61
- Skip virtual environment creation. By default, a Python virtual
62
- environment is created in the project directory.
89
+ Skip virtual environment creation (only for local platform). By default,
90
+ a Python virtual environment is created in the project directory.
63
91
  .TP
64
92
  .BI \-\-dir " DIRECTORY"
65
93
  Parent directory for the project. The project will be created as
@@ -68,14 +96,20 @@ a subdirectory. Default is the current directory.
68
96
  When run without a project name, the tool prompts for:
69
97
  .SS Project Configuration
70
98
  .IP \(bu 2
99
+ .B Target platform
100
+ \- Local, AWS Lambda, GCP, or Azure
101
+ .IP \(bu 2
71
102
  .B Project name
72
103
  \- Name of the project directory
73
104
  .IP \(bu 2
74
105
  .B Project directory
75
106
  \- Where to create the project
76
107
  .IP \(bu 2
108
+ .B Region
109
+ \- Cloud region (for cloud platforms only)
110
+ .IP \(bu 2
77
111
  .B Agent type
78
- \- Basic or full setup
112
+ \- Basic or full setup (for local platform only)
79
113
  .SS Feature Selection
80
114
  The following features can be enabled or disabled:
81
115
  .IP \(bu 2
@@ -189,8 +223,8 @@ Run without arguments for guided setup:
189
223
  sw-agent-init
190
224
  .fi
191
225
  .RE
192
- .SS Quick Mode
193
- Create a basic agent project:
226
+ .SS Quick Mode - Local
227
+ Create a basic local agent project:
194
228
  .PP
195
229
  .RS
196
230
  .nf
@@ -198,7 +232,7 @@ sw-agent-init myagent
198
232
  .fi
199
233
  .RE
200
234
  .PP
201
- Create a full-featured project:
235
+ Create a full-featured local project:
202
236
  .PP
203
237
  .RS
204
238
  .nf
@@ -221,7 +255,39 @@ Create in a specific directory:
221
255
  sw-agent-init myagent --dir /opt/projects
222
256
  .fi
223
257
  .RE
224
- .SS After Creation
258
+ .SS Quick Mode - Cloud Functions
259
+ Create an AWS Lambda project:
260
+ .PP
261
+ .RS
262
+ .nf
263
+ sw-agent-init myagent -p aws
264
+ .fi
265
+ .RE
266
+ .PP
267
+ Create an AWS Lambda project in a specific region:
268
+ .PP
269
+ .RS
270
+ .nf
271
+ sw-agent-init myagent -p aws -r us-west-2
272
+ .fi
273
+ .RE
274
+ .PP
275
+ Create a Google Cloud Function project:
276
+ .PP
277
+ .RS
278
+ .nf
279
+ sw-agent-init myagent -p gcp
280
+ .fi
281
+ .RE
282
+ .PP
283
+ Create an Azure Function project:
284
+ .PP
285
+ .RS
286
+ .nf
287
+ sw-agent-init myagent -p azure
288
+ .fi
289
+ .RE
290
+ .SS After Creation - Local
225
291
  Start the agent:
226
292
  .PP
227
293
  .RS
@@ -251,6 +317,33 @@ swaig-test agents/main_agent.py --exec get_info --topic "test"
251
317
  swaig-test agents/main_agent.py --dump-swml
252
318
  .fi
253
319
  .RE
320
+ .SS After Creation - Cloud Functions
321
+ Deploy to AWS Lambda:
322
+ .PP
323
+ .RS
324
+ .nf
325
+ cd myagent
326
+ ./deploy.sh
327
+ .fi
328
+ .RE
329
+ .PP
330
+ Deploy to Google Cloud:
331
+ .PP
332
+ .RS
333
+ .nf
334
+ cd myagent
335
+ ./deploy.sh
336
+ .fi
337
+ .RE
338
+ .PP
339
+ Deploy to Azure:
340
+ .PP
341
+ .RS
342
+ .nf
343
+ cd myagent
344
+ ./deploy.sh
345
+ .fi
346
+ .RE
254
347
  .SH AGENT TYPES
255
348
  .SS Basic Agent
256
349
  The basic agent type includes:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 1.0.10
3
+ Version: 1.0.11
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -75,6 +75,9 @@ Requires-Dist: pgvector>=0.2.0; extra == "search-all"
75
75
  Provides-Extra: pgvector
76
76
  Requires-Dist: psycopg2-binary>=2.9.0; extra == "pgvector"
77
77
  Requires-Dist: pgvector>=0.2.0; extra == "pgvector"
78
+ Provides-Extra: mcp-gateway
79
+ Requires-Dist: flask>=2.0.0; extra == "mcp-gateway"
80
+ Requires-Dist: flask-limiter>=3.5.0; extra == "mcp-gateway"
78
81
  Provides-Extra: all
79
82
  Requires-Dist: sentence-transformers>=2.2.0; extra == "all"
80
83
  Requires-Dist: scikit-learn>=1.3.0; extra == "all"
@@ -1,11 +1,11 @@
1
- signalwire_agents/__init__.py,sha256=ayt9AO0brsom1nxCluBItWnJC9Qv_kwaTknpL6YPsQE,5031
1
+ signalwire_agents/__init__.py,sha256=gW38F5mpUdBDy9opRa56Adm8kHMib6gvWd_4zfmNW7s,5031
2
2
  signalwire_agents/agent_server.py,sha256=5GPt6XekVxep9esZY9s4MG6sOR-VF_m53U25r3WoML4,30964
3
3
  signalwire_agents/schema.json,sha256=YQv4-KiegE00XvxoLMKAml6aCGitnt3kBq31ECxTHK8,385886
4
4
  signalwire_agents/agents/bedrock.py,sha256=J582gooNtxtep4xdVOfyDzRtHp_XrurPMS93xf2Xod0,10836
5
5
  signalwire_agents/cli/__init__.py,sha256=XbxAQFaCIdGXIXJiriVBWoFPOJsC401u21588nO4TG8,388
6
6
  signalwire_agents/cli/build_search.py,sha256=wDuonVY9fcqspThRHmbArTuZLKsiStu-ozVExtqawA8,54937
7
7
  signalwire_agents/cli/config.py,sha256=2i4e0BArdKsaXxjeueYYRNke7GWicHPYC2wuitVrP7A,2541
8
- signalwire_agents/cli/init_project.py,sha256=A7Qx7UoMOG1fdWWyn4ceH6oc5BV6jVAxudPrtOVBBjM,36396
8
+ signalwire_agents/cli/init_project.py,sha256=nNBPni3x1xzdJ5pAZsQRUYK1t1e64NzwUUkq-Uc3mzo,78835
9
9
  signalwire_agents/cli/swaig_test_wrapper.py,sha256=t63HQpEc1Up5AcysEHP1OsEQcgSMKH-9H1L2IhFso18,1533
10
10
  signalwire_agents/cli/test_swaig.py,sha256=-v-XjTUWZNxmMJuOF5_cB1Jz8x8emJoqgqS_8jLeT4Y,31487
11
11
  signalwire_agents/cli/types.py,sha256=U8Abc4Atb5-wMbhM3MjcuIXsbONLOu1ucePWOCgqdco,1810
@@ -64,6 +64,10 @@ signalwire_agents/core/mixins/tool_mixin.py,sha256=6CaNdaspHcfte0qSB_bSN8PTsqxRZ
64
64
  signalwire_agents/core/mixins/web_mixin.py,sha256=FvllyWjdms5M8i37hCx9WLbeqJdd84Uymqb0aI0c6BU,50639
65
65
  signalwire_agents/core/security/__init__.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
66
66
  signalwire_agents/core/security/session_manager.py,sha256=s5hXYcFnrsYFoyo-zcN7EJy-wInZQI_cWTBHX9MxHR4,9164
67
+ signalwire_agents/mcp_gateway/__init__.py,sha256=uZhyt0cBa9Wk_OPr_Ewphh6apsaf_5O9G48NVkm_00I,619
68
+ signalwire_agents/mcp_gateway/gateway_service.py,sha256=hytoEhiXSyZxk_LxV-zo0mfCCAJf-y2Gs5gJ__iIRB0,23081
69
+ signalwire_agents/mcp_gateway/mcp_manager.py,sha256=DaR49BX5b6hn2mXJygS3idNEr3IDImWGNdsrQSYxiEE,19669
70
+ signalwire_agents/mcp_gateway/session_manager.py,sha256=sGMPT8aSzS2RRR_ewkd0nxwYI-LK5CaWrendGhXAW9c,8505
67
71
  signalwire_agents/prefabs/__init__.py,sha256=MW11J63XH7KxF2MWguRsMFM9iqMWexaEO9ynDPL_PDM,715
68
72
  signalwire_agents/prefabs/concierge.py,sha256=XB-Ziy0tteYI5NHu7Sinx-N960nzkhq2m4v8Ei8UHas,9921
69
73
  signalwire_agents/prefabs/faq_bot.py,sha256=cUuHhnDB8S4aVg-DiQe4jBmCAPrYQrND_Mff9iaeEa0,10572
@@ -131,12 +135,12 @@ signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663
131
135
  signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
132
136
  signalwire_agents/web/__init__.py,sha256=XE_pSTY9Aalzr7J7wqFth1Zr3cccQHPPcF5HWNrOpz8,383
133
137
  signalwire_agents/web/web_service.py,sha256=a2PSHJgX1tlZr0Iz1A1UouZjXEePJAZL632evvLVM38,21071
134
- signalwire_agents-1.0.10.data/data/share/man/man1/sw-agent-init.1,sha256=EFE8iOj2aGrZRwSXRoZdrZTZXOROuQmuIrZuzp9yI2s,6880
135
- signalwire_agents-1.0.10.data/data/share/man/man1/sw-search.1,sha256=9jJ6V6t6DgmXByz8Lw9exjf683Cw3sJGro8-eB0M9EY,10413
136
- signalwire_agents-1.0.10.data/data/share/man/man1/swaig-test.1,sha256=Ri0EITo8YMFowkcYltwPSwU4VJdRzo7XTWloi5WddCg,7815
137
- signalwire_agents-1.0.10.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
138
- signalwire_agents-1.0.10.dist-info/METADATA,sha256=WetB2a73mHJZC_2t_qiZIqgVxyp8v8CC2adEl25TRzw,41600
139
- signalwire_agents-1.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
140
- signalwire_agents-1.0.10.dist-info/entry_points.txt,sha256=GDaOxm8I9Bf3Sd2jDSGRXDG2ezDMgmX6ockjpDfK_IY,200
141
- signalwire_agents-1.0.10.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
142
- signalwire_agents-1.0.10.dist-info/RECORD,,
138
+ signalwire_agents-1.0.11.data/data/share/man/man1/sw-agent-init.1,sha256=J4k5Oi74BnWCPCvsaw00vuyyqDPuIECjJIPu5OynvJc,8381
139
+ signalwire_agents-1.0.11.data/data/share/man/man1/sw-search.1,sha256=9jJ6V6t6DgmXByz8Lw9exjf683Cw3sJGro8-eB0M9EY,10413
140
+ signalwire_agents-1.0.11.data/data/share/man/man1/swaig-test.1,sha256=Ri0EITo8YMFowkcYltwPSwU4VJdRzo7XTWloi5WddCg,7815
141
+ signalwire_agents-1.0.11.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
142
+ signalwire_agents-1.0.11.dist-info/METADATA,sha256=s_HNeVZzGACli-KI2PbUvToviz9aFZvigOcCtd__lIw,41740
143
+ signalwire_agents-1.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
144
+ signalwire_agents-1.0.11.dist-info/entry_points.txt,sha256=TDmLd7TVuj2AWfhxhaw6C26Do6oTdSQltsB4V7jpOjw,265
145
+ signalwire_agents-1.0.11.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
146
+ signalwire_agents-1.0.11.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  [console_scripts]
2
+ mcp-gateway = signalwire_agents.mcp_gateway.gateway_service:main
2
3
  sw-agent-init = signalwire_agents.cli.init_project:main
3
4
  sw-search = signalwire_agents.cli.build_search:console_entry_point
4
5
  swaig-test = signalwire_agents.cli.swaig_test_wrapper:main