videosdkagent-cli 0.0.1__py2.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,204 @@
1
+ from pathlib import Path
2
+ import json
3
+ import aiohttp
4
+ from datetime import datetime, timedelta
5
+ from rich.console import Console
6
+ from InquirerPy import inquirer
7
+ from videosdk_cli.utils.config_manager import get_config_value
8
+ import os
9
+
10
+ console = Console()
11
+
12
+ CACHE_DIR = Path.home() / ".videosdk"
13
+ CACHE_DIR.mkdir(exist_ok=True)
14
+ CACHE_FILE = CACHE_DIR / "template_cache.json"
15
+ TIMESTAMP_FILE = CACHE_DIR / "template_cache_timestamp.txt"
16
+ CDN_BASE_URL = os.environ.get("CDN_URL","https://probargaining-lynda-contradictiously.ngrok-free.dev/cdn")
17
+ API_BASE_URL = os.environ.get("API_BASE_URL","http://localhost:8000")
18
+
19
+
20
+ PROVIDER_IMPORTS = {
21
+ "DeepgramSTT": ("videosdk.plugins.deepgram", "DeepgramSTT", "STT", "deepgram", "DEEPGRAM_API_KEY"),
22
+ "GoogleSTT": ("videosdk.plugins.google", "GoogleSTT", "STT", "google-cloud-speech","GOOGLE_API_KEY"),
23
+ "OpenAISTT": ("videosdk.plugins.openai", "OpenAISTT", "STT", "openai", "OPENAI_API_KEY"),
24
+
25
+ "OpenAILLM": ("videosdk.plugins.openai", "OpenAILLM", "LLM", "openai", "OPENAI_API_KEY"),
26
+ "GoogleLLM": ("videosdk.plugins.google", "GoogleLLM", "LLM", "google-cloud-language", "GOOGLE_API_KEY"),
27
+
28
+ "ElevenLabsTTS": ("videosdk.plugins.elevenlabs", "ElevenLabsTTS", "TTS", "elevenlabs", "ELEVENLABS_API_KEY"),
29
+ "OpenAITTS": ("videosdk.plugins.openai", "OpenAITTS", "TTS", "openai", "OPENAI_API_KEY"),
30
+ "GoogleTTS": ("videosdk.plugins.google", "GoogleTTS", "TTS", "google-cloud-texttospeech", "GOOGLE_API_KEY"),
31
+ "DeepgramTTS": ("videosdk.plugins.deepgram", "DeepgramTTS", "TTS", "deepgram", "DEEPGRAM_API_KEY"),
32
+
33
+ "OpenAIRealtime": ("videosdk.plugins.openai", "OpenAIRealtime", "Realtime", "openai", "OPENAI_API_KEY"),
34
+ "GeminiRealtime": ("videosdk.plugins.google", "GeminiRealtime", "Realtime", "google-cloud-realtime", "GOOGLE_API_KEY"),
35
+ }
36
+
37
+ STATIC_REQUIREMENTS = [
38
+ "videosdk",
39
+ "aiohttp",
40
+ "rich",
41
+ "videosdk.agents",
42
+ "videosdk.plugins.silero",
43
+ "videosdk.plugins.turn_detector",
44
+ ]
45
+
46
+
47
+ async def fetch_template_cache(force_refresh=False):
48
+ """Fetch template metadata JSON from server if cache missing or expired."""
49
+ if TIMESTAMP_FILE.exists() and not force_refresh:
50
+ ts = datetime.fromisoformat(TIMESTAMP_FILE.read_text(encoding="utf-8"))
51
+ if datetime.now() - ts < timedelta(hours=24) and CACHE_FILE.exists():
52
+ console.print("[green]Using cached template metadata[/green]")
53
+ return
54
+
55
+ token = get_config_value("VIDEOSDK_AUTH_TOKEN")
56
+ if not token:
57
+ console.print("[red]VIDEOSDK_AUTH_TOKEN not set[/red]")
58
+ return
59
+
60
+ console.print("Fetching latest template metadata from server...")
61
+
62
+ try:
63
+ import aiohttp
64
+
65
+ timeout = aiohttp.ClientTimeout(total=10)
66
+ async with aiohttp.ClientSession(timeout=timeout) as session:
67
+ async with session.get(
68
+ f"{API_BASE_URL}/v2/cli/templates",
69
+ headers={
70
+ "Accept": "application/json",
71
+ "Authorization": f"{token}",
72
+ },
73
+ ) as resp:
74
+ if resp.status != 200:
75
+ console.print(
76
+ f"[red]Failed to fetch template metadata: {resp.status}[/red]"
77
+ )
78
+ return
79
+ data = await resp.json()
80
+ templateResponse = data.get("templates", {})
81
+ CACHE_FILE.write_text(json.dumps(templateResponse, indent=4), encoding="utf-8")
82
+ TIMESTAMP_FILE.write_text(datetime.now().isoformat(), encoding="utf-8")
83
+ console.print("[green]Template metadata cache updated.[/green]")
84
+
85
+ except Exception as e:
86
+ console.print(f"[red]Error fetching template metadata: {e}[/red]")
87
+
88
+ async def select_provider_arrow(prompt_message: str, options: list[str]) -> str:
89
+ """Arrow-based selection for providers."""
90
+ choice = await inquirer.select(
91
+ message=prompt_message,
92
+ choices=options,
93
+ pointer="👉",
94
+ instruction="Use ↑ ↓ to navigate, Enter to select",
95
+ ).execute_async()
96
+ return choice
97
+
98
+
99
+ def generate_cascading_providers(providers: dict):
100
+ """Generate imports and modules for cascading pipeline."""
101
+ imports = []
102
+ provider_modules = []
103
+ for key in ["STT", "LLM", "TTS"]:
104
+ provider_class = providers.get(key)
105
+ if provider_class and provider_class in PROVIDER_IMPORTS:
106
+ module, class_name, _, _, _ = PROVIDER_IMPORTS[provider_class]
107
+ imports.append(f"from {module} import {class_name}")
108
+ provider_modules.append(module)
109
+ return imports, provider_modules
110
+
111
+
112
+ def generate_realtime_providers(providers: dict):
113
+ """Generate imports and modules for realtime pipeline."""
114
+ imports = []
115
+ provider_modules = []
116
+ provider_class = providers.get("Realtime")
117
+ if provider_class and provider_class in PROVIDER_IMPORTS:
118
+ module, class_name, _, _, _ = PROVIDER_IMPORTS[provider_class]
119
+ imports.append(f"from {module} import {class_name}")
120
+ provider_modules.append(module)
121
+ return imports, provider_modules
122
+
123
+
124
+ def write_template_file(app_dir: Path, filename: str, imports_str: str, template_code: str):
125
+ file_path = app_dir / filename
126
+ file_path.write_text(f"{imports_str}\n\n{template_code}", encoding="utf-8")
127
+
128
+
129
+ def write_config(
130
+ app_dir: Path,
131
+ template_id: str,
132
+ file_name: str,
133
+ description: str,
134
+ providers: dict,
135
+ env_path: str = None,
136
+ auto_configured: bool = True,
137
+ requirements_installed: bool = False
138
+ ):
139
+ """Write project config.json with both template ID and file name."""
140
+ config_data = {
141
+ "template_id": template_id,
142
+ "template_file": file_name,
143
+ "description": description,
144
+ "providers": providers,
145
+ "auto_configured": auto_configured,
146
+ "requirements_installed": requirements_installed,
147
+ }
148
+ if env_path:
149
+ config_data["venv_path"] = env_path
150
+
151
+ (app_dir / "config.json").write_text(
152
+ json.dumps(config_data, indent=4),
153
+ encoding="utf-8"
154
+ )
155
+
156
+ def write_requirements(app_dir: Path, requirements: list):
157
+ (app_dir / "requirements.txt").write_text("\n".join(sorted(set(requirements))), encoding="utf-8")
158
+
159
+ def write_env_file(app_dir: Path, selected_providers: dict, api_keys: dict):
160
+ """Create a .env file with API keys and auth token."""
161
+ from collections import OrderedDict
162
+ auth_token = get_config_value("VIDEOSDK_AUTH_TOKEN") or ""
163
+
164
+ env_vars = OrderedDict()
165
+ env_vars["VIDEOSDK_AUTH_TOKEN"] = auth_token
166
+
167
+ for provider in selected_providers.values():
168
+ env_var_name = PROVIDER_IMPORTS.get(
169
+ provider,
170
+ (None, None, None, None, f"{provider.upper()}_API_KEY")
171
+ )[-1]
172
+ env_vars[env_var_name] = api_keys.get(provider, "")
173
+
174
+ env_content = "\n".join(f"{key}={value}" for key, value in env_vars.items())
175
+ (app_dir / ".env").write_text(env_content, encoding="utf-8")
176
+
177
+ async def prompt_provider_api_keys(providers: dict, api_keys: dict = None) -> dict:
178
+ """Ask user for API keys for selected providers. Skip if already in api_keys."""
179
+ if api_keys is None:
180
+ api_keys = {}
181
+
182
+ for provider in providers.values():
183
+ if provider in api_keys:
184
+ continue
185
+
186
+ key = await inquirer.text(
187
+ message=f"Enter API key for {provider}:",
188
+ validate=lambda val: len(val.strip()) > 0 or "API key cannot be empty"
189
+ ).execute_async()
190
+
191
+ api_keys[provider] = key
192
+
193
+ return api_keys
194
+
195
+ async def prompt_existing_env():
196
+ """Ask user whether to use an existing Python environment."""
197
+ use_existing = await select_provider_arrow(
198
+ "Do you want to use an existing Python environment (venv/conda)?",
199
+ ["No, create new venv", "Yes, I have an environment"]
200
+ )
201
+ if use_existing == "Yes, I have an environment":
202
+ path = input("Enter the path to your environment: ").strip()
203
+ return Path(path)
204
+ return None
@@ -0,0 +1,51 @@
1
+ from pathlib import Path
2
+ import json
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timedelta
5
+
6
+ CACHE_DIR = Path.home() / ".videosdk"
7
+ CACHE_DIR.mkdir(exist_ok=True)
8
+ CACHE_FILE = CACHE_DIR / "template_cache.json"
9
+ CACHE_TIMESTAMP_FILE = CACHE_DIR / "template_cache_timestamp.txt"
10
+
11
+ @dataclass
12
+ class Template:
13
+ """Represents a VideoSDK template."""
14
+ file_name: str
15
+ description: str
16
+ mode: str
17
+ pipe_type: str
18
+ STT_options: list = None
19
+ LLM_options: list = None
20
+ TTS_options: list = None
21
+ provider_options: list = None
22
+
23
+ class KeyValuePair:
24
+ """Loads templates from cached JSON metadata."""
25
+ def __init__(self, cache_file=CACHE_FILE):
26
+ self.templates = {}
27
+ if cache_file.exists():
28
+ try:
29
+ data = json.loads(cache_file.read_text(encoding="utf-8"))
30
+ for name, info in data.items():
31
+ self.templates[name] = Template(
32
+ file_name=info.get("file_name"),
33
+ description=info.get("description"),
34
+ mode=info.get("mode"),
35
+ pipe_type=info.get("pipe_type"),
36
+ STT_options=info.get("STT_options"),
37
+ LLM_options=info.get("LLM_options"),
38
+ TTS_options=info.get("TTS_options"),
39
+ provider_options=info.get("provider_options")
40
+ )
41
+ except Exception as e:
42
+ print(f"[red]Failed to load template cache: {e}[/red]")
43
+ self.templates = {}
44
+
45
+ def get_template_names(self):
46
+ """Return list of template names."""
47
+ return list(self.templates.keys())
48
+
49
+ def get_template(self, name: str):
50
+ """Return Template object by name."""
51
+ return self.templates.get(name)
@@ -0,0 +1,8 @@
1
+ def print_kv_blocks(items):
2
+ max_key_len = max(len(k) for item in items for k in item.keys())
3
+
4
+ for idx, item in enumerate(items):
5
+ for key, value in item.items():
6
+ print(f"{key.ljust(max_key_len)} : {value}")
7
+ if idx != len(items) - 1:
8
+ print()
@@ -0,0 +1,53 @@
1
+ import asyncio
2
+ import contextlib
3
+ from rich.progress import Progress, BarColumn, TextColumn, TimeElapsedColumn
4
+
5
+
6
+ async def run_with_progress(
7
+ coro,
8
+ *,
9
+ console,
10
+ title: str = "Working",
11
+ duration: float = 10.0,
12
+ ):
13
+ with Progress(
14
+ TextColumn(f"[bold cyan]{title}"),
15
+ BarColumn(bar_width=None),
16
+ TextColumn("{task.percentage:>3.0f}%"),
17
+ TimeElapsedColumn(),
18
+ console=console,
19
+ ) as progress:
20
+
21
+ task_id = progress.add_task("progress", total=100)
22
+ start_time = asyncio.get_event_loop().time()
23
+
24
+ api_task = asyncio.create_task(coro)
25
+
26
+ try:
27
+ while True:
28
+ elapsed = asyncio.get_event_loop().time() - start_time
29
+ percent = min((elapsed / duration) * 95, 95)
30
+
31
+ progress.update(task_id, completed=percent)
32
+
33
+ if api_task.done() and elapsed >= duration:
34
+ break
35
+
36
+ await asyncio.sleep(0.1)
37
+
38
+ result = await api_task
39
+ progress.update(task_id, completed=100)
40
+ return result
41
+
42
+ except asyncio.CancelledError:
43
+ raise
44
+
45
+ finally:
46
+ if not api_task.done():
47
+ api_task.cancel()
48
+
49
+ with contextlib.suppress(
50
+ asyncio.CancelledError,
51
+ Exception,
52
+ ):
53
+ await api_task
@@ -0,0 +1,75 @@
1
+ import yaml
2
+ from pathlib import Path
3
+ from dataclasses import dataclass, asdict
4
+ from typing import Optional
5
+
6
+
7
+ @dataclass
8
+ class AgentConfig:
9
+ templateId: Optional[str] = None
10
+ id: Optional[str] = None
11
+ name: Optional[str] = None
12
+ image: Optional[str] = None
13
+
14
+ @classmethod
15
+ def from_dict(cls, data: dict):
16
+ # Filter keys to only those present in the dataclass
17
+ return cls(**{
18
+ k: v for k, v in data.items()
19
+ if k in cls.__annotations__
20
+ })
21
+
22
+ @dataclass
23
+ class VideosdkYamlConfig:
24
+ agent: AgentConfig
25
+
26
+ @classmethod
27
+ def from_dict(cls, data: dict):
28
+ agent_data = data.get("agent", {})
29
+ # If agent_data is None (key exists but empty), default to empty dict
30
+ if agent_data is None:
31
+ agent_data = {}
32
+ return cls(agent=AgentConfig.from_dict(agent_data))
33
+
34
+ def update_agent_config(app_dir: Path, agent_config: AgentConfig):
35
+ videosdk_dir = app_dir
36
+ videosdk_dir.mkdir(parents=True, exist_ok=True)
37
+
38
+ config_file = videosdk_dir / "videosdk.yaml"
39
+
40
+ config_data = {}
41
+
42
+ if config_file.exists():
43
+ try:
44
+ with open(config_file, "r") as f:
45
+ config_data = yaml.safe_load(f) or {}
46
+ except yaml.YAMLError:
47
+ pass
48
+
49
+ # Load into dataclass structure
50
+ yaml_config = VideosdkYamlConfig.from_dict(config_data)
51
+
52
+ # Update agent fields dynamically from the passed AgentConfig object
53
+ # We iterate over fields in the passed config and update if they are not None
54
+
55
+ updates = asdict(agent_config)
56
+ for key, value in updates.items():
57
+ if value is not None and hasattr(yaml_config.agent, key):
58
+ setattr(yaml_config.agent, key, value)
59
+
60
+ # Convert back to dict and clean None values
61
+ raw_output = asdict(yaml_config)
62
+
63
+ output_data = clean_nones(raw_output)
64
+
65
+ # Write to YAML
66
+ with open(config_file, "w") as f:
67
+ yaml.dump(output_data, f, default_flow_style=False, sort_keys=False)
68
+
69
+ # Helper to remove None values recursively
70
+ def clean_nones(value):
71
+ if isinstance(value, dict):
72
+ return {k: clean_nones(v) for k, v in value.items() if v is not None}
73
+ if isinstance(value, list):
74
+ return [clean_nones(v) for v in value if v is not None]
75
+ return value
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: videosdkagent-cli
3
+ Version: 0.0.1
4
+ Summary: VideoSDK CLI tool
5
+ Author-email: Krishna <krishna@videosdk.live>
6
+ Requires-Dist: aiohttp>=3.9.0
7
+ Requires-Dist: click>=8.1.0
8
+ Requires-Dist: docker-image-py>=0.1.13
9
+ Requires-Dist: fastapi>=0.111.0
10
+ Requires-Dist: inquirerpy>=0.3.1
11
+ Requires-Dist: nest-asyncio>=1.5
12
+ Requires-Dist: python-dotenv>=1.0
13
+ Requires-Dist: pyyaml>=6.0
14
+ Requires-Dist: rich>=13.0.0
15
+ Requires-Dist: uvicorn>=0.25.0
@@ -0,0 +1,28 @@
1
+ videosdk_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ videosdk_cli/auth.py,sha256=2xMKx_oWKZN-jxhvL8fHJ1MoYO9OuIFZ2MmYRJ2i-sg,1872
3
+ videosdk_cli/build.py,sha256=KW6nDFI0cTuQjCzP4gtS4SdMnZjT9poLJdffq-9R5Ko,16202
4
+ videosdk_cli/main.py,sha256=PYjRDxGtL3rmDEaN3pkYoEprAfwWxTSdBi-niZXoisE,2452
5
+ videosdk_cli/projects.py,sha256=DG83uO0gUjkMHNhaJ6WjCKTsWfJeDc4g85igo_9YIQM,4314
6
+ videosdk_cli/run_agent.py,sha256=3Rlki35Q3cTnJnYCbFiCE9EoHfLnQCq79skH-JCA-oA,3205
7
+ videosdk_cli/secret_set.py,sha256=1sVQxuPAcplDWJGM5Jm9mo5gNHBaaV9u0T9HDq6F0iM,2539
8
+ videosdk_cli/templates.py,sha256=ph9zsROqp5HUM_N-YzXeGvUxyT10Q0XsUQfTpQvJj6g,6564
9
+ videosdk_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ videosdk_cli/utils/analytics.py,sha256=7qrVEVi2rU9Runz2CZrtJA5PlrjQJLiTvGfgn83YSog,1968
11
+ videosdk_cli/utils/api_client.py,sha256=JKfVBc2QaC0tffnloTRnijaa3Z2EaSS8LkAVXxJ5EgE,3533
12
+ videosdk_cli/utils/auth_api_client.py,sha256=5dp2gibuC7YEczeWDjbLKGRQxOTQK3QeMea3LCO0yx8,1379
13
+ videosdk_cli/utils/config_manager.py,sha256=yygGOp-CaU1KINClLZC7nFKI8Ob7vzuL-7cBP20dNEU,1893
14
+ videosdk_cli/utils/exceptions.py,sha256=1Tjxxmw8Ts8DOQEZqnJBmNAFgJ0CpcHkAQ95RLQs4iw,133
15
+ videosdk_cli/utils/project_config.py,sha256=X-jro6nUDkZ1ZTdJ324JfqE8_0MCZMyGqGUDhM_e0Rs,3141
16
+ videosdk_cli/utils/template_helper.py,sha256=eVMhgWnASNqRB5hkKrefG3yM3jlLb4Duxx8x8B4u2MM,8220
17
+ videosdk_cli/utils/template_options.py,sha256=BFNCmn6lLUCJ-eZ9TkhVbRtMNTP89EVm8MHhZQv61Q0,1872
18
+ videosdk_cli/utils/videosdk_yaml_helper.py,sha256=kXdO5ZkEyV_OptCOd_NFNSGCZKv_J8nAwIXwFtxXbkc,2303
19
+ videosdk_cli/utils/apis/deployment_client.py,sha256=KyScSq6CGm1fX2vus7hS1_whSCfYFkyk0ItRNBYq6dY,6359
20
+ videosdk_cli/utils/apis/error.py,sha256=UgJI1a0ZU1ZYKAxz01pOKhFYP1XrIsz8JQVVv7XwgDc,248
21
+ videosdk_cli/utils/apis/videosdk_auth_api_client.py,sha256=_yJg4nE6WQ9s1mKvYCS0EdHz7kbrXVJabQ30VFcMSo8,1896
22
+ videosdk_cli/utils/manager/agent_manager.py,sha256=Yf7hjn_2krpYl01kwL-T9rRPRRF7ay0nI4dZRHrB1TE,12142
23
+ videosdk_cli/utils/ui/kv_blocks.py,sha256=_KJZ0_q7AbaIDIh9kx6VvHuSQ3AN5fYo-vsMjYIBbFM,288
24
+ videosdk_cli/utils/ui/progress_runner.py,sha256=UhXgoQGDOKW3bSZuXy8BxCfFy_g9QpYOijeO3xJq_8Y,1380
25
+ videosdkagent_cli-0.0.1.dist-info/METADATA,sha256=Jpm5Rm-EhOF6nW0fAMfQtmxL0_i48Ru5zCfJA1aryuc,449
26
+ videosdkagent_cli-0.0.1.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
27
+ videosdkagent_cli-0.0.1.dist-info/entry_points.txt,sha256=7qv3V6B64WCwEshsIsTUc3fYcg6iNbnVJJOc6WE4U1E,51
28
+ videosdkagent_cli-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ videosdk = videosdk_cli.main:cli