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
videosdk_cli/__init__.py
ADDED
|
File without changes
|
videosdk_cli/auth.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import click
|
|
3
|
+
from videosdk_cli.utils.config_manager import get_config_value, set_config_value
|
|
4
|
+
from videosdk_cli.utils.api_client import VideoSDKClient
|
|
5
|
+
from videosdk_cli.utils.auth_api_client import AuthAPIClient
|
|
6
|
+
import os
|
|
7
|
+
import webbrowser
|
|
8
|
+
|
|
9
|
+
async def handle_auth():
|
|
10
|
+
API_BASE_URL = os.environ.get("API_BASE_URL","http://localhost:8000")
|
|
11
|
+
client = AuthAPIClient(API_BASE_URL)
|
|
12
|
+
click.echo("ā¹ Initiating browser authentication...")
|
|
13
|
+
|
|
14
|
+
data = await client.start_auth()
|
|
15
|
+
|
|
16
|
+
generated_url= os.environ.get("AUTH_DASHBOARD_URL","http://localhost:4949") + f"/cli/confirm-auth?requestId={data['requestId']}"
|
|
17
|
+
webbrowser.open(generated_url)
|
|
18
|
+
|
|
19
|
+
click.echo(f"ā Opened <{generated_url}> in your default browser.\n")
|
|
20
|
+
|
|
21
|
+
requestId = data["requestId"]
|
|
22
|
+
expires_in = data["expiresIn"]
|
|
23
|
+
|
|
24
|
+
spinner = ["|", "/", "-", "\\"]
|
|
25
|
+
poll_interval = 3
|
|
26
|
+
attempts = expires_in // poll_interval
|
|
27
|
+
|
|
28
|
+
click.echo("Waiting for authentication... ", nl=False)
|
|
29
|
+
|
|
30
|
+
for i in range(attempts):
|
|
31
|
+
click.echo(spinner[i % len(spinner)], nl=False)
|
|
32
|
+
click.echo("\b", nl=False)
|
|
33
|
+
await asyncio.sleep(poll_interval)
|
|
34
|
+
|
|
35
|
+
status = await client.check_status(requestId)
|
|
36
|
+
|
|
37
|
+
if status["status"] == "approved":
|
|
38
|
+
set_config_value("VIDEOSDK_AUTH_TOKEN", status["token"])
|
|
39
|
+
|
|
40
|
+
click.echo(
|
|
41
|
+
f"\nā Successfully authenticated)"
|
|
42
|
+
)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if status["status"] == "expired":
|
|
46
|
+
raise click.ClickException(
|
|
47
|
+
"Authentication link expired. Please run `videosdk auth` again."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
raise click.ClickException(
|
|
51
|
+
"Authentication timed out. Please try again."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def handle_logout():
|
|
55
|
+
set_config_value("VIDEOSDK_AUTH_TOKEN", None)
|
|
56
|
+
click.echo("ā Successfully logged out.")
|
|
57
|
+
|
videosdk_cli/build.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
from email.policy import default
|
|
2
|
+
import click
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
import os
|
|
6
|
+
import asyncio
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from rich.prompt import Prompt
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from InquirerPy import inquirer
|
|
11
|
+
from videosdk_cli.secret_set import secret_set
|
|
12
|
+
from videosdk_cli.utils.project_config import get_config_option
|
|
13
|
+
from docker_image.reference import Reference
|
|
14
|
+
from videosdk_cli.utils.apis.deployment_client import DeploymentClient
|
|
15
|
+
from videosdk_cli.utils.ui.progress_runner import run_with_progress
|
|
16
|
+
from videosdk_cli.utils.manager.agent_manager import init_agent, list_agents_manager,describe_agent_manager,image_pull_secret_manager
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
def agent_cli():
|
|
21
|
+
"""Build a Docker image from a Dockerfile and push it to a registry."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@agent_cli.command(name="init")
|
|
25
|
+
@click.option("--name", "-n", default=None, help='Name of the agent')
|
|
26
|
+
def init(name):
|
|
27
|
+
console.print("[bold blue]Initializing Agent...[/bold blue]")
|
|
28
|
+
try:
|
|
29
|
+
asyncio.run(run_with_progress(
|
|
30
|
+
init_agent(Path(os.getcwd()), name),
|
|
31
|
+
console=console,
|
|
32
|
+
title="Initializing Agent",
|
|
33
|
+
duration=5.0,
|
|
34
|
+
))
|
|
35
|
+
console.print(f"[bold green]Agent initialized successfully.[/bold green]")
|
|
36
|
+
except Exception as e:
|
|
37
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@agent_cli.command(name="build")
|
|
42
|
+
@click.option('--tag','-t', default=None, help='Name and optionally a tag in the \'name:tag\' format.')
|
|
43
|
+
@click.option('--file','-f', 'dockerfile',default=None, help='Name of the Dockerfile')
|
|
44
|
+
def build(tag, dockerfile):
|
|
45
|
+
"""Build a Docker image from a Dockerfile."""
|
|
46
|
+
platform = 'linux/arm64'
|
|
47
|
+
# Determine Dockerfile path
|
|
48
|
+
if dockerfile:
|
|
49
|
+
dockerfile_path = Path(os.getcwd()) / dockerfile
|
|
50
|
+
else:
|
|
51
|
+
dockerfile_path = Path(os.getcwd()) / "Dockerfile"
|
|
52
|
+
dockerfile = "Dockerfile"
|
|
53
|
+
|
|
54
|
+
if not dockerfile_path.exists():
|
|
55
|
+
console.print(f"[bold red]Error:[/bold red] Dockerfile not found at {dockerfile_path}")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
# Resolve tag
|
|
59
|
+
tag = get_config_option(
|
|
60
|
+
tag,
|
|
61
|
+
['agent', 'image'],
|
|
62
|
+
required=True,
|
|
63
|
+
fail_message="--tag not provided and 'agent.image' not found in videosdk.yaml"
|
|
64
|
+
)
|
|
65
|
+
tag = tag.lower()
|
|
66
|
+
|
|
67
|
+
console.print(f"[bold blue]Building Docker image...[/bold blue]")
|
|
68
|
+
console.print(f"Platform: [green]{platform}[/green]")
|
|
69
|
+
console.print(f"Tag: [green]{tag}[/green]")
|
|
70
|
+
console.print(f"Dockerfile: [green]{dockerfile_path}[/green]")
|
|
71
|
+
|
|
72
|
+
cmd = [
|
|
73
|
+
"docker", "build",
|
|
74
|
+
"--platform", platform,
|
|
75
|
+
"-t", tag,
|
|
76
|
+
"-f", str(dockerfile),
|
|
77
|
+
"."
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
subprocess.run(cmd, check=True)
|
|
82
|
+
console.print(f"[bold green]Successfully built image: {tag}[/bold green]")
|
|
83
|
+
except subprocess.CalledProcessError as e:
|
|
84
|
+
console.print(f"[bold red]Failed to build image.[/bold red]")
|
|
85
|
+
sys.exit(e.returncode)
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
console.print(f"[bold red]Error:[/bold red] 'docker' command not found. Please ensure Docker is installed and in your PATH.")
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@agent_cli.command(name="push")
|
|
92
|
+
@click.option( "-t", "--tag", default=None, help='Name and optionally a tag in the \'name:tag\' format.')
|
|
93
|
+
@click.option("-r", "--registry", default=None, help='Registry URL custom or default dockerhub.io')
|
|
94
|
+
@click.option("-u", "--username", default=None, help='Registry username')
|
|
95
|
+
@click.option("-p", "--password", default=None, help='Registry password')
|
|
96
|
+
def push(tag, registry, username, password):
|
|
97
|
+
"""Push a Docker image to a registry."""
|
|
98
|
+
|
|
99
|
+
# 1. Resolve Tag
|
|
100
|
+
tag = get_config_option(
|
|
101
|
+
tag,
|
|
102
|
+
['agent', 'image'],
|
|
103
|
+
required=True,
|
|
104
|
+
fail_message="--tag not provided and 'agent.image' not found in videosdk.yaml"
|
|
105
|
+
)
|
|
106
|
+
tag = tag.lower()
|
|
107
|
+
ref = Reference.parse_normalized_named(tag)
|
|
108
|
+
hostname,name =ref.split_hostname()
|
|
109
|
+
registry = registry or hostname
|
|
110
|
+
|
|
111
|
+
console.print(f"[bold blue]Pushing Docker image...[/bold blue]")
|
|
112
|
+
console.print(f"Tag: [green]{tag}[/green]")
|
|
113
|
+
console.print(f"Registry: [green]{registry}[/green]")
|
|
114
|
+
|
|
115
|
+
# 4. Docker Login (if credentials provided)
|
|
116
|
+
if username and password:
|
|
117
|
+
login_cmd = ["docker", "login", "-u", username, "-p", password]
|
|
118
|
+
if registry != "docker.io":
|
|
119
|
+
login_cmd.append(registry)
|
|
120
|
+
|
|
121
|
+
console.print(f"Logging into {registry}...")
|
|
122
|
+
try:
|
|
123
|
+
subprocess.run(login_cmd, check=True)
|
|
124
|
+
console.print(f"[bold green]Login successful.[/bold green]")
|
|
125
|
+
except subprocess.CalledProcessError:
|
|
126
|
+
console.print(f"[bold red]Login failed.[/bold red]")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
except FileNotFoundError:
|
|
129
|
+
console.print(f"[bold red]Error:[/bold red] 'docker' command not found.")
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
|
|
132
|
+
# 5. Docker Push
|
|
133
|
+
push_cmd = ["docker", "push", tag]
|
|
134
|
+
try:
|
|
135
|
+
subprocess.run(push_cmd, check=True)
|
|
136
|
+
console.print(f"[bold green]Successfully pushed image: {tag}[/bold green]")
|
|
137
|
+
except subprocess.CalledProcessError:
|
|
138
|
+
console.print(f"[bold red]Failed to push image.[/bold red]")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
console.print(f"[bold red]Error:[/bold red] 'docker' command not found.")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@agent_cli.command(name="deploy")
|
|
146
|
+
@click.option("--tag", "-t", default=None, help='Name and optionally a tag in the \'name:tag\' format.')
|
|
147
|
+
@click.option(
|
|
148
|
+
"--minReplica",
|
|
149
|
+
default=1,
|
|
150
|
+
type=click.IntRange(min=0),
|
|
151
|
+
help="Minimum replicas (>= 0)",
|
|
152
|
+
)
|
|
153
|
+
@click.option(
|
|
154
|
+
"--maxReplica",
|
|
155
|
+
default=5,
|
|
156
|
+
type=click.IntRange(min=0, max=50),
|
|
157
|
+
help="Maximum replicas (0ā50)",
|
|
158
|
+
)
|
|
159
|
+
@click.option("--profile", default="cpu-small", help='Profile (cpu-small, cpu-medium, cpu-large)')
|
|
160
|
+
@click.option("--agent-id", help='Agent ID')
|
|
161
|
+
@click.option("--env-secret", help='Environment Secret')
|
|
162
|
+
@click.option("--image-pull-secret", help='Image Pull Secret')
|
|
163
|
+
def deploy(tag, minreplica, maxreplica, profile, agent_id,env_secret,image_pull_secret):
|
|
164
|
+
"""Deploy an agent."""
|
|
165
|
+
|
|
166
|
+
if minreplica > maxreplica:
|
|
167
|
+
raise click.BadParameter(
|
|
168
|
+
"--minReplica cannot be greater than --maxReplica"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Resolve Tag/Image URL
|
|
172
|
+
tag = get_config_option(
|
|
173
|
+
tag,
|
|
174
|
+
['agent', 'image'],
|
|
175
|
+
required=True,
|
|
176
|
+
fail_message="--tag not provided and 'agent.image' not found in videosdk.yaml"
|
|
177
|
+
)
|
|
178
|
+
# The image URL is essentially the tag
|
|
179
|
+
ref = Reference.parse_normalized_named(tag)
|
|
180
|
+
hostname,name =ref.split_hostname()
|
|
181
|
+
image_url = tag
|
|
182
|
+
|
|
183
|
+
agent_id = get_config_option(
|
|
184
|
+
agent_id,
|
|
185
|
+
['agent', 'id'],
|
|
186
|
+
required=True,
|
|
187
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
console.print(f"[bold blue]Deploying Agent...[/bold blue]")
|
|
191
|
+
console.print(f"Agent ID: [green]{agent_id}[/green]")
|
|
192
|
+
console.print(f"Image: [green]{image_url}[/green]")
|
|
193
|
+
console.print(f"Replicas: [green]{minreplica}-{maxreplica}[/green]")
|
|
194
|
+
console.print(f"Profile: [green]{profile}[/green]")
|
|
195
|
+
|
|
196
|
+
client = DeploymentClient()
|
|
197
|
+
try:
|
|
198
|
+
asyncio.run(run_with_progress(
|
|
199
|
+
client.agent_deployment(
|
|
200
|
+
agent_id=agent_id,
|
|
201
|
+
image_url=image_url,
|
|
202
|
+
min_replica=minreplica,
|
|
203
|
+
max_replica=maxreplica,
|
|
204
|
+
profile=profile,
|
|
205
|
+
image_cr=hostname,
|
|
206
|
+
image_pull_secret=image_pull_secret,
|
|
207
|
+
env_secret=env_secret
|
|
208
|
+
),
|
|
209
|
+
console=console,
|
|
210
|
+
title="Deploying Agent",
|
|
211
|
+
duration=5.0,
|
|
212
|
+
))
|
|
213
|
+
console.print(f"[bold green]Deployment triggered successfully for agent: {agent_id}[/bold green]")
|
|
214
|
+
except KeyboardInterrupt:
|
|
215
|
+
console.print("\n[yellow]Deployment cancelled by user.[/yellow]")
|
|
216
|
+
raise click.Abort()
|
|
217
|
+
except Exception as e:
|
|
218
|
+
console.print(f"[bold red]Deployment failed:[/bold red] {e}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@agent_cli.command(name="deploy-update")
|
|
222
|
+
@click.option("--deployment-id","-d",required=True,help="deployment id you want to update")
|
|
223
|
+
@click.option(
|
|
224
|
+
"--minReplica",
|
|
225
|
+
type=click.IntRange(min=0),
|
|
226
|
+
help="Minimum replicas (>= 0)",
|
|
227
|
+
)
|
|
228
|
+
@click.option(
|
|
229
|
+
"--maxReplica",
|
|
230
|
+
type=click.IntRange(min=0, max=50),
|
|
231
|
+
help="Maximum replicas (0ā50)",
|
|
232
|
+
)
|
|
233
|
+
@click.option("--profile", help='Profile (cpu-small, cpu-medium, cpu-large)')
|
|
234
|
+
@click.option("--agent-id", help='Agent ID')
|
|
235
|
+
@click.option("--env-secret", help='Environment Secret')
|
|
236
|
+
@click.option("--image-pull-secret", help='Image Pull Secret')
|
|
237
|
+
def deploy_update(deployment_id,minreplica,maxreplica,profile,agent_id,env_secret,image_pull_secret):
|
|
238
|
+
console.print(f"[bold blue]Updating Deployment...[/bold blue]")
|
|
239
|
+
console.print(f"Min Replicas: [green]{minreplica}[/green]")
|
|
240
|
+
console.print(f"Max Replicas: [green]{maxreplica}[/green]")
|
|
241
|
+
console.print(f"Profile: [green]{profile}[/green]")
|
|
242
|
+
# Resolve Tag/Image URL
|
|
243
|
+
|
|
244
|
+
image_url = None
|
|
245
|
+
hostname = None
|
|
246
|
+
|
|
247
|
+
agent_id = get_config_option(
|
|
248
|
+
agent_id,
|
|
249
|
+
['agent', 'id'],
|
|
250
|
+
required=True,
|
|
251
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
client = DeploymentClient()
|
|
255
|
+
try:
|
|
256
|
+
asyncio.run(run_with_progress(
|
|
257
|
+
client.agent_deployment_update(
|
|
258
|
+
deployment_id=deployment_id,
|
|
259
|
+
agent_id=agent_id,
|
|
260
|
+
image_cr=hostname,
|
|
261
|
+
image_url=image_url,
|
|
262
|
+
env_secret=env_secret,
|
|
263
|
+
image_pull_secret=image_pull_secret,
|
|
264
|
+
min_replica=minreplica,
|
|
265
|
+
max_replica=maxreplica,
|
|
266
|
+
profile=profile
|
|
267
|
+
),
|
|
268
|
+
console=console,
|
|
269
|
+
title="Updating Deployment",
|
|
270
|
+
duration=5.0,
|
|
271
|
+
))
|
|
272
|
+
console.print(f"[bold green]Deployment updated successfully[/bold green]")
|
|
273
|
+
except KeyboardInterrupt:
|
|
274
|
+
console.print("\n[yellow]Deployment update cancelled by user.[/yellow]")
|
|
275
|
+
raise click.Abort()
|
|
276
|
+
except Exception as e:
|
|
277
|
+
console.print(f"[bold red]Deployment update failed:[/bold red] {e}")
|
|
278
|
+
|
|
279
|
+
# @agent_cli.command(name="deploy list")
|
|
280
|
+
# @click.option("--deployment-id", default=None, help='Deployment ID')
|
|
281
|
+
# def deploy_list(deployment_id):
|
|
282
|
+
@agent_cli.command(name="deploy-deactivate")
|
|
283
|
+
@click.option("--deployment-id", default=None,required=True,help='Deployment ID')
|
|
284
|
+
@click.option("--force",default=False,help='Force deactivate')
|
|
285
|
+
@click.option("--agent-id",help='Agent ID')
|
|
286
|
+
def deploy_deactivate(deployment_id,force,agent_id):
|
|
287
|
+
"""Deactivate a deployment."""
|
|
288
|
+
console.print("[bold blue]Deactivating Deployment...[/bold blue]")
|
|
289
|
+
agent_id = get_config_option(
|
|
290
|
+
agent_id,
|
|
291
|
+
['agent', 'id'],
|
|
292
|
+
required=True,
|
|
293
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
294
|
+
)
|
|
295
|
+
client = DeploymentClient()
|
|
296
|
+
try:
|
|
297
|
+
asyncio.run(run_with_progress(
|
|
298
|
+
client.agent_deactivate(deployment_id=deployment_id,agent_id=agent_id,force=force),
|
|
299
|
+
console=console,
|
|
300
|
+
title="Deactivating Deployment",
|
|
301
|
+
duration=2.0,
|
|
302
|
+
))
|
|
303
|
+
console.print("[bold green]Deployment deactivated successfully.[/bold green]")
|
|
304
|
+
except Exception as e:
|
|
305
|
+
console.print(f"[bold red]Failed to deactivate deployment:[/bold red] {e}")
|
|
306
|
+
|
|
307
|
+
@agent_cli.command(name="deploy-activate")
|
|
308
|
+
@click.option("--deployment-id", default=None,required=True,help='Deployment ID')
|
|
309
|
+
@click.option("--agent-id",help='Agent ID')
|
|
310
|
+
def deploy_activate(deployment_id,agent_id):
|
|
311
|
+
"""Activate a deployment."""
|
|
312
|
+
console.print("[bold blue]Activating Deployment...[/bold blue]")
|
|
313
|
+
agent_id = get_config_option(
|
|
314
|
+
agent_id,
|
|
315
|
+
['agent', 'id'],
|
|
316
|
+
required=True,
|
|
317
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
318
|
+
)
|
|
319
|
+
client = DeploymentClient()
|
|
320
|
+
try:
|
|
321
|
+
asyncio.run(run_with_progress(
|
|
322
|
+
client.agent_activate(deployment_id=deployment_id,agent_id=agent_id),
|
|
323
|
+
console=console,
|
|
324
|
+
title="Activating Deployment",
|
|
325
|
+
duration=2.0,
|
|
326
|
+
))
|
|
327
|
+
console.print("[bold green]Deployment activated successfully.[/bold green]")
|
|
328
|
+
except Exception as e:
|
|
329
|
+
console.print(f"[bold red]Failed to deactivate deployment:[/bold red] {e}")
|
|
330
|
+
|
|
331
|
+
@agent_cli.command(name="start")
|
|
332
|
+
@click.option("--deployment-id","-d",default=None,help='Deployment ID')
|
|
333
|
+
@click.option("--agent-id","-a",default=None,help='Agent ID')
|
|
334
|
+
@click.option("--meeting-id","-m",default=None,help='Meeting ID')
|
|
335
|
+
def start(deployment_id,agent_id,meeting_id):
|
|
336
|
+
console.print("[bold blue]Starting Deployment...[/bold blue]")
|
|
337
|
+
agent_id = get_config_option(
|
|
338
|
+
agent_id,
|
|
339
|
+
['agent', 'id'],
|
|
340
|
+
required=True,
|
|
341
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
342
|
+
)
|
|
343
|
+
client = DeploymentClient()
|
|
344
|
+
try:
|
|
345
|
+
asyncio.run(run_with_progress(
|
|
346
|
+
client.agent_start(deployment_id=deployment_id,agent_id=agent_id,meeting_id=meeting_id),
|
|
347
|
+
console=console,
|
|
348
|
+
title="Starting Deployment",
|
|
349
|
+
duration=2.0,
|
|
350
|
+
))
|
|
351
|
+
console.print("[bold green]Deployment started successfully.[/bold green]")
|
|
352
|
+
except Exception as e:
|
|
353
|
+
console.print(f"[bold red]Failed to start deployment:[/bold red] {e}")
|
|
354
|
+
|
|
355
|
+
@agent_cli.command(name="stop")
|
|
356
|
+
@click.option("--meeting-id","-m",default=None,help='Meeting ID')
|
|
357
|
+
def stop(meeting_id):
|
|
358
|
+
console.print("[bold blue]Stopping Deployment...[/bold blue]")
|
|
359
|
+
|
|
360
|
+
client = DeploymentClient()
|
|
361
|
+
try:
|
|
362
|
+
asyncio.run(run_with_progress(
|
|
363
|
+
client.agent_stop(meeting_id=meeting_id),
|
|
364
|
+
console=console,
|
|
365
|
+
title="Stopping Deployment",
|
|
366
|
+
duration=2.0,
|
|
367
|
+
))
|
|
368
|
+
console.print("[bold green]Deployment stopped successfully.[/bold green]")
|
|
369
|
+
except Exception as e:
|
|
370
|
+
console.print(f"[bold red]Failed to stop deployment:[/bold red] {e}")
|
|
371
|
+
|
|
372
|
+
@agent_cli.command(name="list")
|
|
373
|
+
@click.option("--agent-id", default=None, help='Agent ID')
|
|
374
|
+
@click.option("--head", default=None,type=click.IntRange(min=0, max=500),help='Number of agents to list')
|
|
375
|
+
@click.option("--tail",default=None,type=click.IntRange(min=0, max=500),help='Number of agents to list')
|
|
376
|
+
def list_agents(agent_id,head,tail):
|
|
377
|
+
"""List all agents."""
|
|
378
|
+
console.print(f"[bold blue]Listing Agents...[/bold blue]")
|
|
379
|
+
console.print(f"Agent ID: [green]{agent_id}[/green]")
|
|
380
|
+
agent_id=get_config_option(
|
|
381
|
+
agent_id,
|
|
382
|
+
['agent', 'id'],
|
|
383
|
+
required=True,
|
|
384
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
385
|
+
)
|
|
386
|
+
try:
|
|
387
|
+
asyncio.run(
|
|
388
|
+
list_agents_manager(agent_id,head,tail)
|
|
389
|
+
)
|
|
390
|
+
console.print(f"[bold green]Agents listed successfully.[/bold green]")
|
|
391
|
+
except Exception as e:
|
|
392
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
393
|
+
|
|
394
|
+
@agent_cli.command(name="describe")
|
|
395
|
+
@click.option("--agent-id", default=None, help='Agent ID')
|
|
396
|
+
@click.option("--deployment-id", default=None, help='Deployment ID')
|
|
397
|
+
def describe_agent(agent_id,deployment_id):
|
|
398
|
+
console.print(f"[bold blue]Describing Agent...[/bold blue]")
|
|
399
|
+
console.print(f"Agent ID: [green]{agent_id}[/green]")
|
|
400
|
+
agent_id=get_config_option(
|
|
401
|
+
agent_id,
|
|
402
|
+
['agent', 'id'],
|
|
403
|
+
required=True,
|
|
404
|
+
fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
|
|
405
|
+
)
|
|
406
|
+
deployment_id=get_config_option(
|
|
407
|
+
deployment_id,
|
|
408
|
+
['agent', 'deploymentId'],
|
|
409
|
+
required=False,
|
|
410
|
+
fail_message="--deployment-id not provided and 'agent.deploymentId' not found in videosdk.yaml"
|
|
411
|
+
)
|
|
412
|
+
try:
|
|
413
|
+
asyncio.run(
|
|
414
|
+
describe_agent_manager(agent_id,deployment_id)
|
|
415
|
+
)
|
|
416
|
+
except Exception as e:
|
|
417
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@agent_cli.command(name="image-pull-secret")
|
|
421
|
+
@click.option("--name",required=True, help='Secret set name')
|
|
422
|
+
def image_pull_secret(name):
|
|
423
|
+
try:
|
|
424
|
+
asyncio.run(
|
|
425
|
+
image_pull_secret_manager(name)
|
|
426
|
+
)
|
|
427
|
+
except Exception as e:
|
|
428
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
429
|
+
|
|
430
|
+
agent_cli.add_command(secret_set)
|
videosdk_cli/main.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import asyncio
|
|
3
|
+
import nest_asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
load_dotenv()
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from videosdk_cli.auth import handle_auth, handle_logout
|
|
9
|
+
from videosdk_cli.projects import list_projects
|
|
10
|
+
from videosdk_cli.templates import template_cli
|
|
11
|
+
from videosdk_cli.run_agent import run_agent
|
|
12
|
+
from videosdk_cli.utils import analytics
|
|
13
|
+
from videosdk_cli.build import agent_cli
|
|
14
|
+
import atexit
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
load_dotenv()
|
|
17
|
+
nest_asyncio.apply()
|
|
18
|
+
|
|
19
|
+
CURRENT_CMD = " ".join([Path(sys.argv[0]).name] + sys.argv[1:])
|
|
20
|
+
|
|
21
|
+
def run_async(coro):
|
|
22
|
+
loop = asyncio.get_event_loop()
|
|
23
|
+
return loop.run_until_complete(coro)
|
|
24
|
+
|
|
25
|
+
@click.group()
|
|
26
|
+
def cli():
|
|
27
|
+
"""VideoSDK CLI tool"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@cli.command()
|
|
31
|
+
def auth():
|
|
32
|
+
"""Authenticate and store token"""
|
|
33
|
+
try:
|
|
34
|
+
run_async(handle_auth())
|
|
35
|
+
except KeyboardInterrupt:
|
|
36
|
+
click.echo("\nAuthentication cancelled by user.")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise click.ClickException(f"Authentication failed: Please try again")
|
|
39
|
+
|
|
40
|
+
@cli.command()
|
|
41
|
+
def logout():
|
|
42
|
+
"""Logout and remove token"""
|
|
43
|
+
try:
|
|
44
|
+
run_async(handle_logout())
|
|
45
|
+
except KeyboardInterrupt:
|
|
46
|
+
click.echo("\nLogout cancelled by user.")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise click.ClickException(f"Logout failed: Please try again")
|
|
49
|
+
|
|
50
|
+
# @cli.command()
|
|
51
|
+
# def projects():
|
|
52
|
+
# """List projects"""
|
|
53
|
+
# try:
|
|
54
|
+
# run_async(list_projects())
|
|
55
|
+
# except KeyboardInterrupt:
|
|
56
|
+
# raise
|
|
57
|
+
# except Exception as e:
|
|
58
|
+
# raise e
|
|
59
|
+
|
|
60
|
+
cli.add_command(template_cli, name="template")
|
|
61
|
+
cli.add_command(agent_cli, name="agent")
|
|
62
|
+
|
|
63
|
+
@cli.command()
|
|
64
|
+
@click.argument("project_name")
|
|
65
|
+
def run(project_name):
|
|
66
|
+
"""Run a VideoSDK project"""
|
|
67
|
+
try:
|
|
68
|
+
run_agent(project_name)
|
|
69
|
+
except KeyboardInterrupt:
|
|
70
|
+
raise
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise e
|
|
73
|
+
|
|
74
|
+
def log_command_at_exit():
|
|
75
|
+
"""Ensures analytics logging happens on exit (normal or KeyboardInterrupt)"""
|
|
76
|
+
try:
|
|
77
|
+
asyncio.run(analytics.log_command(CURRENT_CMD))
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Failed to log analytics: {e}")
|
|
80
|
+
|
|
81
|
+
# atexit.register(log_command_at_exit)
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
try:
|
|
85
|
+
cli()
|
|
86
|
+
except KeyboardInterrupt:
|
|
87
|
+
click.echo("\n[bold yellow]Command interrupted by user[/bold yellow]")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
click.echo(f"[bold red]Error:[/bold red] {e}")
|
videosdk_cli/projects.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from videosdk_cli.utils.config_manager import get_config_value, set_config_value, save_config
|
|
5
|
+
from videosdk_cli.utils.api_client import VideoSDKClient, VideoSDKTokenClient, AuthenticationError
|
|
6
|
+
from InquirerPy import inquirer
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def list_projects():
|
|
12
|
+
"""Fetches and displays a list of your projects with arrow selection."""
|
|
13
|
+
auth_token = get_config_value("auth_token")
|
|
14
|
+
|
|
15
|
+
if not auth_token:
|
|
16
|
+
console.print("[red]Authentication details not found. Please run:[/red] videosdk auth")
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
client = VideoSDKClient(auth_token=auth_token)
|
|
21
|
+
data = await client.fetch_projects()
|
|
22
|
+
|
|
23
|
+
projects = data.get("data", [])
|
|
24
|
+
if not projects:
|
|
25
|
+
console.print("[yellow]No projects found.[/yellow]")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
table = Table(title="Available Projects", show_lines=True)
|
|
29
|
+
table.add_column("No.", justify="right", style="cyan")
|
|
30
|
+
table.add_column("Project Name", style="magenta")
|
|
31
|
+
table.add_column("Created At", style="green")
|
|
32
|
+
table.add_column("API Key", style="yellow")
|
|
33
|
+
table.add_column("Status")
|
|
34
|
+
|
|
35
|
+
for i, p in enumerate(projects, start=1):
|
|
36
|
+
status = "[green]Active[/green]" if not p.get("isDeactived") else "[red]Inactive[/red]"
|
|
37
|
+
table.add_row(
|
|
38
|
+
str(i),
|
|
39
|
+
p.get("name"),
|
|
40
|
+
p.get("createdAt"),
|
|
41
|
+
p.get("keyPair", {}).get("apiKey"),
|
|
42
|
+
status
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
console.print(table)
|
|
46
|
+
|
|
47
|
+
choices = [f"{p['name']} ({p['keyPair']['apiKey']})" for p in projects]
|
|
48
|
+
choice_label = await inquirer.select(
|
|
49
|
+
message="Select a project:",
|
|
50
|
+
choices=choices,
|
|
51
|
+
pointer="š",
|
|
52
|
+
instruction="Use ā ā to navigate, Enter to select",
|
|
53
|
+
).execute_async()
|
|
54
|
+
|
|
55
|
+
selected_index = choices.index(choice_label)
|
|
56
|
+
selected = projects[selected_index]
|
|
57
|
+
|
|
58
|
+
set_config_value("selected_project_name", selected["name"])
|
|
59
|
+
set_config_value("selected_project_api_key", selected.get("keyPair", {}).get("apiKey"))
|
|
60
|
+
console.print(f"[green]ā
Selected project:[/green] {selected['name']}")
|
|
61
|
+
|
|
62
|
+
await gen_token()
|
|
63
|
+
|
|
64
|
+
except AuthenticationError as e:
|
|
65
|
+
console.print(f"[red]Authentication error: {e}[/red]")
|
|
66
|
+
await handle_auth_reset()
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def gen_token():
|
|
73
|
+
"""Fetches VIDEOSDK_AUTH_TOKEN for the selected project."""
|
|
74
|
+
project_name = get_config_value("selected_project_name")
|
|
75
|
+
api_key = get_config_value("selected_project_api_key")
|
|
76
|
+
auth_token = get_config_value("auth_token")
|
|
77
|
+
|
|
78
|
+
if not api_key or not project_name:
|
|
79
|
+
console.print("[red]Project details not found. Please run:[/red] videosdk projects")
|
|
80
|
+
return
|
|
81
|
+
if not auth_token:
|
|
82
|
+
console.print("[red]Authentication details not found. Please run:[/red] videosdk auth")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
expires_str = f"{365}d"
|
|
87
|
+
|
|
88
|
+
client = VideoSDKTokenClient(auth_token, api_key, expires_str)
|
|
89
|
+
data = await client.fetch_token()
|
|
90
|
+
token = data.get("token")
|
|
91
|
+
|
|
92
|
+
if token:
|
|
93
|
+
console.print(f"[green]ā
Generated token:[/green]")
|
|
94
|
+
set_config_value("VIDEOSDK_AUTH_TOKEN", token)
|
|
95
|
+
else:
|
|
96
|
+
console.print("[red]ā Token not found. Check your API key or auth token.[/red]")
|
|
97
|
+
|
|
98
|
+
except AuthenticationError as e:
|
|
99
|
+
console.print(f"[red]Authentication error: {e}[/red]")
|
|
100
|
+
await handle_auth_reset()
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def handle_auth_reset():
|
|
107
|
+
"""Ask user before resetting auth token."""
|
|
108
|
+
reset = await inquirer.confirm(
|
|
109
|
+
message="Do you want to reset your authentication token?",
|
|
110
|
+
default=False
|
|
111
|
+
).execute_async()
|
|
112
|
+
|
|
113
|
+
if reset:
|
|
114
|
+
save_config(None)
|
|
115
|
+
console.print("[yellow]Auth token cleared. Please run 'videosdk auth' again.[/yellow]")
|
|
116
|
+
else:
|
|
117
|
+
console.print("[yellow]Auth token not cleared. You may experience errors until re-authenticated.[/yellow]")
|