sudosu 0.1.5__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.
- sudosu/__init__.py +3 -0
- sudosu/cli.py +561 -0
- sudosu/commands/__init__.py +15 -0
- sudosu/commands/agent.py +318 -0
- sudosu/commands/config.py +96 -0
- sudosu/commands/init.py +73 -0
- sudosu/commands/integrations.py +563 -0
- sudosu/commands/memory.py +170 -0
- sudosu/commands/onboarding.py +319 -0
- sudosu/commands/tasks.py +635 -0
- sudosu/core/__init__.py +238 -0
- sudosu/core/agent_loader.py +263 -0
- sudosu/core/connection.py +196 -0
- sudosu/core/default_agent.py +541 -0
- sudosu/core/prompt_refiner.py +0 -0
- sudosu/core/safety.py +75 -0
- sudosu/core/session.py +205 -0
- sudosu/tools/__init__.py +373 -0
- sudosu/ui/__init__.py +451 -0
- sudosu-0.1.5.dist-info/METADATA +172 -0
- sudosu-0.1.5.dist-info/RECORD +25 -0
- sudosu-0.1.5.dist-info/WHEEL +5 -0
- sudosu-0.1.5.dist-info/entry_points.txt +2 -0
- sudosu-0.1.5.dist-info/licenses/LICENSE +21 -0
- sudosu-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"""Integration command handlers for external services.
|
|
2
|
+
|
|
3
|
+
Supports: Gmail, Slack, Notion, Linear, GitHub, Google Drive, Google Docs,
|
|
4
|
+
Google Sheets, and any other Composio-configured tool.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
import webbrowser
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from sudosu.core import get_config_value, set_config_value, get_backend_url
|
|
15
|
+
from sudosu.ui import (
|
|
16
|
+
console,
|
|
17
|
+
print_error,
|
|
18
|
+
print_info,
|
|
19
|
+
print_success,
|
|
20
|
+
print_warning,
|
|
21
|
+
COLOR_PRIMARY,
|
|
22
|
+
COLOR_SECONDARY,
|
|
23
|
+
COLOR_ACCENT,
|
|
24
|
+
COLOR_INTERACTIVE,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_http_backend_url() -> str:
|
|
29
|
+
"""Get the HTTP backend URL derived from the WebSocket URL.
|
|
30
|
+
|
|
31
|
+
Converts wss://example.com/ws -> https://example.com
|
|
32
|
+
Converts ws://localhost:8000/ws -> http://localhost:8000
|
|
33
|
+
"""
|
|
34
|
+
# Check for explicit override first
|
|
35
|
+
explicit_url = os.environ.get("SUDOSU_BACKEND_URL")
|
|
36
|
+
if explicit_url:
|
|
37
|
+
return explicit_url.rstrip("/")
|
|
38
|
+
|
|
39
|
+
# Get the WebSocket URL from config
|
|
40
|
+
ws_url = get_backend_url()
|
|
41
|
+
|
|
42
|
+
# Convert WebSocket URL to HTTP URL
|
|
43
|
+
# wss://example.com/ws -> https://example.com
|
|
44
|
+
# ws://localhost:8000/ws -> http://localhost:8000
|
|
45
|
+
http_url = ws_url
|
|
46
|
+
|
|
47
|
+
# Replace protocol
|
|
48
|
+
if http_url.startswith("wss://"):
|
|
49
|
+
http_url = http_url.replace("wss://", "https://", 1)
|
|
50
|
+
elif http_url.startswith("ws://"):
|
|
51
|
+
http_url = http_url.replace("ws://", "http://", 1)
|
|
52
|
+
|
|
53
|
+
# Remove /ws path suffix if present
|
|
54
|
+
if http_url.endswith("/ws"):
|
|
55
|
+
http_url = http_url[:-3]
|
|
56
|
+
|
|
57
|
+
return http_url.rstrip("/")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Display names for toolkits
|
|
61
|
+
TOOLKIT_DISPLAY_NAMES = {
|
|
62
|
+
"gmail": "Gmail",
|
|
63
|
+
"slack": "Slack",
|
|
64
|
+
"notion": "Notion",
|
|
65
|
+
"linear": "Linear",
|
|
66
|
+
"github": "GitHub",
|
|
67
|
+
"googledrive": "Google Drive",
|
|
68
|
+
"googledocs": "Google Docs",
|
|
69
|
+
"googlesheets": "Google Sheets",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_display_name(toolkit: str) -> str:
|
|
74
|
+
"""Get human-readable display name for a toolkit."""
|
|
75
|
+
return TOOLKIT_DISPLAY_NAMES.get(toolkit, toolkit.title())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_user_id() -> str:
|
|
79
|
+
"""Get or create a unique user ID for this CLI installation.
|
|
80
|
+
|
|
81
|
+
The user_id is stored in ~/.sudosu/config.yaml and is used
|
|
82
|
+
to associate integrations (like Gmail) with this user.
|
|
83
|
+
"""
|
|
84
|
+
user_id = get_config_value("user_id")
|
|
85
|
+
if not user_id:
|
|
86
|
+
import uuid
|
|
87
|
+
user_id = str(uuid.uuid4())
|
|
88
|
+
set_config_value("user_id", user_id)
|
|
89
|
+
return user_id
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def get_available_integrations() -> list[dict]:
|
|
93
|
+
"""Get list of available integrations from backend.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List of dicts with integration details
|
|
97
|
+
"""
|
|
98
|
+
user_id = get_user_id()
|
|
99
|
+
backend_url = get_http_backend_url()
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
103
|
+
response = await client.get(
|
|
104
|
+
f"{backend_url}/api/integrations",
|
|
105
|
+
params={"user_id": user_id},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if response.status_code == 200:
|
|
109
|
+
return response.json()
|
|
110
|
+
else:
|
|
111
|
+
return {"available": [], "connected": [], "details": []}
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return {"available": [], "connected": [], "error": str(e)}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def check_integration_status(integration: str) -> dict:
|
|
118
|
+
"""Check the status of any integration.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
integration: Name of the integration (e.g., gmail, slack, notion)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
dict with 'connected' (bool) and other status info
|
|
125
|
+
"""
|
|
126
|
+
user_id = get_user_id()
|
|
127
|
+
backend_url = get_http_backend_url()
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
131
|
+
response = await client.get(
|
|
132
|
+
f"{backend_url}/api/integrations/{integration}/status/{user_id}",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if response.status_code == 200:
|
|
136
|
+
return response.json()
|
|
137
|
+
else:
|
|
138
|
+
return {"connected": False, "error": f"HTTP {response.status_code}"}
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return {"connected": False, "error": str(e)}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def initiate_connection(integration: str) -> dict:
|
|
145
|
+
"""Initiate OAuth connection for any integration.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
integration: Name of the integration (e.g., gmail, slack, notion)
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
dict with 'auth_url' if successful, or 'error' if failed
|
|
152
|
+
"""
|
|
153
|
+
user_id = get_user_id()
|
|
154
|
+
backend_url = get_http_backend_url()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
158
|
+
response = await client.post(
|
|
159
|
+
f"{backend_url}/api/integrations/{integration}/connect",
|
|
160
|
+
json={"user_id": user_id},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if response.status_code == 200:
|
|
164
|
+
return response.json()
|
|
165
|
+
else:
|
|
166
|
+
data = response.json()
|
|
167
|
+
return {"error": data.get("detail", f"HTTP {response.status_code}")}
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
return {"error": str(e)}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def disconnect_integration(integration: str) -> dict:
|
|
174
|
+
"""Disconnect any integration.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
integration: Name of the integration (e.g., gmail, slack, notion)
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
dict with 'success' (bool) and message
|
|
181
|
+
"""
|
|
182
|
+
user_id = get_user_id()
|
|
183
|
+
backend_url = get_http_backend_url()
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
187
|
+
response = await client.post(
|
|
188
|
+
f"{backend_url}/api/integrations/{integration}/disconnect",
|
|
189
|
+
json={"user_id": user_id},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if response.status_code == 200:
|
|
193
|
+
return response.json()
|
|
194
|
+
else:
|
|
195
|
+
data = response.json()
|
|
196
|
+
return {"success": False, "error": data.get("detail", f"HTTP {response.status_code}")}
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
return {"success": False, "error": str(e)}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def poll_for_connection(
|
|
203
|
+
integration: str = "gmail",
|
|
204
|
+
timeout: int = 120,
|
|
205
|
+
poll_interval: int = 2,
|
|
206
|
+
) -> bool:
|
|
207
|
+
"""Poll for connection completion after user authorizes in browser.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
integration: Name of the integration
|
|
211
|
+
timeout: Maximum seconds to wait
|
|
212
|
+
poll_interval: Seconds between polls
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
True if connected successfully, False otherwise
|
|
216
|
+
"""
|
|
217
|
+
start_time = time.time()
|
|
218
|
+
|
|
219
|
+
while time.time() - start_time < timeout:
|
|
220
|
+
status = await check_integration_status(integration)
|
|
221
|
+
|
|
222
|
+
if status.get("connected"):
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
await asyncio.sleep(poll_interval)
|
|
226
|
+
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def handle_connect_command(args: str = ""):
|
|
231
|
+
"""Handle /connect command - connect any integration.
|
|
232
|
+
|
|
233
|
+
Usage:
|
|
234
|
+
/connect gmail - Connect Gmail account
|
|
235
|
+
/connect slack - Connect Slack workspace
|
|
236
|
+
/connect notion - Connect Notion workspace
|
|
237
|
+
/connect linear - Connect Linear account
|
|
238
|
+
/connect github - Connect GitHub account
|
|
239
|
+
/connect - Show available integrations
|
|
240
|
+
"""
|
|
241
|
+
# Get available integrations first
|
|
242
|
+
integrations_info = await get_available_integrations()
|
|
243
|
+
available = integrations_info.get("available", [])
|
|
244
|
+
|
|
245
|
+
if "error" in integrations_info:
|
|
246
|
+
print_error(f"Failed to get integrations: {integrations_info['error']}")
|
|
247
|
+
print_info("Make sure the backend is running: cd sudosu-backend && uvicorn app.main:app")
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
if not available:
|
|
251
|
+
print_warning("No integrations available. Configure them in your Composio account.")
|
|
252
|
+
print_info("Visit: https://app.composio.dev/auth-configs")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Parse integration name from args
|
|
256
|
+
parts = args.strip().split()
|
|
257
|
+
|
|
258
|
+
# If no integration specified, show available options
|
|
259
|
+
if not parts:
|
|
260
|
+
console.print()
|
|
261
|
+
console.print(f"[bold {COLOR_SECONDARY}]━━━ Available Integrations ━━━[/bold {COLOR_SECONDARY}]")
|
|
262
|
+
console.print()
|
|
263
|
+
for toolkit in available:
|
|
264
|
+
display_name = get_display_name(toolkit)
|
|
265
|
+
console.print(f" • [{COLOR_INTERACTIVE}]{toolkit}[/{COLOR_INTERACTIVE}] - {display_name}")
|
|
266
|
+
console.print()
|
|
267
|
+
console.print("[dim]Usage: /connect <integration>[/dim]")
|
|
268
|
+
console.print("[dim]Example: /connect slack[/dim]")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
integration = parts[0].lower()
|
|
272
|
+
|
|
273
|
+
if integration not in available:
|
|
274
|
+
print_error(f"Unknown integration: {integration}")
|
|
275
|
+
print_info(f"Available integrations: {', '.join(available)}")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
display_name = get_display_name(integration)
|
|
279
|
+
|
|
280
|
+
# Check if already connected
|
|
281
|
+
print_info(f"Checking {display_name} connection status...")
|
|
282
|
+
status = await check_integration_status(integration)
|
|
283
|
+
|
|
284
|
+
if status.get("connected"):
|
|
285
|
+
print_success(f"✓ {display_name} is already connected!")
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
# Initiate connection
|
|
289
|
+
print_info(f"Initiating {display_name} connection...")
|
|
290
|
+
result = await initiate_connection(integration)
|
|
291
|
+
|
|
292
|
+
if "error" in result:
|
|
293
|
+
print_error(f"Failed to initiate connection: {result['error']}")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
auth_url = result.get("auth_url")
|
|
297
|
+
if not auth_url:
|
|
298
|
+
print_error("No authorization URL received from backend")
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
# Open browser and wait for authorization
|
|
302
|
+
console.print()
|
|
303
|
+
console.print(f"[bold cyan]━━━ {display_name} Authorization ━━━[/bold cyan]")
|
|
304
|
+
console.print()
|
|
305
|
+
console.print(f"Opening your browser to authorize {display_name} access...")
|
|
306
|
+
console.print()
|
|
307
|
+
console.print("[dim]If the browser doesn't open, visit this URL:[/dim]")
|
|
308
|
+
console.print(f"[link={auth_url}]{auth_url}[/link]")
|
|
309
|
+
console.print()
|
|
310
|
+
|
|
311
|
+
# Try to open the browser
|
|
312
|
+
try:
|
|
313
|
+
webbrowser.open(auth_url)
|
|
314
|
+
except Exception:
|
|
315
|
+
pass # URL already displayed above
|
|
316
|
+
|
|
317
|
+
# Poll for completion
|
|
318
|
+
console.print(f"[{COLOR_PRIMARY}]Waiting for authorization...[/{COLOR_PRIMARY}]", end="")
|
|
319
|
+
|
|
320
|
+
connected = False
|
|
321
|
+
timeout = 120 # 2 minutes
|
|
322
|
+
poll_interval = 2
|
|
323
|
+
start_time = time.time()
|
|
324
|
+
|
|
325
|
+
while time.time() - start_time < timeout:
|
|
326
|
+
console.print(".", end="", style=COLOR_PRIMARY)
|
|
327
|
+
|
|
328
|
+
status = await check_integration_status(integration)
|
|
329
|
+
if status.get("connected"):
|
|
330
|
+
connected = True
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
await asyncio.sleep(poll_interval)
|
|
334
|
+
|
|
335
|
+
console.print() # New line after dots
|
|
336
|
+
console.print()
|
|
337
|
+
|
|
338
|
+
if connected:
|
|
339
|
+
print_success(f"✓ {display_name} connected successfully!")
|
|
340
|
+
console.print()
|
|
341
|
+
_show_integration_examples(integration)
|
|
342
|
+
else:
|
|
343
|
+
print_warning(f"Connection timed out. Please try again with /connect {integration}")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _show_integration_examples(integration: str):
|
|
347
|
+
"""Show example commands for a connected integration."""
|
|
348
|
+
examples = {
|
|
349
|
+
"gmail": [
|
|
350
|
+
"'Read my latest emails'",
|
|
351
|
+
"'Send an email to bob@example.com'",
|
|
352
|
+
"'Draft an email about the project update'",
|
|
353
|
+
],
|
|
354
|
+
"slack": [
|
|
355
|
+
"'Send a message to #general channel'",
|
|
356
|
+
"'List my Slack channels'",
|
|
357
|
+
"'Send a DM to @john'",
|
|
358
|
+
],
|
|
359
|
+
"notion": [
|
|
360
|
+
"'Create a new page in Notion'",
|
|
361
|
+
"'Search my Notion workspace for meeting notes'",
|
|
362
|
+
"'Update my project page'",
|
|
363
|
+
],
|
|
364
|
+
"linear": [
|
|
365
|
+
"'Create a bug issue for the login problem'",
|
|
366
|
+
"'List my assigned issues'",
|
|
367
|
+
"'Update issue status to In Progress'",
|
|
368
|
+
],
|
|
369
|
+
"github": [
|
|
370
|
+
"'Create an issue for the new feature'",
|
|
371
|
+
"'List open PRs in my repo'",
|
|
372
|
+
"'Star the langchain repository'",
|
|
373
|
+
],
|
|
374
|
+
"googledrive": [
|
|
375
|
+
"'List files in my Drive'",
|
|
376
|
+
"'Upload a document'",
|
|
377
|
+
"'Create a new folder'",
|
|
378
|
+
],
|
|
379
|
+
"googledocs": [
|
|
380
|
+
"'Create a new document'",
|
|
381
|
+
"'Get content from my meeting notes doc'",
|
|
382
|
+
],
|
|
383
|
+
"googlesheets": [
|
|
384
|
+
"'Create a new spreadsheet'",
|
|
385
|
+
"'Update values in my budget sheet'",
|
|
386
|
+
],
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if integration in examples:
|
|
390
|
+
console.print("[dim]Your agent can now:[/dim]")
|
|
391
|
+
for example in examples[integration]:
|
|
392
|
+
console.print(f"[dim] {example}[/dim]")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
async def handle_disconnect_command(args: str = ""):
|
|
396
|
+
"""Handle /disconnect command - disconnect any integration.
|
|
397
|
+
|
|
398
|
+
Usage:
|
|
399
|
+
/disconnect gmail - Disconnect Gmail
|
|
400
|
+
/disconnect slack - Disconnect Slack
|
|
401
|
+
/disconnect - Show connected integrations
|
|
402
|
+
"""
|
|
403
|
+
# Get available integrations first
|
|
404
|
+
integrations_info = await get_available_integrations()
|
|
405
|
+
available = integrations_info.get("available", [])
|
|
406
|
+
connected = integrations_info.get("connected", [])
|
|
407
|
+
|
|
408
|
+
# Parse integration name from args
|
|
409
|
+
parts = args.strip().split()
|
|
410
|
+
|
|
411
|
+
# If no integration specified, show connected ones
|
|
412
|
+
if not parts:
|
|
413
|
+
if not connected:
|
|
414
|
+
print_info("No integrations are currently connected.")
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
console.print()
|
|
418
|
+
console.print(f"[bold {COLOR_SECONDARY}]━━━ Connected Integrations ━━━[/bold {COLOR_SECONDARY}]")
|
|
419
|
+
console.print()
|
|
420
|
+
for toolkit in connected:
|
|
421
|
+
display_name = get_display_name(toolkit)
|
|
422
|
+
console.print(f" • [{COLOR_INTERACTIVE}]{toolkit}[/{COLOR_INTERACTIVE}] - {display_name}")
|
|
423
|
+
console.print()
|
|
424
|
+
console.print("[dim]Usage: /disconnect <integration>[/dim]")
|
|
425
|
+
console.print("[dim]Example: /disconnect slack[/dim]")
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
integration = parts[0].lower()
|
|
429
|
+
|
|
430
|
+
if integration not in available:
|
|
431
|
+
print_error(f"Unknown integration: {integration}")
|
|
432
|
+
print_info(f"Available integrations: {', '.join(available)}")
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
display_name = get_display_name(integration)
|
|
436
|
+
|
|
437
|
+
# Check if connected
|
|
438
|
+
status = await check_integration_status(integration)
|
|
439
|
+
|
|
440
|
+
if not status.get("connected"):
|
|
441
|
+
print_info(f"{display_name} is not connected.")
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# Disconnect
|
|
445
|
+
print_info(f"Disconnecting {display_name}...")
|
|
446
|
+
result = await disconnect_integration(integration)
|
|
447
|
+
|
|
448
|
+
if result.get("success") or result.get("connected") is False:
|
|
449
|
+
print_success(f"✓ {display_name} disconnected successfully")
|
|
450
|
+
else:
|
|
451
|
+
print_error(f"Failed to disconnect: {result.get('error', 'Unknown error')}")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async def get_registry_info() -> dict:
|
|
455
|
+
"""Get tool registry information from backend.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Dict with registry data or error
|
|
459
|
+
"""
|
|
460
|
+
user_id = get_user_id()
|
|
461
|
+
backend_url = get_http_backend_url()
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
465
|
+
response = await client.get(
|
|
466
|
+
f"{backend_url}/api/registry/{user_id}/summary",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if response.status_code == 200:
|
|
470
|
+
return response.json()
|
|
471
|
+
else:
|
|
472
|
+
return {"error": f"HTTP {response.status_code}"}
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
return {"error": str(e)}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
async def handle_integrations_command(args: str = ""): # noqa: ARG001
|
|
479
|
+
"""Handle /integrations command - show all integration status.
|
|
480
|
+
|
|
481
|
+
Usage:
|
|
482
|
+
/integrations - Show status of all integrations
|
|
483
|
+
"""
|
|
484
|
+
console.print()
|
|
485
|
+
console.print("[bold cyan]━━━ Integrations ━━━[/bold cyan]")
|
|
486
|
+
console.print()
|
|
487
|
+
|
|
488
|
+
# Get all integrations with status
|
|
489
|
+
integrations_info = await get_available_integrations()
|
|
490
|
+
|
|
491
|
+
if "error" in integrations_info:
|
|
492
|
+
print_error(f"Failed to get integrations: {integrations_info['error']}")
|
|
493
|
+
print_info("Make sure the backend is running.")
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
details = integrations_info.get("details", [])
|
|
497
|
+
available = integrations_info.get("available", [])
|
|
498
|
+
|
|
499
|
+
if not details and not available:
|
|
500
|
+
print_warning("No integrations configured.")
|
|
501
|
+
print_info("Configure integrations at: https://app.composio.dev/auth-configs")
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
# Show status for each integration
|
|
505
|
+
if details:
|
|
506
|
+
for item in details:
|
|
507
|
+
slug = item.get("slug", "")
|
|
508
|
+
name = item.get("name", slug.title())
|
|
509
|
+
is_connected = item.get("connected", False)
|
|
510
|
+
|
|
511
|
+
if is_connected:
|
|
512
|
+
console.print(f" [{COLOR_INTERACTIVE}]●[/{COLOR_INTERACTIVE}] {name}: [{COLOR_INTERACTIVE}]Connected[/{COLOR_INTERACTIVE}]")
|
|
513
|
+
else:
|
|
514
|
+
console.print(f" [dim]○[/dim] {name}: [dim]Not connected[/dim]")
|
|
515
|
+
else:
|
|
516
|
+
# Fallback: check each available integration
|
|
517
|
+
for toolkit in available:
|
|
518
|
+
display_name = get_display_name(toolkit)
|
|
519
|
+
status = await check_integration_status(toolkit)
|
|
520
|
+
|
|
521
|
+
if status.get("connected"):
|
|
522
|
+
console.print(f" [{COLOR_INTERACTIVE}]●[/{COLOR_INTERACTIVE}] {display_name}: [{COLOR_INTERACTIVE}]Connected[/{COLOR_INTERACTIVE}]")
|
|
523
|
+
else:
|
|
524
|
+
console.print(f" [dim]○[/dim] {display_name}: [dim]Not connected[/dim]")
|
|
525
|
+
|
|
526
|
+
# Show tool registry info
|
|
527
|
+
console.print()
|
|
528
|
+
console.print(f"[bold {COLOR_SECONDARY}]━━━ Tool Registry ━━━[/bold {COLOR_SECONDARY}]")
|
|
529
|
+
console.print()
|
|
530
|
+
|
|
531
|
+
registry_info = await get_registry_info()
|
|
532
|
+
|
|
533
|
+
if "error" in registry_info:
|
|
534
|
+
console.print(f" [dim]Registry not available: {registry_info['error']}[/dim]")
|
|
535
|
+
else:
|
|
536
|
+
connected_count = registry_info.get("connected_count", 0)
|
|
537
|
+
connected = registry_info.get("connected", [])
|
|
538
|
+
|
|
539
|
+
if connected_count == 0:
|
|
540
|
+
console.print(" [dim]No tools registered (connect an integration first)[/dim]")
|
|
541
|
+
else:
|
|
542
|
+
console.print(f" [dim]Smart tool loading enabled[/dim]")
|
|
543
|
+
for item in connected:
|
|
544
|
+
slug = item.get("slug", "")
|
|
545
|
+
display_name = item.get("display_name", slug.title())
|
|
546
|
+
tool_count = item.get("tool_count", 0)
|
|
547
|
+
capabilities = item.get("capabilities", [])[:3]
|
|
548
|
+
|
|
549
|
+
console.print(f" • [{COLOR_INTERACTIVE}]{display_name}[/{COLOR_INTERACTIVE}]: {tool_count} tools")
|
|
550
|
+
if capabilities:
|
|
551
|
+
caps_str = ", ".join(capabilities[:3])
|
|
552
|
+
console.print(f" [dim]Capabilities: {caps_str}[/dim]")
|
|
553
|
+
|
|
554
|
+
console.print()
|
|
555
|
+
console.print("[dim]Commands:[/dim]")
|
|
556
|
+
console.print("[dim] /connect <integration> - Connect an integration[/dim]")
|
|
557
|
+
console.print("[dim] /disconnect <integration> - Disconnect an integration[/dim]")
|
|
558
|
+
console.print()
|
|
559
|
+
console.print("[dim]Examples:[/dim]")
|
|
560
|
+
console.print("[dim] /connect slack[/dim]")
|
|
561
|
+
console.print("[dim] /connect notion[/dim]")
|
|
562
|
+
console.print("[dim] /disconnect gmail[/dim]")
|
|
563
|
+
console.print()
|