vibesurf 0.1.32__py3-none-any.whl → 0.1.34__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.
Potentially problematic release.
This version of vibesurf might be problematic. Click here for more details.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/browser_use_agent.py +1 -1
- vibe_surf/agents/prompts/vibe_surf_prompt.py +6 -0
- vibe_surf/agents/report_writer_agent.py +50 -0
- vibe_surf/agents/vibe_surf_agent.py +55 -0
- vibe_surf/backend/api/composio.py +952 -0
- vibe_surf/backend/database/migrations/v005_add_composio_integration.sql +33 -0
- vibe_surf/backend/database/migrations/v006_add_credentials_table.sql +26 -0
- vibe_surf/backend/database/models.py +53 -1
- vibe_surf/backend/database/queries.py +312 -2
- vibe_surf/backend/main.py +28 -0
- vibe_surf/backend/shared_state.py +123 -9
- vibe_surf/chrome_extension/scripts/api-client.js +32 -0
- vibe_surf/chrome_extension/scripts/settings-manager.js +954 -1
- vibe_surf/chrome_extension/sidepanel.html +190 -0
- vibe_surf/chrome_extension/styles/settings-integrations.css +927 -0
- vibe_surf/chrome_extension/styles/settings-modal.css +7 -3
- vibe_surf/chrome_extension/styles/settings-responsive.css +37 -5
- vibe_surf/cli.py +98 -3
- vibe_surf/telemetry/__init__.py +60 -0
- vibe_surf/telemetry/service.py +112 -0
- vibe_surf/telemetry/views.py +156 -0
- vibe_surf/tools/composio_client.py +456 -0
- vibe_surf/tools/mcp_client.py +21 -2
- vibe_surf/tools/vibesurf_tools.py +290 -87
- vibe_surf/tools/views.py +16 -0
- vibe_surf/tools/website_api/youtube/client.py +35 -13
- vibe_surf/utils.py +13 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/METADATA +11 -9
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/RECORD +34 -25
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.32.dist-info → vibesurf-0.1.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Migration: v005_add_composio_integration.sql
|
|
2
|
+
-- Description: Add composio_toolkits table for Composio integration management
|
|
3
|
+
-- Version: 0.0.5
|
|
4
|
+
|
|
5
|
+
-- Enable foreign keys
|
|
6
|
+
PRAGMA foreign_keys = ON;
|
|
7
|
+
|
|
8
|
+
-- Create Composio Toolkits table
|
|
9
|
+
CREATE TABLE IF NOT EXISTS composio_toolkits (
|
|
10
|
+
id VARCHAR(36) NOT NULL PRIMARY KEY,
|
|
11
|
+
name VARCHAR(100) NOT NULL,
|
|
12
|
+
slug VARCHAR(100) NOT NULL UNIQUE,
|
|
13
|
+
description TEXT,
|
|
14
|
+
logo TEXT,
|
|
15
|
+
app_url TEXT,
|
|
16
|
+
enabled BOOLEAN NOT NULL DEFAULT 0,
|
|
17
|
+
tools TEXT,
|
|
18
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
-- Create indexes for composio_toolkits
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_composio_toolkits_name ON composio_toolkits(name);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_composio_toolkits_slug ON composio_toolkits(slug);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_composio_toolkits_enabled ON composio_toolkits(enabled);
|
|
26
|
+
|
|
27
|
+
-- Create trigger for automatic timestamp updates
|
|
28
|
+
CREATE TRIGGER IF NOT EXISTS update_composio_toolkits_updated_at
|
|
29
|
+
AFTER UPDATE ON composio_toolkits
|
|
30
|
+
FOR EACH ROW
|
|
31
|
+
BEGIN
|
|
32
|
+
UPDATE composio_toolkits SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
|
|
33
|
+
END;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Migration v006: Add credentials table for storing encrypted API keys
|
|
2
|
+
-- Created: 2025-01-10
|
|
3
|
+
|
|
4
|
+
-- Enable foreign keys
|
|
5
|
+
PRAGMA foreign_keys = ON;
|
|
6
|
+
|
|
7
|
+
-- Create credentials table
|
|
8
|
+
CREATE TABLE IF NOT EXISTS credentials (
|
|
9
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
10
|
+
key_name VARCHAR(100) NOT NULL UNIQUE,
|
|
11
|
+
encrypted_value TEXT,
|
|
12
|
+
description TEXT,
|
|
13
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
14
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- Create index for faster lookups
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_credentials_key_name ON credentials(key_name);
|
|
19
|
+
|
|
20
|
+
-- Create trigger for automatic timestamp updates
|
|
21
|
+
CREATE TRIGGER IF NOT EXISTS update_credentials_updated_at
|
|
22
|
+
AFTER UPDATE ON credentials
|
|
23
|
+
FOR EACH ROW
|
|
24
|
+
BEGIN
|
|
25
|
+
UPDATE credentials SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
|
|
26
|
+
END;
|
|
@@ -189,6 +189,50 @@ class McpProfile(Base):
|
|
|
189
189
|
def __repr__(self):
|
|
190
190
|
return f"<McpProfile(display_name={self.display_name}, server_name={self.mcp_server_name}, active={self.is_active})>"
|
|
191
191
|
|
|
192
|
+
class ComposioToolkit(Base):
|
|
193
|
+
"""Composio Toolkit model for managing Composio app integrations"""
|
|
194
|
+
__tablename__ = 'composio_toolkits'
|
|
195
|
+
|
|
196
|
+
# Primary identifier
|
|
197
|
+
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
198
|
+
name = Column(String(100), nullable=False)
|
|
199
|
+
slug = Column(String(100), nullable=False, unique=True)
|
|
200
|
+
|
|
201
|
+
# Toolkit information
|
|
202
|
+
description = Column(Text, nullable=True)
|
|
203
|
+
logo = Column(Text, nullable=True) # URL to logo
|
|
204
|
+
app_url = Column(Text, nullable=True)
|
|
205
|
+
|
|
206
|
+
# Configuration
|
|
207
|
+
enabled = Column(Boolean, default=False, nullable=False)
|
|
208
|
+
tools = Column(Text, nullable=True) # JSON string storing tool_name: 0|1 mapping
|
|
209
|
+
|
|
210
|
+
# Timestamps
|
|
211
|
+
created_at = Column(DateTime, nullable=False, default=func.now())
|
|
212
|
+
updated_at = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
|
213
|
+
|
|
214
|
+
def __repr__(self):
|
|
215
|
+
return f"<ComposioToolkit(name={self.name}, slug={self.slug}, enabled={self.enabled})>"
|
|
216
|
+
|
|
217
|
+
class Credential(Base):
|
|
218
|
+
"""Credential model for storing encrypted API keys and other sensitive data"""
|
|
219
|
+
__tablename__ = 'credentials'
|
|
220
|
+
|
|
221
|
+
# Primary identifier
|
|
222
|
+
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
223
|
+
key_name = Column(String(100), nullable=False, unique=True) # e.g., "COMPOSIO_API_KEY"
|
|
224
|
+
encrypted_value = Column(Text, nullable=True) # Encrypted value using MAC address
|
|
225
|
+
|
|
226
|
+
# Metadata
|
|
227
|
+
description = Column(Text, nullable=True)
|
|
228
|
+
|
|
229
|
+
# Timestamps
|
|
230
|
+
created_at = Column(DateTime, nullable=False, default=func.now())
|
|
231
|
+
updated_at = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
|
232
|
+
|
|
233
|
+
def __repr__(self):
|
|
234
|
+
return f"<Credential(key_name={self.key_name})>"
|
|
235
|
+
|
|
192
236
|
Index('idx_uploaded_files_session_time', UploadedFile.session_id, UploadedFile.upload_time)
|
|
193
237
|
Index('idx_uploaded_files_active', UploadedFile.is_deleted, UploadedFile.upload_time)
|
|
194
238
|
Index('idx_uploaded_files_filename', UploadedFile.original_filename)
|
|
@@ -201,4 +245,12 @@ Index('idx_mcp_profiles_active', McpProfile.is_active)
|
|
|
201
245
|
# Voice Profile indexes
|
|
202
246
|
Index('idx_voice_profiles_name', VoiceProfile.voice_profile_name)
|
|
203
247
|
Index('idx_voice_profiles_type', VoiceProfile.voice_model_type)
|
|
204
|
-
Index('idx_voice_profiles_active', VoiceProfile.is_active)
|
|
248
|
+
Index('idx_voice_profiles_active', VoiceProfile.is_active)
|
|
249
|
+
|
|
250
|
+
# Composio Toolkit indexes
|
|
251
|
+
Index('idx_composio_toolkits_name', ComposioToolkit.name)
|
|
252
|
+
Index('idx_composio_toolkits_slug', ComposioToolkit.slug)
|
|
253
|
+
Index('idx_composio_toolkits_enabled', ComposioToolkit.enabled)
|
|
254
|
+
|
|
255
|
+
# Credential indexes
|
|
256
|
+
Index('idx_credentials_key_name', Credential.key_name)
|
|
@@ -3,12 +3,12 @@ Database Query Operations for VibeSurf Backend - With LLM Profile Management
|
|
|
3
3
|
|
|
4
4
|
Centralized database operations for Task and LLMProfile tables.
|
|
5
5
|
"""
|
|
6
|
-
|
|
6
|
+
import pdb
|
|
7
7
|
from typing import List, Optional, Dict, Any
|
|
8
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
9
|
from sqlalchemy import select, update, delete, func, desc, and_, or_
|
|
10
10
|
from sqlalchemy.orm import selectinload
|
|
11
|
-
from .models import Task, TaskStatus, LLMProfile, UploadedFile, McpProfile, VoiceProfile, VoiceModelType
|
|
11
|
+
from .models import Task, TaskStatus, LLMProfile, UploadedFile, McpProfile, VoiceProfile, VoiceModelType, ComposioToolkit, Credential
|
|
12
12
|
from ..utils.encryption import encrypt_api_key, decrypt_api_key
|
|
13
13
|
import logging
|
|
14
14
|
import json
|
|
@@ -1117,3 +1117,313 @@ class VoiceProfileQueries:
|
|
|
1117
1117
|
except Exception as e:
|
|
1118
1118
|
logger.error(f"Failed to update last_used for Voice profile {voice_profile_name}: {e}")
|
|
1119
1119
|
raise
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
class ComposioToolkitQueries:
|
|
1123
|
+
"""Query operations for ComposioToolkit model"""
|
|
1124
|
+
|
|
1125
|
+
@staticmethod
|
|
1126
|
+
async def create_toolkit(
|
|
1127
|
+
db: AsyncSession,
|
|
1128
|
+
name: str,
|
|
1129
|
+
slug: str,
|
|
1130
|
+
description: Optional[str] = None,
|
|
1131
|
+
logo: Optional[str] = None,
|
|
1132
|
+
app_url: Optional[str] = None,
|
|
1133
|
+
enabled: bool = False,
|
|
1134
|
+
tools: Optional[str] = None
|
|
1135
|
+
) -> Dict[str, Any]:
|
|
1136
|
+
"""Create a new Composio toolkit"""
|
|
1137
|
+
try:
|
|
1138
|
+
toolkit = ComposioToolkit(
|
|
1139
|
+
name=name,
|
|
1140
|
+
slug=slug,
|
|
1141
|
+
description=description,
|
|
1142
|
+
logo=logo,
|
|
1143
|
+
app_url=app_url,
|
|
1144
|
+
enabled=enabled,
|
|
1145
|
+
tools=tools
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
db.add(toolkit)
|
|
1149
|
+
await db.flush()
|
|
1150
|
+
await db.refresh(toolkit)
|
|
1151
|
+
|
|
1152
|
+
# Extract data immediately to avoid greenlet issues
|
|
1153
|
+
toolkit_data = {
|
|
1154
|
+
"id": toolkit.id,
|
|
1155
|
+
"name": toolkit.name,
|
|
1156
|
+
"slug": toolkit.slug,
|
|
1157
|
+
"description": toolkit.description,
|
|
1158
|
+
"logo": toolkit.logo,
|
|
1159
|
+
"app_url": toolkit.app_url,
|
|
1160
|
+
"enabled": toolkit.enabled,
|
|
1161
|
+
"tools": toolkit.tools,
|
|
1162
|
+
"created_at": toolkit.created_at,
|
|
1163
|
+
"updated_at": toolkit.updated_at
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return toolkit_data
|
|
1167
|
+
except Exception as e:
|
|
1168
|
+
logger.error(f"Failed to create Composio toolkit {name}: {e}")
|
|
1169
|
+
raise
|
|
1170
|
+
|
|
1171
|
+
@staticmethod
|
|
1172
|
+
async def get_toolkit(db: AsyncSession, toolkit_id: str) -> Optional[ComposioToolkit]:
|
|
1173
|
+
"""Get Composio toolkit by ID"""
|
|
1174
|
+
try:
|
|
1175
|
+
result = await db.execute(
|
|
1176
|
+
select(ComposioToolkit).where(ComposioToolkit.id == toolkit_id)
|
|
1177
|
+
)
|
|
1178
|
+
toolkit = result.scalar_one_or_none()
|
|
1179
|
+
if toolkit:
|
|
1180
|
+
# Ensure all attributes are loaded by accessing them
|
|
1181
|
+
_ = (toolkit.id, toolkit.created_at, toolkit.updated_at, toolkit.enabled)
|
|
1182
|
+
return toolkit
|
|
1183
|
+
except Exception as e:
|
|
1184
|
+
logger.error(f"Failed to get Composio toolkit {toolkit_id}: {e}")
|
|
1185
|
+
raise
|
|
1186
|
+
|
|
1187
|
+
@staticmethod
|
|
1188
|
+
async def get_toolkit_by_slug(db: AsyncSession, slug: str) -> Optional[ComposioToolkit]:
|
|
1189
|
+
"""Get Composio toolkit by slug"""
|
|
1190
|
+
try:
|
|
1191
|
+
result = await db.execute(
|
|
1192
|
+
select(ComposioToolkit).where(ComposioToolkit.slug == slug)
|
|
1193
|
+
)
|
|
1194
|
+
toolkit = result.scalar_one_or_none()
|
|
1195
|
+
if toolkit:
|
|
1196
|
+
_ = (toolkit.id, toolkit.created_at, toolkit.updated_at, toolkit.enabled)
|
|
1197
|
+
return toolkit
|
|
1198
|
+
except Exception as e:
|
|
1199
|
+
logger.error(f"Failed to get Composio toolkit by slug {slug}: {e}")
|
|
1200
|
+
raise
|
|
1201
|
+
|
|
1202
|
+
@staticmethod
|
|
1203
|
+
async def list_toolkits(
|
|
1204
|
+
db: AsyncSession,
|
|
1205
|
+
enabled_only: bool = False,
|
|
1206
|
+
limit: int = -1,
|
|
1207
|
+
offset: int = 0
|
|
1208
|
+
) -> List[ComposioToolkit]:
|
|
1209
|
+
"""List Composio toolkits"""
|
|
1210
|
+
try:
|
|
1211
|
+
query = select(ComposioToolkit)
|
|
1212
|
+
|
|
1213
|
+
if enabled_only:
|
|
1214
|
+
query = query.where(ComposioToolkit.enabled == True)
|
|
1215
|
+
|
|
1216
|
+
# Handle -1 as "get all records"
|
|
1217
|
+
if limit != -1:
|
|
1218
|
+
query = query.limit(limit)
|
|
1219
|
+
|
|
1220
|
+
# Always apply offset if provided
|
|
1221
|
+
if offset > 0:
|
|
1222
|
+
query = query.offset(offset)
|
|
1223
|
+
|
|
1224
|
+
result = await db.execute(query)
|
|
1225
|
+
toolkits = result.scalars().all()
|
|
1226
|
+
|
|
1227
|
+
# Ensure all attributes are loaded for each toolkit
|
|
1228
|
+
for toolkit in toolkits:
|
|
1229
|
+
_ = (toolkit.id, toolkit.created_at, toolkit.updated_at, toolkit.enabled)
|
|
1230
|
+
|
|
1231
|
+
return toolkits
|
|
1232
|
+
except Exception as e:
|
|
1233
|
+
logger.error(f"Failed to list Composio toolkits: {e}")
|
|
1234
|
+
raise
|
|
1235
|
+
|
|
1236
|
+
@staticmethod
|
|
1237
|
+
async def get_enabled_toolkits(db: AsyncSession) -> List[ComposioToolkit]:
|
|
1238
|
+
"""Get all enabled Composio toolkits"""
|
|
1239
|
+
try:
|
|
1240
|
+
result = await db.execute(
|
|
1241
|
+
select(ComposioToolkit).where(ComposioToolkit.enabled == True)
|
|
1242
|
+
)
|
|
1243
|
+
toolkits = result.scalars().all()
|
|
1244
|
+
|
|
1245
|
+
# Ensure all attributes are loaded for each toolkit
|
|
1246
|
+
for toolkit in toolkits:
|
|
1247
|
+
_ = (toolkit.id, toolkit.created_at, toolkit.updated_at, toolkit.enabled)
|
|
1248
|
+
|
|
1249
|
+
return toolkits
|
|
1250
|
+
except Exception as e:
|
|
1251
|
+
logger.error(f"Failed to get enabled Composio toolkits: {e}")
|
|
1252
|
+
raise
|
|
1253
|
+
|
|
1254
|
+
@staticmethod
|
|
1255
|
+
async def update_toolkit(
|
|
1256
|
+
db: AsyncSession,
|
|
1257
|
+
toolkit_id: str,
|
|
1258
|
+
updates: Dict[str, Any]
|
|
1259
|
+
) -> bool:
|
|
1260
|
+
"""Update Composio toolkit"""
|
|
1261
|
+
try:
|
|
1262
|
+
result = await db.execute(
|
|
1263
|
+
update(ComposioToolkit)
|
|
1264
|
+
.where(ComposioToolkit.id == toolkit_id)
|
|
1265
|
+
.values(**updates)
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
return result.rowcount > 0
|
|
1269
|
+
except Exception as e:
|
|
1270
|
+
logger.error(f"Failed to update Composio toolkit {toolkit_id}: {e}")
|
|
1271
|
+
raise
|
|
1272
|
+
|
|
1273
|
+
@staticmethod
|
|
1274
|
+
async def update_toolkit_by_slug(
|
|
1275
|
+
db: AsyncSession,
|
|
1276
|
+
slug: str,
|
|
1277
|
+
updates: Dict[str, Any]
|
|
1278
|
+
) -> bool:
|
|
1279
|
+
"""Update Composio toolkit by slug"""
|
|
1280
|
+
try:
|
|
1281
|
+
result = await db.execute(
|
|
1282
|
+
update(ComposioToolkit)
|
|
1283
|
+
.where(ComposioToolkit.slug == slug)
|
|
1284
|
+
.values(**updates)
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
return result.rowcount > 0
|
|
1288
|
+
except Exception as e:
|
|
1289
|
+
logger.error(f"Failed to update Composio toolkit by slug {slug}: {e}")
|
|
1290
|
+
raise
|
|
1291
|
+
|
|
1292
|
+
@staticmethod
|
|
1293
|
+
async def delete_toolkit(db: AsyncSession, toolkit_id: str) -> bool:
|
|
1294
|
+
"""Delete Composio toolkit"""
|
|
1295
|
+
try:
|
|
1296
|
+
result = await db.execute(
|
|
1297
|
+
delete(ComposioToolkit).where(ComposioToolkit.id == toolkit_id)
|
|
1298
|
+
)
|
|
1299
|
+
return result.rowcount > 0
|
|
1300
|
+
except Exception as e:
|
|
1301
|
+
logger.error(f"Failed to delete Composio toolkit {toolkit_id}: {e}")
|
|
1302
|
+
raise
|
|
1303
|
+
|
|
1304
|
+
@staticmethod
|
|
1305
|
+
async def delete_toolkit_by_slug(db: AsyncSession, slug: str) -> bool:
|
|
1306
|
+
"""Delete Composio toolkit by slug"""
|
|
1307
|
+
try:
|
|
1308
|
+
result = await db.execute(
|
|
1309
|
+
delete(ComposioToolkit).where(ComposioToolkit.slug == slug)
|
|
1310
|
+
)
|
|
1311
|
+
return result.rowcount > 0
|
|
1312
|
+
except Exception as e:
|
|
1313
|
+
logger.error(f"Failed to delete Composio toolkit by slug {slug}: {e}")
|
|
1314
|
+
raise
|
|
1315
|
+
|
|
1316
|
+
@staticmethod
|
|
1317
|
+
async def toggle_toolkit_enabled(db: AsyncSession, toolkit_id: str, enabled: bool) -> bool:
|
|
1318
|
+
"""Toggle toolkit enabled status"""
|
|
1319
|
+
try:
|
|
1320
|
+
result = await db.execute(
|
|
1321
|
+
update(ComposioToolkit)
|
|
1322
|
+
.where(ComposioToolkit.id == toolkit_id)
|
|
1323
|
+
.values(enabled=enabled)
|
|
1324
|
+
)
|
|
1325
|
+
return result.rowcount > 0
|
|
1326
|
+
except Exception as e:
|
|
1327
|
+
logger.error(f"Failed to toggle Composio toolkit {toolkit_id} enabled status: {e}")
|
|
1328
|
+
raise
|
|
1329
|
+
|
|
1330
|
+
@staticmethod
|
|
1331
|
+
async def update_toolkit_tools(db: AsyncSession, toolkit_id: str, tools: str) -> bool:
|
|
1332
|
+
"""Update toolkit tools configuration"""
|
|
1333
|
+
try:
|
|
1334
|
+
result = await db.execute(
|
|
1335
|
+
update(ComposioToolkit)
|
|
1336
|
+
.where(ComposioToolkit.id == toolkit_id)
|
|
1337
|
+
.values(tools=tools)
|
|
1338
|
+
)
|
|
1339
|
+
return result.rowcount > 0
|
|
1340
|
+
except Exception as e:
|
|
1341
|
+
logger.error(f"Failed to update Composio toolkit {toolkit_id} tools: {e}")
|
|
1342
|
+
raise
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
class CredentialQueries:
|
|
1346
|
+
"""Query operations for Credential model"""
|
|
1347
|
+
|
|
1348
|
+
@staticmethod
|
|
1349
|
+
async def get_credential(db: AsyncSession, key_name: str) -> Optional[str]:
|
|
1350
|
+
"""Get decrypted credential value by key name"""
|
|
1351
|
+
try:
|
|
1352
|
+
result = await db.execute(
|
|
1353
|
+
select(Credential).where(Credential.key_name == key_name)
|
|
1354
|
+
)
|
|
1355
|
+
credential = result.scalar_one_or_none()
|
|
1356
|
+
if not credential or not credential.encrypted_value:
|
|
1357
|
+
return None
|
|
1358
|
+
|
|
1359
|
+
# Decrypt the value
|
|
1360
|
+
decrypted_value = decrypt_api_key(credential.encrypted_value)
|
|
1361
|
+
return decrypted_value
|
|
1362
|
+
|
|
1363
|
+
except Exception as e:
|
|
1364
|
+
logger.error(f"Failed to get credential {key_name}: {e}")
|
|
1365
|
+
return None
|
|
1366
|
+
|
|
1367
|
+
@staticmethod
|
|
1368
|
+
async def store_credential(db: AsyncSession, key_name: str, value: str, description: Optional[str] = None) -> bool:
|
|
1369
|
+
"""Store encrypted credential"""
|
|
1370
|
+
try:
|
|
1371
|
+
# Encrypt the value
|
|
1372
|
+
encrypted_value = encrypt_api_key(value)
|
|
1373
|
+
|
|
1374
|
+
# Check if credential exists
|
|
1375
|
+
result = await db.execute(
|
|
1376
|
+
select(Credential).where(Credential.key_name == key_name)
|
|
1377
|
+
)
|
|
1378
|
+
existing_credential = result.scalar_one_or_none()
|
|
1379
|
+
|
|
1380
|
+
if existing_credential:
|
|
1381
|
+
# Update existing credential
|
|
1382
|
+
await db.execute(
|
|
1383
|
+
update(Credential)
|
|
1384
|
+
.where(Credential.key_name == key_name)
|
|
1385
|
+
.values(
|
|
1386
|
+
encrypted_value=encrypted_value,
|
|
1387
|
+
description=description,
|
|
1388
|
+
updated_at=func.now()
|
|
1389
|
+
)
|
|
1390
|
+
)
|
|
1391
|
+
else:
|
|
1392
|
+
# Create new credential
|
|
1393
|
+
credential = Credential(
|
|
1394
|
+
key_name=key_name,
|
|
1395
|
+
encrypted_value=encrypted_value,
|
|
1396
|
+
description=description
|
|
1397
|
+
)
|
|
1398
|
+
db.add(credential)
|
|
1399
|
+
|
|
1400
|
+
await db.flush()
|
|
1401
|
+
return True
|
|
1402
|
+
|
|
1403
|
+
except Exception as e:
|
|
1404
|
+
logger.error(f"Failed to store credential {key_name}: {e}")
|
|
1405
|
+
return False
|
|
1406
|
+
|
|
1407
|
+
@staticmethod
|
|
1408
|
+
async def delete_credential(db: AsyncSession, key_name: str) -> bool:
|
|
1409
|
+
"""Delete credential"""
|
|
1410
|
+
try:
|
|
1411
|
+
result = await db.execute(
|
|
1412
|
+
delete(Credential).where(Credential.key_name == key_name)
|
|
1413
|
+
)
|
|
1414
|
+
return result.rowcount > 0
|
|
1415
|
+
except Exception as e:
|
|
1416
|
+
logger.error(f"Failed to delete credential {key_name}: {e}")
|
|
1417
|
+
return False
|
|
1418
|
+
|
|
1419
|
+
@staticmethod
|
|
1420
|
+
async def list_credential_names(db: AsyncSession) -> List[str]:
|
|
1421
|
+
"""List all credential key names (for administrative purposes)"""
|
|
1422
|
+
try:
|
|
1423
|
+
result = await db.execute(
|
|
1424
|
+
select(Credential.key_name).order_by(Credential.created_at)
|
|
1425
|
+
)
|
|
1426
|
+
return [row[0] for row in result.all()]
|
|
1427
|
+
except Exception as e:
|
|
1428
|
+
logger.error(f"Failed to list credential names: {e}")
|
|
1429
|
+
return []
|
vibe_surf/backend/main.py
CHANGED
|
@@ -22,6 +22,7 @@ from .api.config import router as config_router
|
|
|
22
22
|
from .api.browser import router as browser_router
|
|
23
23
|
from .api.voices import router as voices_router
|
|
24
24
|
from .api.agent import router as agent_router
|
|
25
|
+
from .api.composio import router as composio_router
|
|
25
26
|
|
|
26
27
|
# Import shared state
|
|
27
28
|
from . import shared_state
|
|
@@ -29,6 +30,8 @@ from . import shared_state
|
|
|
29
30
|
# Configure logging
|
|
30
31
|
|
|
31
32
|
from vibe_surf.logger import get_logger
|
|
33
|
+
from vibe_surf.telemetry.service import ProductTelemetry
|
|
34
|
+
from vibe_surf.telemetry.views import BackendTelemetryEvent
|
|
32
35
|
|
|
33
36
|
logger = get_logger(__name__)
|
|
34
37
|
|
|
@@ -55,6 +58,7 @@ app.include_router(config_router, prefix="/api", tags=["config"])
|
|
|
55
58
|
app.include_router(browser_router, prefix="/api", tags=["browser"])
|
|
56
59
|
app.include_router(voices_router, prefix="/api", tags=["voices"])
|
|
57
60
|
app.include_router(agent_router, prefix="/api", tags=["agent"])
|
|
61
|
+
app.include_router(composio_router, prefix="/api", tags=["composio"])
|
|
58
62
|
|
|
59
63
|
# Global variable to control browser monitoring task
|
|
60
64
|
browser_monitor_task = None
|
|
@@ -110,6 +114,15 @@ async def startup_event():
|
|
|
110
114
|
"""Initialize database and VibeSurf components on startup"""
|
|
111
115
|
global browser_monitor_task
|
|
112
116
|
|
|
117
|
+
# Initialize telemetry and capture startup event
|
|
118
|
+
telemetry = ProductTelemetry()
|
|
119
|
+
import vibe_surf
|
|
120
|
+
startup_event = BackendTelemetryEvent(
|
|
121
|
+
version=vibe_surf.__version__,
|
|
122
|
+
action='startup'
|
|
123
|
+
)
|
|
124
|
+
telemetry.capture(startup_event)
|
|
125
|
+
|
|
113
126
|
# Initialize VibeSurf components and update shared state
|
|
114
127
|
await shared_state.initialize_vibesurf_components()
|
|
115
128
|
|
|
@@ -118,6 +131,9 @@ async def startup_event():
|
|
|
118
131
|
logger.info("🔍 Started browser connection monitor")
|
|
119
132
|
|
|
120
133
|
logger.info("🚀 VibeSurf Backend API started with single-task execution model")
|
|
134
|
+
|
|
135
|
+
# Flush telemetry
|
|
136
|
+
telemetry.flush()
|
|
121
137
|
|
|
122
138
|
@app.on_event("shutdown")
|
|
123
139
|
async def shutdown_event():
|
|
@@ -126,6 +142,15 @@ async def shutdown_event():
|
|
|
126
142
|
|
|
127
143
|
logger.info("🛑 Starting graceful shutdown...")
|
|
128
144
|
|
|
145
|
+
# Capture telemetry shutdown event
|
|
146
|
+
telemetry = ProductTelemetry()
|
|
147
|
+
import vibe_surf
|
|
148
|
+
shutdown_event = BackendTelemetryEvent(
|
|
149
|
+
version=vibe_surf.__version__,
|
|
150
|
+
action='shutdown'
|
|
151
|
+
)
|
|
152
|
+
telemetry.capture(shutdown_event)
|
|
153
|
+
|
|
129
154
|
# Cancel browser monitor task
|
|
130
155
|
if browser_monitor_task and not browser_monitor_task.done():
|
|
131
156
|
browser_monitor_task.cancel()
|
|
@@ -153,6 +178,9 @@ async def shutdown_event():
|
|
|
153
178
|
logger.error(f"❌ Error closing database manager: {e}")
|
|
154
179
|
|
|
155
180
|
logger.info("🛑 VibeSurf Backend API stopped")
|
|
181
|
+
|
|
182
|
+
# Flush telemetry before shutdown
|
|
183
|
+
telemetry.flush()
|
|
156
184
|
|
|
157
185
|
# Health check endpoint
|
|
158
186
|
@app.get("/health")
|