videosdkagent-cli 0.0.1__py2.py3-none-any.whl → 0.0.5__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.
- videosdk_cli/auth.py +95 -30
- videosdk_cli/build.py +1387 -287
- videosdk_cli/main.py +69 -36
- videosdk_cli/projects.py +36 -19
- videosdk_cli/templates.py +87 -32
- videosdk_cli/utils/analytics.py +4 -3
- videosdk_cli/utils/api_client.py +26 -24
- videosdk_cli/utils/apis/deployment_client.py +247 -86
- videosdk_cli/utils/apis/videosdk_auth_api_client.py +5 -6
- videosdk_cli/utils/auth_api_client.py +2 -2
- videosdk_cli/utils/manager/agent_manager.py +275 -107
- videosdk_cli/utils/template_helper.py +102 -28
- videosdk_cli/utils/ui/progress_runner.py +15 -7
- videosdk_cli/utils/ui/theme.py +144 -0
- videosdk_cli/utils/videosdk_yaml_helper.py +173 -23
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/METADATA +2 -2
- videosdkagent_cli-0.0.5.dist-info/RECORD +28 -0
- videosdk_cli/secret_set.py +0 -82
- videosdkagent_cli-0.0.1.dist-info/RECORD +0 -28
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/WHEEL +0 -0
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/entry_points.txt +0 -0
|
@@ -13,25 +13,89 @@ CACHE_DIR = Path.home() / ".videosdk"
|
|
|
13
13
|
CACHE_DIR.mkdir(exist_ok=True)
|
|
14
14
|
CACHE_FILE = CACHE_DIR / "template_cache.json"
|
|
15
15
|
TIMESTAMP_FILE = CACHE_DIR / "template_cache_timestamp.txt"
|
|
16
|
-
CDN_BASE_URL =
|
|
17
|
-
API_BASE_URL =
|
|
16
|
+
CDN_BASE_URL = "https://cdn.videosdk.live"
|
|
17
|
+
API_BASE_URL = "https://api.videosdk.live"
|
|
18
|
+
DASHBOARD_URL = "https://stage.app.videosdk.live"
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
PROVIDER_IMPORTS = {
|
|
21
|
-
"DeepgramSTT":
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
"DeepgramSTT": (
|
|
23
|
+
"videosdk.plugins.deepgram",
|
|
24
|
+
"DeepgramSTT",
|
|
25
|
+
"STT",
|
|
26
|
+
"deepgram",
|
|
27
|
+
"DEEPGRAM_API_KEY",
|
|
28
|
+
),
|
|
29
|
+
"GoogleSTT": (
|
|
30
|
+
"videosdk.plugins.google",
|
|
31
|
+
"GoogleSTT",
|
|
32
|
+
"STT",
|
|
33
|
+
"google-cloud-speech",
|
|
34
|
+
"GOOGLE_API_KEY",
|
|
35
|
+
),
|
|
36
|
+
"OpenAISTT": (
|
|
37
|
+
"videosdk.plugins.openai",
|
|
38
|
+
"OpenAISTT",
|
|
39
|
+
"STT",
|
|
40
|
+
"openai",
|
|
41
|
+
"OPENAI_API_KEY",
|
|
42
|
+
),
|
|
43
|
+
"OpenAILLM": (
|
|
44
|
+
"videosdk.plugins.openai",
|
|
45
|
+
"OpenAILLM",
|
|
46
|
+
"LLM",
|
|
47
|
+
"openai",
|
|
48
|
+
"OPENAI_API_KEY",
|
|
49
|
+
),
|
|
50
|
+
"GoogleLLM": (
|
|
51
|
+
"videosdk.plugins.google",
|
|
52
|
+
"GoogleLLM",
|
|
53
|
+
"LLM",
|
|
54
|
+
"google-cloud-language",
|
|
55
|
+
"GOOGLE_API_KEY",
|
|
56
|
+
),
|
|
57
|
+
"ElevenLabsTTS": (
|
|
58
|
+
"videosdk.plugins.elevenlabs",
|
|
59
|
+
"ElevenLabsTTS",
|
|
60
|
+
"TTS",
|
|
61
|
+
"elevenlabs",
|
|
62
|
+
"ELEVENLABS_API_KEY",
|
|
63
|
+
),
|
|
64
|
+
"OpenAITTS": (
|
|
65
|
+
"videosdk.plugins.openai",
|
|
66
|
+
"OpenAITTS",
|
|
67
|
+
"TTS",
|
|
68
|
+
"openai",
|
|
69
|
+
"OPENAI_API_KEY",
|
|
70
|
+
),
|
|
71
|
+
"GoogleTTS": (
|
|
72
|
+
"videosdk.plugins.google",
|
|
73
|
+
"GoogleTTS",
|
|
74
|
+
"TTS",
|
|
75
|
+
"google-cloud-texttospeech",
|
|
76
|
+
"GOOGLE_API_KEY",
|
|
77
|
+
),
|
|
78
|
+
"DeepgramTTS": (
|
|
79
|
+
"videosdk.plugins.deepgram",
|
|
80
|
+
"DeepgramTTS",
|
|
81
|
+
"TTS",
|
|
82
|
+
"deepgram",
|
|
83
|
+
"DEEPGRAM_API_KEY",
|
|
84
|
+
),
|
|
85
|
+
"OpenAIRealtime": (
|
|
86
|
+
"videosdk.plugins.openai",
|
|
87
|
+
"OpenAIRealtime",
|
|
88
|
+
"Realtime",
|
|
89
|
+
"openai",
|
|
90
|
+
"OPENAI_API_KEY",
|
|
91
|
+
),
|
|
92
|
+
"GeminiRealtime": (
|
|
93
|
+
"videosdk.plugins.google",
|
|
94
|
+
"GeminiRealtime",
|
|
95
|
+
"Realtime",
|
|
96
|
+
"google-cloud-realtime",
|
|
97
|
+
"GOOGLE_API_KEY",
|
|
98
|
+
),
|
|
35
99
|
}
|
|
36
100
|
|
|
37
101
|
STATIC_REQUIREMENTS = [
|
|
@@ -78,13 +142,16 @@ async def fetch_template_cache(force_refresh=False):
|
|
|
78
142
|
return
|
|
79
143
|
data = await resp.json()
|
|
80
144
|
templateResponse = data.get("templates", {})
|
|
81
|
-
CACHE_FILE.write_text(
|
|
145
|
+
CACHE_FILE.write_text(
|
|
146
|
+
json.dumps(templateResponse, indent=4), encoding="utf-8"
|
|
147
|
+
)
|
|
82
148
|
TIMESTAMP_FILE.write_text(datetime.now().isoformat(), encoding="utf-8")
|
|
83
149
|
console.print("[green]Template metadata cache updated.[/green]")
|
|
84
150
|
|
|
85
151
|
except Exception as e:
|
|
86
152
|
console.print(f"[red]Error fetching template metadata: {e}[/red]")
|
|
87
153
|
|
|
154
|
+
|
|
88
155
|
async def select_provider_arrow(prompt_message: str, options: list[str]) -> str:
|
|
89
156
|
"""Arrow-based selection for providers."""
|
|
90
157
|
choice = await inquirer.select(
|
|
@@ -121,7 +188,9 @@ def generate_realtime_providers(providers: dict):
|
|
|
121
188
|
return imports, provider_modules
|
|
122
189
|
|
|
123
190
|
|
|
124
|
-
def write_template_file(
|
|
191
|
+
def write_template_file(
|
|
192
|
+
app_dir: Path, filename: str, imports_str: str, template_code: str
|
|
193
|
+
):
|
|
125
194
|
file_path = app_dir / filename
|
|
126
195
|
file_path.write_text(f"{imports_str}\n\n{template_code}", encoding="utf-8")
|
|
127
196
|
|
|
@@ -134,12 +203,12 @@ def write_config(
|
|
|
134
203
|
providers: dict,
|
|
135
204
|
env_path: str = None,
|
|
136
205
|
auto_configured: bool = True,
|
|
137
|
-
requirements_installed: bool = False
|
|
206
|
+
requirements_installed: bool = False,
|
|
138
207
|
):
|
|
139
208
|
"""Write project config.json with both template ID and file name."""
|
|
140
209
|
config_data = {
|
|
141
|
-
"template_id": template_id,
|
|
142
|
-
"template_file": file_name,
|
|
210
|
+
"template_id": template_id,
|
|
211
|
+
"template_file": file_name,
|
|
143
212
|
"description": description,
|
|
144
213
|
"providers": providers,
|
|
145
214
|
"auto_configured": auto_configured,
|
|
@@ -149,16 +218,20 @@ def write_config(
|
|
|
149
218
|
config_data["venv_path"] = env_path
|
|
150
219
|
|
|
151
220
|
(app_dir / "config.json").write_text(
|
|
152
|
-
json.dumps(config_data, indent=4),
|
|
153
|
-
encoding="utf-8"
|
|
221
|
+
json.dumps(config_data, indent=4), encoding="utf-8"
|
|
154
222
|
)
|
|
155
223
|
|
|
224
|
+
|
|
156
225
|
def write_requirements(app_dir: Path, requirements: list):
|
|
157
|
-
(app_dir / "requirements.txt").write_text(
|
|
226
|
+
(app_dir / "requirements.txt").write_text(
|
|
227
|
+
"\n".join(sorted(set(requirements))), encoding="utf-8"
|
|
228
|
+
)
|
|
229
|
+
|
|
158
230
|
|
|
159
231
|
def write_env_file(app_dir: Path, selected_providers: dict, api_keys: dict):
|
|
160
232
|
"""Create a .env file with API keys and auth token."""
|
|
161
233
|
from collections import OrderedDict
|
|
234
|
+
|
|
162
235
|
auth_token = get_config_value("VIDEOSDK_AUTH_TOKEN") or ""
|
|
163
236
|
|
|
164
237
|
env_vars = OrderedDict()
|
|
@@ -166,14 +239,14 @@ def write_env_file(app_dir: Path, selected_providers: dict, api_keys: dict):
|
|
|
166
239
|
|
|
167
240
|
for provider in selected_providers.values():
|
|
168
241
|
env_var_name = PROVIDER_IMPORTS.get(
|
|
169
|
-
provider,
|
|
170
|
-
(None, None, None, None, f"{provider.upper()}_API_KEY")
|
|
242
|
+
provider, (None, None, None, None, f"{provider.upper()}_API_KEY")
|
|
171
243
|
)[-1]
|
|
172
244
|
env_vars[env_var_name] = api_keys.get(provider, "")
|
|
173
245
|
|
|
174
246
|
env_content = "\n".join(f"{key}={value}" for key, value in env_vars.items())
|
|
175
247
|
(app_dir / ".env").write_text(env_content, encoding="utf-8")
|
|
176
248
|
|
|
249
|
+
|
|
177
250
|
async def prompt_provider_api_keys(providers: dict, api_keys: dict = None) -> dict:
|
|
178
251
|
"""Ask user for API keys for selected providers. Skip if already in api_keys."""
|
|
179
252
|
if api_keys is None:
|
|
@@ -185,18 +258,19 @@ async def prompt_provider_api_keys(providers: dict, api_keys: dict = None) -> di
|
|
|
185
258
|
|
|
186
259
|
key = await inquirer.text(
|
|
187
260
|
message=f"Enter API key for {provider}:",
|
|
188
|
-
validate=lambda val: len(val.strip()) > 0 or "API key cannot be empty"
|
|
261
|
+
validate=lambda val: len(val.strip()) > 0 or "API key cannot be empty",
|
|
189
262
|
).execute_async()
|
|
190
263
|
|
|
191
264
|
api_keys[provider] = key
|
|
192
265
|
|
|
193
266
|
return api_keys
|
|
194
267
|
|
|
268
|
+
|
|
195
269
|
async def prompt_existing_env():
|
|
196
270
|
"""Ask user whether to use an existing Python environment."""
|
|
197
271
|
use_existing = await select_provider_arrow(
|
|
198
272
|
"Do you want to use an existing Python environment (venv/conda)?",
|
|
199
|
-
["No, create new venv", "Yes, I have an environment"]
|
|
273
|
+
["No, create new venv", "Yes, I have an environment"],
|
|
200
274
|
)
|
|
201
275
|
if use_existing == "Yes, I have an environment":
|
|
202
276
|
path = input("Enter the path to your environment: ").strip()
|
|
@@ -26,19 +26,27 @@ async def run_with_progress(
|
|
|
26
26
|
try:
|
|
27
27
|
while True:
|
|
28
28
|
elapsed = asyncio.get_event_loop().time() - start_time
|
|
29
|
-
percent = min((elapsed / duration) * 95, 95)
|
|
30
29
|
|
|
30
|
+
# Check if task is done - complete immediately if so
|
|
31
|
+
if api_task.done():
|
|
32
|
+
result = await api_task
|
|
33
|
+
progress.update(task_id, completed=100)
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
# Update progress based on elapsed time (up to 95% until task completes)
|
|
37
|
+
# Duration is the maximum expected time
|
|
38
|
+
percent = min((elapsed / duration) * 95, 95)
|
|
31
39
|
progress.update(task_id, completed=percent)
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
# If we've exceeded the maximum duration, break and wait for task
|
|
42
|
+
if elapsed >= duration:
|
|
43
|
+
# Task might still be running, wait for it
|
|
44
|
+
result = await api_task
|
|
45
|
+
progress.update(task_id, completed=100)
|
|
46
|
+
return result
|
|
35
47
|
|
|
36
48
|
await asyncio.sleep(0.1)
|
|
37
49
|
|
|
38
|
-
result = await api_task
|
|
39
|
-
progress.update(task_id, completed=100)
|
|
40
|
-
return result
|
|
41
|
-
|
|
42
50
|
except asyncio.CancelledError:
|
|
43
51
|
raise
|
|
44
52
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VideoSDK CLI Theme and Styling
|
|
3
|
+
|
|
4
|
+
Brand Colors:
|
|
5
|
+
- Primary: #D1BCFE (light purple/lavender)
|
|
6
|
+
- Secondary: #37265E (dark purple)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.theme import Theme
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
from rich.style import Style
|
|
14
|
+
|
|
15
|
+
# VideoSDK Brand Colors
|
|
16
|
+
PRIMARY = "#D1BCFE"
|
|
17
|
+
SECONDARY = "#37265E"
|
|
18
|
+
SUCCESS = "#50FA7B"
|
|
19
|
+
ERROR = "#FF5555"
|
|
20
|
+
WARNING = "#FFB86C"
|
|
21
|
+
INFO = "#8BE9FD"
|
|
22
|
+
MUTED = "#6272A4"
|
|
23
|
+
|
|
24
|
+
# Custom theme for Rich
|
|
25
|
+
VIDEOSDK_THEME = Theme(
|
|
26
|
+
{
|
|
27
|
+
"primary": PRIMARY,
|
|
28
|
+
"secondary": SECONDARY,
|
|
29
|
+
"success": SUCCESS,
|
|
30
|
+
"error": ERROR,
|
|
31
|
+
"warning": WARNING,
|
|
32
|
+
"info": INFO,
|
|
33
|
+
"muted": MUTED,
|
|
34
|
+
"header": f"bold {PRIMARY}",
|
|
35
|
+
"highlight": f"bold {PRIMARY}",
|
|
36
|
+
"command": f"bold {INFO}",
|
|
37
|
+
"path": f"italic {MUTED}",
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Create themed console
|
|
42
|
+
console = Console(theme=VIDEOSDK_THEME)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ASCII Art Banner - Simple and clean
|
|
46
|
+
BANNER = f"""
|
|
47
|
+
[{PRIMARY}]╭────────────────────────────────────────────────────────────────────╮[/{PRIMARY}]
|
|
48
|
+
[{PRIMARY}]│[/{PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
49
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}]██╗ ██╗██╗██████╗ ███████╗ ██████╗ ███████╗██████╗ ██╗ ██╗[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
50
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}]██║ ██║██║██╔══██╗██╔════╝██╔═══██╗██╔════╝██╔══██╗██║ ██╔╝[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
51
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}]██║ ██║██║██║ ██║█████╗ ██║ ██║███████╗██║ ██║█████╔╝[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
52
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}]╚██╗ ██╔╝██║██║ ██║██╔══╝ ██║ ██║╚════██║██║ ██║██╔═██╗[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
53
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}] ╚████╔╝ ██║██████╔╝███████╗╚██████╔╝███████║██████╔╝██║ ██╗[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
54
|
+
[{PRIMARY}]│[/{PRIMARY}] [bold {PRIMARY}] ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝[/bold {PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
55
|
+
[{PRIMARY}]│[/{PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
56
|
+
[{PRIMARY}]│[/{PRIMARY}] [{MUTED}]Your Complete Platform for Real-Time Communication[/{MUTED}] [{PRIMARY}]│[/{PRIMARY}]
|
|
57
|
+
[{PRIMARY}]│[/{PRIMARY}] [{PRIMARY}]│[/{PRIMARY}]
|
|
58
|
+
[{PRIMARY}]╰────────────────────────────────────────────────────────────────────╯[/{PRIMARY}]
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
MINI_BANNER = """[bold #D1BCFE]◆ VideoSDK CLI[/bold #D1BCFE] [muted]v0.2.0[/muted]"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def print_banner(mini: bool = True):
|
|
65
|
+
"""Print the VideoSDK banner."""
|
|
66
|
+
if mini:
|
|
67
|
+
console.print(MINI_BANNER)
|
|
68
|
+
console.print()
|
|
69
|
+
else:
|
|
70
|
+
console.print(BANNER)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def print_header(text: str):
|
|
74
|
+
"""Print a styled header."""
|
|
75
|
+
console.print()
|
|
76
|
+
console.print(f"[bold {PRIMARY}]◆[/bold {PRIMARY}] [bold white]{text}[/bold white]")
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def print_success(text: str):
|
|
81
|
+
"""Print a success message."""
|
|
82
|
+
console.print(f"[{SUCCESS}]✓[/{SUCCESS}] {text}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def print_error(text: str):
|
|
86
|
+
"""Print an error message."""
|
|
87
|
+
console.print(f"[{ERROR}]✗[/{ERROR}] [bold {ERROR}]{text}[/bold {ERROR}]")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def print_warning(text: str):
|
|
91
|
+
"""Print a warning message."""
|
|
92
|
+
console.print(f"[{WARNING}]![/{WARNING}] {text}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def print_info(text: str):
|
|
96
|
+
"""Print an info message."""
|
|
97
|
+
console.print(f"[{INFO}]ℹ[/{INFO}] {text}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def print_step(text: str, step: int = None, total: int = None):
|
|
101
|
+
"""Print a step in a process."""
|
|
102
|
+
if step and total:
|
|
103
|
+
console.print(
|
|
104
|
+
f"[{PRIMARY}]▸[/{PRIMARY}] [{MUTED}][{step}/{total}][/{MUTED}] {text}"
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
console.print(f"[{PRIMARY}]▸[/{PRIMARY}] {text}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def print_key_value(key: str, value: str):
|
|
111
|
+
"""Print a key-value pair."""
|
|
112
|
+
console.print(f" [{MUTED}]{key}:[/{MUTED}] [{PRIMARY}]{value}[/{PRIMARY}]")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def print_divider():
|
|
116
|
+
"""Print a divider line."""
|
|
117
|
+
console.print(f"[{MUTED}]{'─' * 60}[/{MUTED}]")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_styled_table(title: str = None):
|
|
121
|
+
"""Create a styled table with VideoSDK theme."""
|
|
122
|
+
from rich.table import Table
|
|
123
|
+
|
|
124
|
+
table = Table(
|
|
125
|
+
title=f"[bold {PRIMARY}]{title}[/bold {PRIMARY}]" if title else None,
|
|
126
|
+
show_header=True,
|
|
127
|
+
header_style=f"bold {PRIMARY}",
|
|
128
|
+
border_style=MUTED,
|
|
129
|
+
title_style=f"bold {PRIMARY}",
|
|
130
|
+
show_lines=True,
|
|
131
|
+
row_styles=[f"on {SECONDARY}20", ""], # Alternating row colors
|
|
132
|
+
)
|
|
133
|
+
return table
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def styled_panel(content: str, title: str = None, subtitle: str = None):
|
|
137
|
+
"""Create a styled panel."""
|
|
138
|
+
return Panel(
|
|
139
|
+
content,
|
|
140
|
+
title=f"[bold {PRIMARY}]{title}[/bold {PRIMARY}]" if title else None,
|
|
141
|
+
subtitle=f"[{MUTED}]{subtitle}[/{MUTED}]" if subtitle else None,
|
|
142
|
+
border_style=PRIMARY,
|
|
143
|
+
padding=(1, 2),
|
|
144
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from dataclasses import dataclass, asdict
|
|
3
|
+
from dataclasses import dataclass, asdict, field
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
|
|
@@ -9,54 +9,203 @@ class AgentConfig:
|
|
|
9
9
|
templateId: Optional[str] = None
|
|
10
10
|
id: Optional[str] = None
|
|
11
11
|
name: Optional[str] = None
|
|
12
|
-
|
|
12
|
+
description: Optional[str] = None
|
|
13
|
+
image: Optional[str] = None # Legacy v1.0 support
|
|
13
14
|
|
|
14
15
|
@classmethod
|
|
15
16
|
def from_dict(cls, data: dict):
|
|
16
17
|
# Filter keys to only those present in the dataclass
|
|
17
|
-
return cls(**{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class BuildLogsConfig:
|
|
23
|
+
id: Optional[str] = None # buildId from presigned URL response
|
|
24
|
+
enabled: Optional[bool] = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_dict(cls, data: dict):
|
|
28
|
+
if data is None:
|
|
29
|
+
return cls()
|
|
30
|
+
return cls(id=data.get("id"), enabled=data.get("enabled"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class BuildConfig:
|
|
35
|
+
file: Optional[str] = None
|
|
36
|
+
image: Optional[str] = None
|
|
37
|
+
logs: Optional[BuildLogsConfig] = None
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_dict(cls, data: dict):
|
|
41
|
+
if data is None:
|
|
42
|
+
return cls()
|
|
43
|
+
logs_data = data.get("logs")
|
|
44
|
+
return cls(
|
|
45
|
+
file=data.get("file"),
|
|
46
|
+
image=data.get("image"),
|
|
47
|
+
logs=BuildLogsConfig.from_dict(logs_data) if logs_data else None,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ReplicasConfig:
|
|
53
|
+
min: Optional[int] = None
|
|
54
|
+
max: Optional[int] = None
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_dict(cls, data: dict):
|
|
58
|
+
if data is None:
|
|
59
|
+
return cls()
|
|
60
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class DeployConfig:
|
|
65
|
+
id: Optional[str] = None # deploymentId from API
|
|
66
|
+
replicas: Optional[ReplicasConfig] = None
|
|
67
|
+
profile: Optional[str] = None
|
|
68
|
+
region: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_dict(cls, data: dict):
|
|
72
|
+
if data is None:
|
|
73
|
+
return cls()
|
|
74
|
+
replicas_data = data.get("replicas")
|
|
75
|
+
return cls(
|
|
76
|
+
id=data.get("id"),
|
|
77
|
+
replicas=ReplicasConfig.from_dict(replicas_data) if replicas_data else None,
|
|
78
|
+
profile=data.get("profile"),
|
|
79
|
+
region=data.get("region"),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class SecretsConfig:
|
|
85
|
+
env: Optional[str] = None
|
|
86
|
+
image_pull: Optional[str] = None # maps from 'image-pull' in YAML
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_dict(cls, data: dict):
|
|
90
|
+
if data is None:
|
|
91
|
+
return cls()
|
|
92
|
+
return cls(env=data.get("env"), image_pull=data.get("image-pull"))
|
|
93
|
+
|
|
21
94
|
|
|
22
95
|
@dataclass
|
|
23
96
|
class VideosdkYamlConfig:
|
|
24
|
-
|
|
97
|
+
version: Optional[str] = None
|
|
98
|
+
agent: Optional[AgentConfig] = None
|
|
99
|
+
build: Optional[BuildConfig] = None
|
|
100
|
+
deploy: Optional[DeployConfig] = None
|
|
101
|
+
secrets: Optional[SecretsConfig] = None
|
|
25
102
|
|
|
26
103
|
@classmethod
|
|
27
104
|
def from_dict(cls, data: dict):
|
|
28
105
|
agent_data = data.get("agent", {})
|
|
106
|
+
build_data = data.get("build")
|
|
107
|
+
deploy_data = data.get("deploy")
|
|
108
|
+
secrets_data = data.get("secrets")
|
|
109
|
+
|
|
29
110
|
# If agent_data is None (key exists but empty), default to empty dict
|
|
30
111
|
if agent_data is None:
|
|
31
112
|
agent_data = {}
|
|
32
|
-
return cls(agent=AgentConfig.from_dict(agent_data))
|
|
33
113
|
|
|
34
|
-
|
|
114
|
+
return cls(
|
|
115
|
+
version=data.get("version"),
|
|
116
|
+
agent=AgentConfig.from_dict(agent_data),
|
|
117
|
+
build=BuildConfig.from_dict(build_data) if build_data else None,
|
|
118
|
+
deploy=DeployConfig.from_dict(deploy_data) if deploy_data else None,
|
|
119
|
+
secrets=SecretsConfig.from_dict(secrets_data) if secrets_data else None,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def update_agent_config(
|
|
124
|
+
app_dir: Path,
|
|
125
|
+
agent_config: AgentConfig = None,
|
|
126
|
+
deploy_config: DeployConfig = None,
|
|
127
|
+
build_config: BuildConfig = None,
|
|
128
|
+
secrets_config: SecretsConfig = None,
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Update the videosdk.yaml with agent, deploy, build, and/or secrets configuration.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
app_dir: Directory where videosdk.yaml is located
|
|
135
|
+
agent_config: AgentConfig dataclass with agent fields to update
|
|
136
|
+
deploy_config: DeployConfig dataclass with deploy fields to update
|
|
137
|
+
build_config: BuildConfig dataclass with build fields to update
|
|
138
|
+
secrets_config: SecretsConfig dataclass with secrets fields to update
|
|
139
|
+
"""
|
|
35
140
|
videosdk_dir = app_dir
|
|
36
141
|
videosdk_dir.mkdir(parents=True, exist_ok=True)
|
|
37
|
-
|
|
142
|
+
|
|
38
143
|
config_file = videosdk_dir / "videosdk.yaml"
|
|
39
|
-
|
|
144
|
+
|
|
40
145
|
config_data = {}
|
|
41
|
-
|
|
146
|
+
|
|
42
147
|
if config_file.exists():
|
|
43
148
|
try:
|
|
44
149
|
with open(config_file, "r") as f:
|
|
45
150
|
config_data = yaml.safe_load(f) or {}
|
|
46
151
|
except yaml.YAMLError:
|
|
47
152
|
pass
|
|
48
|
-
|
|
153
|
+
|
|
49
154
|
# Load into dataclass structure
|
|
50
155
|
yaml_config = VideosdkYamlConfig.from_dict(config_data)
|
|
51
|
-
|
|
52
|
-
# Update agent fields
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
156
|
+
|
|
157
|
+
# Update agent fields if provided
|
|
158
|
+
if agent_config:
|
|
159
|
+
if yaml_config.agent is None:
|
|
160
|
+
yaml_config.agent = AgentConfig()
|
|
161
|
+
updates = asdict(agent_config)
|
|
162
|
+
for key, value in updates.items():
|
|
163
|
+
if value is not None and hasattr(yaml_config.agent, key):
|
|
164
|
+
setattr(yaml_config.agent, key, value)
|
|
165
|
+
|
|
166
|
+
# Update deploy fields if provided
|
|
167
|
+
if deploy_config:
|
|
168
|
+
if yaml_config.deploy is None:
|
|
169
|
+
yaml_config.deploy = DeployConfig()
|
|
170
|
+
updates = asdict(deploy_config)
|
|
171
|
+
for key, value in updates.items():
|
|
172
|
+
if value is not None and hasattr(yaml_config.deploy, key):
|
|
173
|
+
# Handle nested ReplicasConfig
|
|
174
|
+
if key == "replicas" and isinstance(value, dict):
|
|
175
|
+
if yaml_config.deploy.replicas is None:
|
|
176
|
+
yaml_config.deploy.replicas = ReplicasConfig()
|
|
177
|
+
for rkey, rvalue in value.items():
|
|
178
|
+
if rvalue is not None:
|
|
179
|
+
setattr(yaml_config.deploy.replicas, rkey, rvalue)
|
|
180
|
+
else:
|
|
181
|
+
setattr(yaml_config.deploy, key, value)
|
|
182
|
+
|
|
183
|
+
# Update build fields if provided
|
|
184
|
+
if build_config:
|
|
185
|
+
if yaml_config.build is None:
|
|
186
|
+
yaml_config.build = BuildConfig()
|
|
187
|
+
updates = asdict(build_config)
|
|
188
|
+
for key, value in updates.items():
|
|
189
|
+
if value is not None and hasattr(yaml_config.build, key):
|
|
190
|
+
# Handle nested BuildLogsConfig
|
|
191
|
+
if key == "logs" and isinstance(value, dict):
|
|
192
|
+
if yaml_config.build.logs is None:
|
|
193
|
+
yaml_config.build.logs = BuildLogsConfig()
|
|
194
|
+
for lkey, lvalue in value.items():
|
|
195
|
+
if lvalue is not None:
|
|
196
|
+
setattr(yaml_config.build.logs, lkey, lvalue)
|
|
197
|
+
else:
|
|
198
|
+
setattr(yaml_config.build, key, value)
|
|
199
|
+
|
|
200
|
+
# Update secrets fields if provided
|
|
201
|
+
if secrets_config:
|
|
202
|
+
if yaml_config.secrets is None:
|
|
203
|
+
yaml_config.secrets = SecretsConfig()
|
|
204
|
+
updates = asdict(secrets_config)
|
|
205
|
+
for key, value in updates.items():
|
|
206
|
+
if value is not None and hasattr(yaml_config.secrets, key):
|
|
207
|
+
setattr(yaml_config.secrets, key, value)
|
|
208
|
+
|
|
60
209
|
# Convert back to dict and clean None values
|
|
61
210
|
raw_output = asdict(yaml_config)
|
|
62
211
|
|
|
@@ -66,10 +215,11 @@ def update_agent_config(app_dir: Path, agent_config: AgentConfig):
|
|
|
66
215
|
with open(config_file, "w") as f:
|
|
67
216
|
yaml.dump(output_data, f, default_flow_style=False, sort_keys=False)
|
|
68
217
|
|
|
218
|
+
|
|
69
219
|
# Helper to remove None values recursively
|
|
70
220
|
def clean_nones(value):
|
|
71
221
|
if isinstance(value, dict):
|
|
72
222
|
return {k: clean_nones(v) for k, v in value.items() if v is not None}
|
|
73
223
|
if isinstance(value, list):
|
|
74
224
|
return [clean_nones(v) for v in value if v is not None]
|
|
75
|
-
return value
|
|
225
|
+
return value
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: videosdkagent-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: VideoSDK CLI tool
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: VIDEOSDK <sdk@videosdk.live>
|
|
6
6
|
Requires-Dist: aiohttp>=3.9.0
|
|
7
7
|
Requires-Dist: click>=8.1.0
|
|
8
8
|
Requires-Dist: docker-image-py>=0.1.13
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
videosdk_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
videosdk_cli/auth.py,sha256=A3tMffpoNoYFHkPFbl-84pjHXqLFLMMJ5oH0S_ruWTc,3495
|
|
3
|
+
videosdk_cli/build.py,sha256=pUjKXhK-GCOvIDPBCYlyPM5SkBzIyW7Mditog8VmXC8,46545
|
|
4
|
+
videosdk_cli/main.py,sha256=MS7os7BuOGOhFMcENx6LL5BXP16bGgCu5_FvdAIj3Hk,3217
|
|
5
|
+
videosdk_cli/projects.py,sha256=e2xHqxTFGpGuGU4eVVfrMGnXX8PULJbREy7CTDHHr9s,4359
|
|
6
|
+
videosdk_cli/run_agent.py,sha256=3Rlki35Q3cTnJnYCbFiCE9EoHfLnQCq79skH-JCA-oA,3205
|
|
7
|
+
videosdk_cli/templates.py,sha256=CLGVGSR_HafIUIyqd1A7jNzMSXcszW5mxyb45eRkcPE,7627
|
|
8
|
+
videosdk_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
videosdk_cli/utils/analytics.py,sha256=4PLCURNI5xiULC3aOjdPRfmAP0JvwYDhwRSb_w84ynI,1975
|
|
10
|
+
videosdk_cli/utils/api_client.py,sha256=ljBre_vaJtmrpwNoovJf6kcPSjLgNcLK_L4U2CNUDKI,3726
|
|
11
|
+
videosdk_cli/utils/auth_api_client.py,sha256=5ins-e3B1QQlpkpQBWoBGGseVexchvXuO0BWSWzbSQA,1361
|
|
12
|
+
videosdk_cli/utils/config_manager.py,sha256=yygGOp-CaU1KINClLZC7nFKI8Ob7vzuL-7cBP20dNEU,1893
|
|
13
|
+
videosdk_cli/utils/exceptions.py,sha256=1Tjxxmw8Ts8DOQEZqnJBmNAFgJ0CpcHkAQ95RLQs4iw,133
|
|
14
|
+
videosdk_cli/utils/project_config.py,sha256=X-jro6nUDkZ1ZTdJ324JfqE8_0MCZMyGqGUDhM_e0Rs,3141
|
|
15
|
+
videosdk_cli/utils/template_helper.py,sha256=r2O7mLe5TC3Sl-fJBduhifJz_Xb25bhKHS8pMTq6CR8,8729
|
|
16
|
+
videosdk_cli/utils/template_options.py,sha256=BFNCmn6lLUCJ-eZ9TkhVbRtMNTP89EVm8MHhZQv61Q0,1872
|
|
17
|
+
videosdk_cli/utils/videosdk_yaml_helper.py,sha256=IIb8HAF8cR4Mths4VHJG9bVy-2tnyGUUfVy8HiZjrLE,7426
|
|
18
|
+
videosdk_cli/utils/apis/deployment_client.py,sha256=MIay-XUql5FUr4NmyqAHfB14z11Hu3FJSiFpWCu9V5U,10689
|
|
19
|
+
videosdk_cli/utils/apis/error.py,sha256=UgJI1a0ZU1ZYKAxz01pOKhFYP1XrIsz8JQVVv7XwgDc,248
|
|
20
|
+
videosdk_cli/utils/apis/videosdk_auth_api_client.py,sha256=w_Gpx_kpjzEtMa3zcL4Wb45NcC65ztpqPrmnRlAjxoQ,1863
|
|
21
|
+
videosdk_cli/utils/manager/agent_manager.py,sha256=7SujOT5j7gbTNQ9KOmXfjqUS8-JBp9FYGbqPcnRTnxU,17021
|
|
22
|
+
videosdk_cli/utils/ui/kv_blocks.py,sha256=_KJZ0_q7AbaIDIh9kx6VvHuSQ3AN5fYo-vsMjYIBbFM,288
|
|
23
|
+
videosdk_cli/utils/ui/progress_runner.py,sha256=CHMQgU6ABKbD6AysMF1EVb3iBrnLqEC8OJPtCjuGYcA,1891
|
|
24
|
+
videosdk_cli/utils/ui/theme.py,sha256=LJ4gEmOwSD5kXH-tesPJBGHlieVoJe1uxRwjtDnqkFY,5857
|
|
25
|
+
videosdkagent_cli-0.0.5.dist-info/METADATA,sha256=pVFC6YFG3tpaJiJqUbOl6S_S_w0ZV6sGk5fKt78FyLs,446
|
|
26
|
+
videosdkagent_cli-0.0.5.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
|
|
27
|
+
videosdkagent_cli-0.0.5.dist-info/entry_points.txt,sha256=7qv3V6B64WCwEshsIsTUc3fYcg6iNbnVJJOc6WE4U1E,51
|
|
28
|
+
videosdkagent_cli-0.0.5.dist-info/RECORD,,
|