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.
- videosdk_cli/__init__.py +0 -0
- videosdk_cli/auth.py +57 -0
- videosdk_cli/build.py +430 -0
- videosdk_cli/main.py +89 -0
- videosdk_cli/projects.py +117 -0
- videosdk_cli/run_agent.py +88 -0
- videosdk_cli/secret_set.py +82 -0
- videosdk_cli/templates.py +168 -0
- videosdk_cli/utils/__init__.py +0 -0
- videosdk_cli/utils/analytics.py +61 -0
- videosdk_cli/utils/api_client.py +107 -0
- videosdk_cli/utils/apis/deployment_client.py +172 -0
- videosdk_cli/utils/apis/error.py +10 -0
- videosdk_cli/utils/apis/videosdk_auth_api_client.py +58 -0
- videosdk_cli/utils/auth_api_client.py +49 -0
- videosdk_cli/utils/config_manager.py +66 -0
- videosdk_cli/utils/exceptions.py +3 -0
- videosdk_cli/utils/manager/agent_manager.py +381 -0
- videosdk_cli/utils/project_config.py +87 -0
- videosdk_cli/utils/template_helper.py +204 -0
- videosdk_cli/utils/template_options.py +51 -0
- videosdk_cli/utils/ui/kv_blocks.py +8 -0
- videosdk_cli/utils/ui/progress_runner.py +53 -0
- videosdk_cli/utils/videosdk_yaml_helper.py +75 -0
- videosdkagent_cli-0.0.1.dist-info/METADATA +15 -0
- videosdkagent_cli-0.0.1.dist-info/RECORD +28 -0
- videosdkagent_cli-0.0.1.dist-info/WHEEL +5 -0
- videosdkagent_cli-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_executables(venv_path: Path):
|
|
12
|
+
"""Determine python and pip executables based on venv/conda."""
|
|
13
|
+
is_windows = sys.platform == "win32"
|
|
14
|
+
|
|
15
|
+
venv_path = Path(venv_path)
|
|
16
|
+
|
|
17
|
+
if (venv_path / "Scripts").exists() or (venv_path / "bin").exists():
|
|
18
|
+
python_exe = venv_path / ("Scripts/python.exe" if is_windows else "bin/python")
|
|
19
|
+
pip_exe = venv_path / ("Scripts/pip.exe" if is_windows else "bin/pip")
|
|
20
|
+
else:
|
|
21
|
+
python_exe = venv_path / ("python.exe" if is_windows else "bin/python")
|
|
22
|
+
pip_exe = venv_path / ("Scripts/pip.exe" if is_windows else "bin/pip")
|
|
23
|
+
|
|
24
|
+
return python_exe, pip_exe
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run_agent(project_name: str):
|
|
28
|
+
parent_dir = Path.cwd()
|
|
29
|
+
project_dir = parent_dir / project_name
|
|
30
|
+
|
|
31
|
+
if not project_dir.exists():
|
|
32
|
+
console.print(f"[bold red]Error:[/bold red] Project '{project_name}' not found.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
config_path = project_dir / "config.json"
|
|
36
|
+
if not config_path.exists():
|
|
37
|
+
console.print(f"[bold red]Error:[/bold red] config.json not found in project '{project_name}'.")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
with open(config_path, "r") as f:
|
|
41
|
+
config = json.load(f)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
venv_path_str = config.get("venv_path", str(project_dir / "venv"))
|
|
45
|
+
venv_path = Path(venv_path_str)
|
|
46
|
+
python_executable, pip_executable = get_executables(venv_path)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if not python_executable.exists():
|
|
50
|
+
console.print(f"[bold yellow]Virtual environment not found. Creating...[/bold yellow]")
|
|
51
|
+
subprocess.run([sys.executable, "-m", "venv", str(venv_path)], check=True)
|
|
52
|
+
console.print("[green]✅ Virtual environment created[/green]")
|
|
53
|
+
python_executable, pip_executable = get_executables(venv_path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if not config.get("requirements_installed", False):
|
|
57
|
+
console.print("[cyan]Installing project requirements...[/cyan]")
|
|
58
|
+
subprocess.run([str(pip_executable), "install", "-r", str(project_dir / "requirements.txt")], check=True)
|
|
59
|
+
print("requirements", )
|
|
60
|
+
config["requirements_installed"] = True
|
|
61
|
+
with open(config_path, "w") as f:
|
|
62
|
+
json.dump(config, f, indent=4)
|
|
63
|
+
console.print("[green]✅ Requirements installed[/green]")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
template_file = config.get("template_file")
|
|
67
|
+
template_path = project_dir / template_file
|
|
68
|
+
|
|
69
|
+
if not template_path.exists():
|
|
70
|
+
console.print(f"[bold red]Error:[/bold red] Template file '{template_file}' not found.")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
console.print(f"[cyan]Running project '{project_name}'...[/cyan]")
|
|
74
|
+
|
|
75
|
+
env_vars = os.environ.copy()
|
|
76
|
+
env_file = project_dir / ".env"
|
|
77
|
+
if env_file.exists():
|
|
78
|
+
from dotenv import dotenv_values
|
|
79
|
+
env_vars.update(dotenv_values(env_file))
|
|
80
|
+
try:
|
|
81
|
+
subprocess.run(
|
|
82
|
+
[str(python_executable), str(template_path), "console"],
|
|
83
|
+
env=env_vars,
|
|
84
|
+
cwd=parent_dir,
|
|
85
|
+
check=True
|
|
86
|
+
)
|
|
87
|
+
except subprocess.CalledProcessError as e:
|
|
88
|
+
console.print(f"[bold red]Project exited with code {e.returncode}[/bold red]")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
import click
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
import asyncio
|
|
5
|
+
from videosdk_cli.utils.manager.agent_manager import secret_set_manager,list_secret_manager,remove_secret_set,describe_secret_set,add_secret_set,remove_secret_set_key
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
@click.group(name="secret-set")
|
|
9
|
+
def secret_set():
|
|
10
|
+
"""Manage secrets"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@secret_set.command(name="create")
|
|
16
|
+
@click.option("--name",required=True,help='Secret name (lowercase, no spaces)')
|
|
17
|
+
@click.option('--file','-f',required=False,help='Secret file path')
|
|
18
|
+
def secret_set_create(name,file):
|
|
19
|
+
try:
|
|
20
|
+
console.print("[bold blue]Setting Secret...[/bold blue]")
|
|
21
|
+
|
|
22
|
+
asyncio.run(
|
|
23
|
+
secret_set_manager(name,file)
|
|
24
|
+
)
|
|
25
|
+
except click.Abort:
|
|
26
|
+
console.print("\nCancelled. No secrets were saved.")
|
|
27
|
+
except Exception as e:
|
|
28
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
29
|
+
|
|
30
|
+
@secret_set.command(name="list")
|
|
31
|
+
def secret_set_list():
|
|
32
|
+
try:
|
|
33
|
+
console.print("[bold blue]Getting Secret...[/bold blue]")
|
|
34
|
+
asyncio.run(
|
|
35
|
+
list_secret_manager()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
40
|
+
|
|
41
|
+
@secret_set.command(name="delete")
|
|
42
|
+
@click.option("--name",required=True,help='Secret name (lowercase, no spaces)')
|
|
43
|
+
def secret_set_delete(name):
|
|
44
|
+
try:
|
|
45
|
+
asyncio.run(
|
|
46
|
+
remove_secret_set(name)
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
50
|
+
|
|
51
|
+
@secret_set.command(name="describe")
|
|
52
|
+
@click.option("--name",required=True,help='Secret name (lowercase, no spaces)')
|
|
53
|
+
def secret_set_describe(name):
|
|
54
|
+
try:
|
|
55
|
+
asyncio.run(
|
|
56
|
+
describe_secret_set(name)
|
|
57
|
+
)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
60
|
+
|
|
61
|
+
@secret_set.command(name="add")
|
|
62
|
+
@click.option("--name",required=True,help='Secret name (lowercase, no spaces)')
|
|
63
|
+
def secret_set_add(name):
|
|
64
|
+
try:
|
|
65
|
+
console.print("[bold blue]Setting Secret...[/bold blue]")
|
|
66
|
+
|
|
67
|
+
asyncio.run(
|
|
68
|
+
add_secret_set(name)
|
|
69
|
+
)
|
|
70
|
+
console.print(f"[bold green]Secret set successfully.[/bold green]")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
73
|
+
|
|
74
|
+
@secret_set.command(name="remove")
|
|
75
|
+
@click.option("--name",required=True,help='Secret name (lowercase, no spaces)')
|
|
76
|
+
def secret_set_remove_key(name):
|
|
77
|
+
try:
|
|
78
|
+
asyncio.run(
|
|
79
|
+
remove_secret_set_key(name)
|
|
80
|
+
)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import asyncio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
7
|
+
from rich.prompt import Prompt
|
|
8
|
+
|
|
9
|
+
from videosdk_cli.utils import template_options, template_helper, videosdk_yaml_helper
|
|
10
|
+
from videosdk_cli.utils.videosdk_yaml_helper import AgentConfig
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def template_cli():
|
|
16
|
+
"""Manage, download, and create projects from official VideoSDK templates."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@template_cli.command(name="list")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--refresh", "-r",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help="Force refresh template metadata from server"
|
|
25
|
+
)
|
|
26
|
+
def list_templates(refresh: bool):
|
|
27
|
+
"""List all available templates."""
|
|
28
|
+
asyncio.run(list_templates_async(refresh))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def list_templates_async(force_refresh: bool = False):
|
|
32
|
+
"""Fetch and display templates from server/cache."""
|
|
33
|
+
await template_helper.fetch_template_cache(force_refresh=force_refresh)
|
|
34
|
+
|
|
35
|
+
kv = template_options.KeyValuePair()
|
|
36
|
+
templates = kv.templates
|
|
37
|
+
|
|
38
|
+
if not templates:
|
|
39
|
+
console.print("[yellow]No templates found in template metadata.[/yellow]")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
table = Table(title="Available VideoSDK Templates", show_lines=True)
|
|
43
|
+
table.add_column("Template id", style="cyan", no_wrap=True)
|
|
44
|
+
table.add_column("File Name", style="green", no_wrap=True)
|
|
45
|
+
table.add_column("Description", style="white")
|
|
46
|
+
# table.add_column("Mode", style="magenta")
|
|
47
|
+
# table.add_column("Pipeline Type", style="blue")
|
|
48
|
+
|
|
49
|
+
for name, tpl in templates.items():
|
|
50
|
+
table.add_row(
|
|
51
|
+
name,
|
|
52
|
+
tpl.file_name,
|
|
53
|
+
tpl.description
|
|
54
|
+
# tpl.mode,
|
|
55
|
+
# tpl.pipe_type
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
console.print(table)
|
|
59
|
+
@template_cli.command(name="get")
|
|
60
|
+
@click.option(
|
|
61
|
+
"--template", "-t",
|
|
62
|
+
required=True,
|
|
63
|
+
help="Template name to create project from"
|
|
64
|
+
)
|
|
65
|
+
@click.argument("app_name")
|
|
66
|
+
def create_project(template: str, app_name: str):
|
|
67
|
+
"""Create a new project from a template."""
|
|
68
|
+
asyncio.run(create_project_async(template, app_name))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def create_project_async(template_id: str, app_name: str):
|
|
72
|
+
"""Create project, fetch template code from server, prompt providers if needed."""
|
|
73
|
+
await template_helper.fetch_template_cache()
|
|
74
|
+
|
|
75
|
+
kv = template_options.KeyValuePair()
|
|
76
|
+
tpl = kv.get_template(template_id)
|
|
77
|
+
|
|
78
|
+
if not tpl:
|
|
79
|
+
console.print(f"[bold red]Error:[/bold red] Template '[cyan]{template_id}[/cyan]' not found in cache.")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
app_dir = Path(app_name)
|
|
83
|
+
if app_dir.exists():
|
|
84
|
+
console.print(f"[bold red]Error:[/bold red] Directory '[cyan]{app_name}[/cyan]' already exists.")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
console.print(f"[cyan]→ Mode:[/cyan] {tpl.mode}")
|
|
88
|
+
console.print(f"[cyan]→ Pipeline type:[/cyan] {tpl.pipe_type}")
|
|
89
|
+
|
|
90
|
+
providers_dict = {}
|
|
91
|
+
imports = []
|
|
92
|
+
provider_modules = []
|
|
93
|
+
|
|
94
|
+
if tpl.mode == "preconfig":
|
|
95
|
+
if tpl.pipe_type == "cascading":
|
|
96
|
+
STT, LLM, TTS = tpl.STT_options, tpl.LLM_options, tpl.TTS_options
|
|
97
|
+
providers_dict = {"STT": STT, "LLM": LLM, "TTS": TTS}
|
|
98
|
+
elif tpl.pipe_type == "realtime":
|
|
99
|
+
[RealtimeProvider] = tpl.provider_options
|
|
100
|
+
providers_dict = {"Realtime": RealtimeProvider}
|
|
101
|
+
|
|
102
|
+
for provider in providers_dict.values():
|
|
103
|
+
if isinstance(provider, list):
|
|
104
|
+
for p in provider:
|
|
105
|
+
module, *_ = template_helper.PROVIDER_IMPORTS[p]
|
|
106
|
+
provider_modules.append(module)
|
|
107
|
+
else:
|
|
108
|
+
module, *_ = template_helper.PROVIDER_IMPORTS[provider]
|
|
109
|
+
provider_modules.append(module)
|
|
110
|
+
|
|
111
|
+
elif tpl.mode == "custom" and tpl.pipe_type == "cascading":
|
|
112
|
+
STT = await template_helper.select_provider_arrow("Select STT provider:", tpl.STT_options)
|
|
113
|
+
LLM = await template_helper.select_provider_arrow("Select LLM provider:", tpl.LLM_options)
|
|
114
|
+
TTS = await template_helper.select_provider_arrow("Select TTS provider:", tpl.TTS_options)
|
|
115
|
+
providers_dict = {"STT": STT, "LLM": LLM, "TTS": TTS}
|
|
116
|
+
imports, provider_modules = template_helper.generate_cascading_providers(providers_dict)
|
|
117
|
+
|
|
118
|
+
api_keys = await template_helper.prompt_provider_api_keys(providers_dict)
|
|
119
|
+
|
|
120
|
+
env_path = await template_helper.prompt_existing_env()
|
|
121
|
+
if env_path and not env_path.exists():
|
|
122
|
+
console.print("[red]Provided environment path does not exist. A new venv will be created.[/red]")
|
|
123
|
+
env_path = None
|
|
124
|
+
|
|
125
|
+
with Progress(
|
|
126
|
+
SpinnerColumn(),
|
|
127
|
+
TextColumn("[progress.description]{task.description}"),
|
|
128
|
+
transient=True,
|
|
129
|
+
) as progress:
|
|
130
|
+
progress.add_task(description=f"Creating project [cyan]{app_name}[/cyan]...", total=None)
|
|
131
|
+
try:
|
|
132
|
+
app_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
template_helper.write_env_file(app_dir, providers_dict, api_keys)
|
|
134
|
+
template_helper.write_requirements(app_dir, template_helper.STATIC_REQUIREMENTS + provider_modules)
|
|
135
|
+
videosdk_yaml_helper.update_agent_config(app_dir, AgentConfig(templateId=template_id))
|
|
136
|
+
except Exception as e:
|
|
137
|
+
console.print(f"[bold red]Error creating project files: {e}[/bold red]")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
import aiohttp
|
|
142
|
+
async with aiohttp.ClientSession() as session:
|
|
143
|
+
url = f"{template_helper.CDN_BASE_URL}/{tpl.file_name}"
|
|
144
|
+
async with session.get(url) as resp:
|
|
145
|
+
if resp.status == 404:
|
|
146
|
+
console.print(f"[bold red]Template '{template_id}' not found on server[/bold red]")
|
|
147
|
+
return
|
|
148
|
+
resp.raise_for_status()
|
|
149
|
+
template_code = await resp.text()
|
|
150
|
+
except Exception as e:
|
|
151
|
+
console.print(f"[bold red]Error fetching template code: {e}[/bold red]")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
imports_str = "\n".join(imports) if imports else ""
|
|
155
|
+
template_helper.write_template_file(app_dir, tpl.file_name, imports_str, template_code)
|
|
156
|
+
|
|
157
|
+
template_helper.write_config(
|
|
158
|
+
app_dir=app_dir,
|
|
159
|
+
template_id=template_id,
|
|
160
|
+
file_name=tpl.file_name,
|
|
161
|
+
description=tpl.description,
|
|
162
|
+
providers=providers_dict,
|
|
163
|
+
env_path=str(env_path) if env_path else None,
|
|
164
|
+
auto_configured=(tpl.mode=="preconfig"),
|
|
165
|
+
requirements_installed=False
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
console.print(f"[green]✅ Project '[bold cyan]{app_name}[/bold cyan]' created successfully![/green]")
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import click
|
|
5
|
+
from videosdk_cli.utils.config_manager import get_config_value
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR = Path(click.get_app_dir("videosdk_config"))
|
|
8
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
9
|
+
ANALYTICS_FILE = CONFIG_DIR / "analytics.json"
|
|
10
|
+
|
|
11
|
+
_pending_analytics = []
|
|
12
|
+
|
|
13
|
+
async def log_command(command: str):
|
|
14
|
+
"""
|
|
15
|
+
Log a CLI command for analytics.
|
|
16
|
+
Send to server every 5 commands.
|
|
17
|
+
"""
|
|
18
|
+
user_id = get_config_value("user_id") or "unknown"
|
|
19
|
+
username = get_config_value("username") or "unknown"
|
|
20
|
+
api_key = get_config_value("selected_project_api_key") or "unknown"
|
|
21
|
+
|
|
22
|
+
entry = {
|
|
23
|
+
"timestamp": datetime.now().isoformat(),
|
|
24
|
+
"user_id": user_id,
|
|
25
|
+
"username": username,
|
|
26
|
+
"api_key": api_key,
|
|
27
|
+
"command": command
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ANALYTICS_FILE.exists():
|
|
31
|
+
data = json.loads(ANALYTICS_FILE.read_text(encoding="utf-8"))
|
|
32
|
+
else:
|
|
33
|
+
data = {"count": 0, "entries": []}
|
|
34
|
+
|
|
35
|
+
data["entries"].append(entry)
|
|
36
|
+
data["count"] += 1
|
|
37
|
+
|
|
38
|
+
ANALYTICS_FILE.write_text(json.dumps(data, indent=4), encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
if data["count"] >= 5:
|
|
41
|
+
await send_analytics_to_server(data)
|
|
42
|
+
ANALYTICS_FILE.write_text(json.dumps({"count": 0, "entries": []}, indent=4))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def send_analytics_to_server(data):
|
|
46
|
+
"""
|
|
47
|
+
Send analytics to your server endpoint.
|
|
48
|
+
"""
|
|
49
|
+
import aiohttp
|
|
50
|
+
from videosdk_cli.utils.template_helper import CDN_BASE_URL
|
|
51
|
+
|
|
52
|
+
url = f"{CDN_BASE_URL}/analytics/collect/"
|
|
53
|
+
try:
|
|
54
|
+
async with aiohttp.ClientSession() as session:
|
|
55
|
+
async with session.post(url, json=data) as resp:
|
|
56
|
+
if resp.status == 200:
|
|
57
|
+
print("[green]Analytics sent successfully[/green]")
|
|
58
|
+
else:
|
|
59
|
+
print(f"[red]Failed to send analytics: {resp.status}[/red]")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"[red]Error sending analytics: {e}[/red]")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import logging
|
|
3
|
+
from videosdk_cli.utils.config_manager import load_config, save_config, set_config_value
|
|
4
|
+
from InquirerPy import inquirer
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
logger.setLevel(logging.INFO)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthenticationError(Exception):
|
|
12
|
+
"""Raised when the stored authentication is invalid or expired."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VideoSDKClient:
|
|
18
|
+
def __init__(self, auth_token):
|
|
19
|
+
self.auth_token = auth_token
|
|
20
|
+
self.url = "https://api.videosdk.live/v1/apikeys"
|
|
21
|
+
self.headers = {
|
|
22
|
+
"Authorization": self.auth_token,
|
|
23
|
+
"Content-Type": "application/json"
|
|
24
|
+
}
|
|
25
|
+
self.res_data = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def fetch_projects(self):
|
|
29
|
+
async with aiohttp.ClientSession() as session:
|
|
30
|
+
async with session.get(self.url, headers=self.headers) as resp:
|
|
31
|
+
text = await resp.text()
|
|
32
|
+
if 400 <= resp.status < 500:
|
|
33
|
+
logger.warning("Session expired or invalid credentials.")
|
|
34
|
+
reset = ask_reset_credentials()
|
|
35
|
+
if reset:
|
|
36
|
+
self._reset_credentials()
|
|
37
|
+
raise AuthenticationError(
|
|
38
|
+
f"Authentication failed ({resp.status}): {text}"
|
|
39
|
+
)
|
|
40
|
+
resp.raise_for_status()
|
|
41
|
+
self.res_data = await resp.json()
|
|
42
|
+
return self.res_data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _reset_credentials(self):
|
|
46
|
+
"""Clear stored auth_token."""
|
|
47
|
+
logger.info("Resetting stored credentials...")
|
|
48
|
+
try:
|
|
49
|
+
save_config(None)
|
|
50
|
+
|
|
51
|
+
except Exception:
|
|
52
|
+
config = load_config()
|
|
53
|
+
config = None
|
|
54
|
+
save_config(config)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class VideoSDKTokenClient:
|
|
59
|
+
def __init__(self, auth_token, api_key, expiresIn):
|
|
60
|
+
self.url = f"https://api.videosdk.live/v1/apikeys/{api_key}/token"
|
|
61
|
+
self.headers = {
|
|
62
|
+
"Authorization": auth_token,
|
|
63
|
+
"Content-Type": "application/json"
|
|
64
|
+
}
|
|
65
|
+
self.payload = {
|
|
66
|
+
"apiKey": api_key,
|
|
67
|
+
"permissions": ["allow_join"],
|
|
68
|
+
"expiresIn": expiresIn
|
|
69
|
+
}
|
|
70
|
+
self.res_data = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def fetch_token(self):
|
|
74
|
+
async with aiohttp.ClientSession() as session:
|
|
75
|
+
async with session.post(self.url, headers=self.headers, json=self.payload) as resp:
|
|
76
|
+
text = await resp.text()
|
|
77
|
+
if 400 <= resp.status < 500:
|
|
78
|
+
logger.warning("Session expired or invalid credentials.")
|
|
79
|
+
reset = ask_reset_credentials()
|
|
80
|
+
if reset:
|
|
81
|
+
self._reset_credentials()
|
|
82
|
+
raise AuthenticationError(f"Authentication failed ({resp.status}): {text}")
|
|
83
|
+
resp.raise_for_status()
|
|
84
|
+
self.res_data = await resp.json()
|
|
85
|
+
return self.res_data
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _reset_credentials(self):
|
|
89
|
+
"""Clear stored auth_token."""
|
|
90
|
+
logger.info("Resetting stored credentials...")
|
|
91
|
+
try:
|
|
92
|
+
save_config(None)
|
|
93
|
+
except Exception:
|
|
94
|
+
config = load_config()
|
|
95
|
+
config = None
|
|
96
|
+
save_config(config)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ask_reset_credentials():
|
|
101
|
+
"""Prompt user before clearing saved auth token."""
|
|
102
|
+
result = inquirer.confirm(
|
|
103
|
+
message="Request failed. Do you want to reset your stored authentication token?",
|
|
104
|
+
default=False
|
|
105
|
+
).execute()
|
|
106
|
+
return result
|
|
107
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from videosdk_cli.utils.apis.videosdk_auth_api_client import VideoSDKAsyncClient
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class DeploymentClient(VideoSDKAsyncClient):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
super().__init__()
|
|
7
|
+
|
|
8
|
+
async def agent_deployment(self,
|
|
9
|
+
agent_id: str,
|
|
10
|
+
image_url: str,
|
|
11
|
+
min_replica: int = 1,
|
|
12
|
+
max_replica: int = 5,
|
|
13
|
+
profile: str = "cpu-small",
|
|
14
|
+
image_type: str = "public",
|
|
15
|
+
image_cr: str = "docker_hub",
|
|
16
|
+
image_pull_secret: Optional[str] = None,
|
|
17
|
+
env_secret: Optional[str] = None
|
|
18
|
+
):
|
|
19
|
+
payload = {
|
|
20
|
+
"agentId": agent_id,
|
|
21
|
+
"image": {
|
|
22
|
+
"imageType": image_type,
|
|
23
|
+
"imageCR": image_cr,
|
|
24
|
+
"imageUrl": image_url
|
|
25
|
+
},
|
|
26
|
+
"minReplica": min_replica,
|
|
27
|
+
"maxReplica": max_replica,
|
|
28
|
+
"profile": profile
|
|
29
|
+
}
|
|
30
|
+
if image_pull_secret is not None:
|
|
31
|
+
payload.setdefault("image", {})["imagePullSecret"]= image_pull_secret
|
|
32
|
+
if env_secret is not None:
|
|
33
|
+
payload["envSecret"] = env_secret
|
|
34
|
+
return await self._request("POST", "/ai/v1/cloud/deployments", json=payload)
|
|
35
|
+
|
|
36
|
+
async def agent_deployment_update(self,
|
|
37
|
+
deployment_id: str,
|
|
38
|
+
agent_id: str,
|
|
39
|
+
image_url: Optional[str] = None,
|
|
40
|
+
min_replica: Optional[int] = None,
|
|
41
|
+
max_replica: Optional[int] = None,
|
|
42
|
+
profile: Optional[str] = None,
|
|
43
|
+
image_type: Optional[str] = None,
|
|
44
|
+
image_cr: Optional[str] = None,
|
|
45
|
+
image_pull_secret: Optional[str] = None,
|
|
46
|
+
env_secret: Optional[str] = None
|
|
47
|
+
):
|
|
48
|
+
# payload = {
|
|
49
|
+
# "agentId": agent_id,
|
|
50
|
+
# "image": {
|
|
51
|
+
# "imageType": image_type,
|
|
52
|
+
# "imageCR": image_cr,
|
|
53
|
+
# "imageUrl": image_url
|
|
54
|
+
# },
|
|
55
|
+
# "minReplica": min_replica,
|
|
56
|
+
# "maxReplica": max_replica,
|
|
57
|
+
# "profile": profile
|
|
58
|
+
# }
|
|
59
|
+
payload = {
|
|
60
|
+
"agentId": agent_id
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if min_replica is not None:
|
|
64
|
+
payload["minReplica"] = min_replica
|
|
65
|
+
|
|
66
|
+
if max_replica is not None:
|
|
67
|
+
payload["maxReplica"] = max_replica
|
|
68
|
+
|
|
69
|
+
if profile is not None:
|
|
70
|
+
payload["profile"] = profile
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if env_secret is not None:
|
|
74
|
+
payload["envSecret"] = env_secret
|
|
75
|
+
|
|
76
|
+
image = {}
|
|
77
|
+
|
|
78
|
+
if image_url is not None:
|
|
79
|
+
image["imageUrl"] = image_url
|
|
80
|
+
|
|
81
|
+
if image_type is not None:
|
|
82
|
+
image["imageType"] = image_type
|
|
83
|
+
|
|
84
|
+
if image_cr is not None:
|
|
85
|
+
image["imageCR"] = image_cr
|
|
86
|
+
|
|
87
|
+
if image_pull_secret is not None:
|
|
88
|
+
image["imagePullSecret"] = image_pull_secret
|
|
89
|
+
|
|
90
|
+
if image:
|
|
91
|
+
payload["image"] = image
|
|
92
|
+
return await self._request("PUT", f"/ai/v1/cloud/deployments/{agent_id}/{deployment_id}", json=payload)
|
|
93
|
+
|
|
94
|
+
async def agent_deactivate(self, agent_id: str, deployment_id: str,force:bool = False):
|
|
95
|
+
return await self._request("PUT", f"/ai/v1/cloud/deployments/{agent_id}/{deployment_id}/activate",json={"activate": False,"force": force})
|
|
96
|
+
|
|
97
|
+
async def agent_activate(self, agent_id: str, deployment_id: str):
|
|
98
|
+
return await self._request("PUT", f"/ai/v1/cloud/deployments/{agent_id}/{deployment_id}/activate",json={"activate": True})
|
|
99
|
+
|
|
100
|
+
async def agent_init(self, name: Optional[str] = None):
|
|
101
|
+
"""
|
|
102
|
+
Initialize an agent.
|
|
103
|
+
|
|
104
|
+
If name is provided, it will be used.
|
|
105
|
+
Otherwise, the backend will generate one.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
payload = {}
|
|
109
|
+
|
|
110
|
+
if name:
|
|
111
|
+
payload["name"] = name
|
|
112
|
+
|
|
113
|
+
return await self._request(
|
|
114
|
+
"POST",
|
|
115
|
+
"/ai/v1/cloud/agents",
|
|
116
|
+
json=payload if payload else None,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def agent_list(self,agent_id:str,head:Optional[int] = None,tail:Optional[int] = None):
|
|
120
|
+
if head is None and tail is None:
|
|
121
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}?perPage=5&sort=-1")
|
|
122
|
+
if head is None:
|
|
123
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}?perPage={tail}&sort=-1")
|
|
124
|
+
if tail is None:
|
|
125
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}?perPage={head}&sort=1")
|
|
126
|
+
|
|
127
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}")
|
|
128
|
+
|
|
129
|
+
async def agent_describe(self,agent_id:str,deployment_id:Optional[str] = None):
|
|
130
|
+
if deployment_id is None:
|
|
131
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}/latest")
|
|
132
|
+
return await self._request("GET", f"/ai/v1/cloud/deployments/{agent_id}/{deployment_id}")
|
|
133
|
+
|
|
134
|
+
async def secret_set(self,name:str,secrets:dict,type:Optional[str] = "NORMAL"):
|
|
135
|
+
payload = {
|
|
136
|
+
"name": name,
|
|
137
|
+
"keys": secrets,
|
|
138
|
+
"type": type
|
|
139
|
+
}
|
|
140
|
+
return await self._request("POST", "/ai/v1/cloud/secrets", json=payload)
|
|
141
|
+
|
|
142
|
+
async def secret_list(self):
|
|
143
|
+
return await self._request("GET", "/ai/v1/cloud/secrets")
|
|
144
|
+
|
|
145
|
+
async def secret_remove(self,name:str):
|
|
146
|
+
return await self._request("DELETE", f"/ai/v1/cloud/secrets/name/{name}")
|
|
147
|
+
|
|
148
|
+
async def secret_get(self,name:str):
|
|
149
|
+
return await self._request("GET", f"/ai/v1/cloud/secrets/name/{name}")
|
|
150
|
+
|
|
151
|
+
async def secret_add_key(self,name:str,secrets:dict):
|
|
152
|
+
print(secrets)
|
|
153
|
+
payload = secrets
|
|
154
|
+
return await self._request("PATCH", f"/ai/v1/cloud/secrets/keys/{name}", json=payload)
|
|
155
|
+
|
|
156
|
+
async def secret_remove_key(self,name:str,keys:list[str]):
|
|
157
|
+
payload = {
|
|
158
|
+
"keys": keys
|
|
159
|
+
}
|
|
160
|
+
return await self._request("DELETE", f"/ai/v1/cloud/secrets/keys/{name}", json=payload)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def agent_start(self,agent_id:str,deployment_id:str,meeting_id:Optional[str] = None):
|
|
164
|
+
if meeting_id is None:
|
|
165
|
+
response = await self._request("POST", f"/v2/rooms")
|
|
166
|
+
meeting_id = response.get("roomId")
|
|
167
|
+
print("Meeting ID: ", meeting_id)
|
|
168
|
+
return await self._request("POST", f"/v2/agent/dispatch", json={"agentId": agent_id,"versionId": deployment_id,"meetingId": meeting_id})
|
|
169
|
+
|
|
170
|
+
async def agent_stop(self,meeting_id:str):
|
|
171
|
+
return await self._request("POST", f"/v2/sessions/end", json={"meetingId": meeting_id})
|
|
172
|
+
|