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.
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}")
@@ -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]")