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/build.py CHANGED
@@ -1,49 +1,147 @@
1
- from email.policy import default
2
1
  import click
3
2
  import sys
4
3
  import subprocess
5
4
  import os
6
5
  import asyncio
7
6
  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
7
  from docker_image.reference import Reference
8
+ from videosdk_cli.utils.project_config import get_config_option
14
9
  from videosdk_cli.utils.apis.deployment_client import DeploymentClient
15
10
  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()
11
+ from videosdk_cli.utils.ui.theme import (
12
+ console,
13
+ print_header,
14
+ print_success,
15
+ print_error,
16
+ print_warning,
17
+ print_info,
18
+ print_key_value,
19
+ print_divider,
20
+ )
21
+ from videosdk_cli.utils.manager.agent_manager import (
22
+ init_agent,
23
+ list_versions_manager,
24
+ list_sessions_manager,
25
+ describe_version_manager,
26
+ image_pull_secret_manager,
27
+ secret_set_manager,
28
+ list_secret_manager,
29
+ remove_secret_set,
30
+ describe_secret_set,
31
+ add_secret_set,
32
+ remove_secret_set_key,
33
+ version_logs_manager,
34
+ )
35
+
18
36
 
37
+ # =============================================================================
38
+ # MAIN AGENT GROUP
39
+ # =============================================================================
19
40
  @click.group()
20
41
  def agent_cli():
21
- """Build a Docker image from a Dockerfile and push it to a registry."""
42
+ """
43
+ Manage VideoSDK AI Agents.
44
+
45
+ Build, deploy, and manage your AI agents with VideoSDK cloud infrastructure.
46
+
47
+ \b
48
+ Quick Start:
49
+ $ videosdk agent init --name my-agent
50
+ $ videosdk agent build --image myrepo/myagent:v1
51
+ $ videosdk agent push --image myrepo/myagent:v1
52
+ $ videosdk agent deploy --image myrepo/myagent:v1
53
+ """
22
54
  pass
23
55
 
56
+
57
+ # =============================================================================
58
+ # AGENT INIT
59
+ # =============================================================================
24
60
  @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]")
61
+ @click.option(
62
+ "--name",
63
+ "-n",
64
+ default=None,
65
+ help="Name for your deployment (auto-generated if not provided)",
66
+ )
67
+ @click.option(
68
+ "--template",
69
+ "-t",
70
+ default=None,
71
+ help="Template ID to use (e.g., Template01)",
72
+ )
73
+ def init(name, template):
74
+ """
75
+ Initialize a new agent deployment.
76
+
77
+ Creates a new agent and deployment in VideoSDK cloud and generates a
78
+ videosdk.yaml configuration file with the agent ID and deployment ID.
79
+
80
+ \b
81
+ Examples:
82
+ $ videosdk agent init --name my-assistant --template Template01
83
+ $ videosdk agent init -n my-bot -t Template01
84
+ $ videosdk agent init # auto-generates name
85
+ """
86
+ print_header("Initializing Deployment")
28
87
  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]")
88
+ asyncio.run(
89
+ run_with_progress(
90
+ init_agent(Path(os.getcwd()), name=name, template=template),
91
+ console=console,
92
+ title="Initializing Deployment",
93
+ duration=5.0,
94
+ )
95
+ )
96
+ print_success("Deployment initialized successfully")
97
+ print_info("Next step: Build your agent Docker image")
98
+ console.print(
99
+ " [bold]videosdk agent build --image <your-docker-username>/<image-name>:<tag>[/bold]"
100
+ )
36
101
  except Exception as e:
37
- console.print(f"[bold red]Error:[/bold red] {e}")
102
+ print_error(f"Error: {e}")
38
103
  sys.exit(1)
39
104
 
40
105
 
106
+ # =============================================================================
107
+ # AGENT BUILD
108
+ # =============================================================================
41
109
  @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'
110
+ @click.option(
111
+ "--image",
112
+ "-i",
113
+ default=None,
114
+ help="Image name with optional tag (e.g., myrepo/myagent:v1)",
115
+ )
116
+ @click.option(
117
+ "--file",
118
+ "-f",
119
+ "dockerfile",
120
+ default=None,
121
+ help="Path to Dockerfile (default: ./Dockerfile)",
122
+ )
123
+ @click.option(
124
+ "--enable-logs",
125
+ is_flag=True,
126
+ default=False,
127
+ help="Upload build logs to VideoSDK cloud",
128
+ )
129
+ def build(image, dockerfile, enable_logs):
130
+ """
131
+ Build a Docker image for your agent.
132
+
133
+ Builds a Docker image using the specified Dockerfile. The image tag
134
+ can be provided via --image flag or read from videosdk.yaml.
135
+
136
+ \b
137
+ Examples:
138
+ $ videosdk agent build --image myrepo/myagent:v1
139
+ $ videosdk agent build --image myrepo/myagent:v1 --file Dockerfile.prod
140
+ $ videosdk agent build --enable-logs # upload build logs to cloud
141
+ $ videosdk agent build # uses image from videosdk.yaml
142
+ """
143
+ platform = "linux/arm64"
144
+
47
145
  # Determine Dockerfile path
48
146
  if dockerfile:
49
147
  dockerfile_path = Path(os.getcwd()) / dockerfile
@@ -52,379 +150,1381 @@ def build(tag, dockerfile):
52
150
  dockerfile = "Dockerfile"
53
151
 
54
152
  if not dockerfile_path.exists():
55
- console.print(f"[bold red]Error:[/bold red] Dockerfile not found at {dockerfile_path}")
153
+ print_error(f"Dockerfile not found at {dockerfile_path}")
56
154
  sys.exit(1)
57
155
 
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"
156
+ # Resolve image tag: CLI > build.image > agent.image (legacy)
157
+ image = get_config_option(
158
+ image,
159
+ ["build", "image"],
160
+ required=False,
64
161
  )
65
- tag = tag.lower()
162
+ if image is None:
163
+ image = get_config_option(
164
+ None,
165
+ ["agent", "image"],
166
+ required=True,
167
+ fail_message="--image not provided and 'build.image' or 'agent.image' not found in videosdk.yaml",
168
+ )
169
+ image = image.lower()
170
+
171
+ # If logs enabled, get deployment_id and presigned URL
172
+ presigned_url = None
173
+ build_id = None
174
+ deployment_id = None
175
+
176
+ if enable_logs:
177
+ deployment_id = get_config_option(
178
+ None,
179
+ ["deploy", "id"],
180
+ required=True,
181
+ fail_message="--enable-logs requires 'deploy.id' in videosdk.yaml. Run 'videosdk agent init' first.",
182
+ )
183
+
184
+ # Extract tag from image (e.g., "myrepo/myagent:v1" -> "v1")
185
+ if ":" in image:
186
+ image_tag = image.split(":")[-1]
187
+ else:
188
+ image_tag = "latest"
189
+
190
+ print_info("Getting presigned URL for build logs...")
191
+ try:
192
+ client = DeploymentClient()
193
+ response = asyncio.run(
194
+ client.get_build_log_presigned_url(
195
+ deployment_id=deployment_id,
196
+ tag=image_tag,
197
+ )
198
+ )
199
+ presigned_url = response.get("presignedUrl")
200
+ build_id = response.get("buildId")
201
+ print_success(f"Build ID: {build_id}")
202
+ except Exception as e:
203
+ print_warning(f"Failed to get presigned URL: {e}")
204
+ print_warning("Build will continue without log upload")
205
+ enable_logs = False
66
206
 
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]")
207
+ print_header("Building Docker Image")
208
+ print_key_value("Platform", platform)
209
+ print_key_value("Image", image)
210
+ print_key_value("Dockerfile", str(dockerfile_path))
211
+ if enable_logs:
212
+ print_key_value("Log Upload", "Enabled")
213
+ print_key_value("Build ID", build_id)
214
+ print_divider()
71
215
 
72
216
  cmd = [
73
- "docker", "build",
74
- "--platform", platform,
75
- "-t", tag,
76
- "-f", str(dockerfile),
77
- "."
217
+ "docker",
218
+ "build",
219
+ "--platform",
220
+ platform,
221
+ "-t",
222
+ image,
223
+ "-f",
224
+ str(dockerfile),
225
+ ".",
78
226
  ]
79
227
 
228
+ build_success = False
229
+ build_output = ""
230
+
80
231
  try:
81
- subprocess.run(cmd, check=True)
82
- console.print(f"[bold green]Successfully built image: {tag}[/bold green]")
232
+ if enable_logs:
233
+ # Run with captured output (still shows to user via PIPE + real-time read)
234
+ process = subprocess.Popen(
235
+ cmd,
236
+ stdout=subprocess.PIPE,
237
+ stderr=subprocess.STDOUT,
238
+ text=True,
239
+ bufsize=1,
240
+ )
241
+
242
+ output_lines = []
243
+ for line in process.stdout:
244
+ console.print(line, end="")
245
+ output_lines.append(line)
246
+
247
+ process.wait()
248
+ build_output = "".join(output_lines)
249
+
250
+ if process.returncode != 0:
251
+ raise subprocess.CalledProcessError(process.returncode, cmd)
252
+ else:
253
+ # Run without capturing (direct output to terminal)
254
+ subprocess.run(cmd, check=True)
255
+
256
+ build_success = True
257
+ console.print()
258
+ print_success(f"Successfully built image: {image}")
259
+ print_info(f"Next step: Push your image to registry")
260
+ console.print(f" [bold]videosdk agent push --image {image}[/bold]")
261
+
83
262
  except subprocess.CalledProcessError as e:
84
- console.print(f"[bold red]Failed to build image.[/bold red]")
85
- sys.exit(e.returncode)
263
+ print_error("Failed to build image")
264
+ build_success = False
265
+ if not enable_logs:
266
+ sys.exit(e.returncode)
86
267
  except FileNotFoundError:
87
- console.print(f"[bold red]Error:[/bold red] 'docker' command not found. Please ensure Docker is installed and in your PATH.")
268
+ print_error(
269
+ "'docker' command not found. Please ensure Docker is installed and in your PATH."
270
+ )
88
271
  sys.exit(1)
89
272
 
273
+ # Handle build logs and yaml updates
274
+ from videosdk_cli.utils.videosdk_yaml_helper import (
275
+ update_agent_config,
276
+ BuildConfig,
277
+ BuildLogsConfig,
278
+ )
279
+ import yaml
280
+
281
+ if enable_logs and presigned_url and build_output:
282
+ # Upload logs if enabled
283
+ print_info("Uploading build logs...")
284
+ try:
285
+ import aiohttp
90
286
 
287
+ async def upload_logs():
288
+ async with aiohttp.ClientSession() as session:
289
+ async with session.put(
290
+ presigned_url,
291
+ data=build_output.encode("utf-8"),
292
+ headers={"Content-Type": "text/plain"},
293
+ ) as resp:
294
+ if resp.status not in (200, 201, 204):
295
+ raise Exception(f"Upload failed with status {resp.status}")
296
+
297
+ asyncio.run(upload_logs())
298
+ print_success("Build logs uploaded successfully")
299
+
300
+ # Save new build_id to videosdk.yaml (replaces any old one)
301
+ build_config = BuildConfig(logs=BuildLogsConfig(id=build_id, enabled=True))
302
+ update_agent_config(
303
+ app_dir=Path(os.getcwd()),
304
+ build_config=build_config,
305
+ )
306
+ print_success(f"Build ID saved to videosdk.yaml: {build_id}")
307
+
308
+ except Exception as e:
309
+ print_warning(f"Failed to upload build logs: {e}")
310
+ elif not enable_logs:
311
+ # Clear build logs from yaml if logs are not enabled
312
+ # This ensures old build IDs don't get used for new builds
313
+ try:
314
+ config_file = Path(os.getcwd()) / "videosdk.yaml"
315
+ if config_file.exists():
316
+ with open(config_file, "r") as f:
317
+ config_data = yaml.safe_load(f) or {}
318
+
319
+ # Remove build.logs section if it exists
320
+ if "build" in config_data and isinstance(config_data["build"], dict):
321
+ if "logs" in config_data["build"]:
322
+ del config_data["build"]["logs"]
323
+ # If build section is now empty, remove it
324
+ if not config_data["build"]:
325
+ del config_data["build"]
326
+
327
+ # Write back
328
+ with open(config_file, "w") as f:
329
+ yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
330
+ except Exception:
331
+ # Silently fail if yaml doesn't exist or can't be updated
332
+ pass
333
+
334
+ # Exit with error if build failed
335
+ if not build_success:
336
+ sys.exit(1)
337
+
338
+
339
+ # =============================================================================
340
+ # AGENT PUSH
341
+ # =============================================================================
91
342
  @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"
343
+ @click.option(
344
+ "--image", "-i", default=None, help="Image name with tag (e.g., myrepo/myagent:v1)"
345
+ )
346
+ @click.option(
347
+ "--server", "-s", default=None, help="Registry server URL (default: docker.io)"
348
+ )
349
+ @click.option(
350
+ "--username", "-u", default=None, help="Registry username for authentication"
351
+ )
352
+ @click.option(
353
+ "--password", "-p", default=None, help="Registry password for authentication"
354
+ )
355
+ def push(image, server, username, password):
356
+ """
357
+ Push a Docker image to a container registry.
358
+
359
+ Pushes the built Docker image to Docker Hub or a private registry.
360
+ Optionally authenticate with username/password.
361
+
362
+ \b
363
+ Examples:
364
+ $ videosdk agent push --image myrepo/myagent:v1
365
+ $ videosdk agent push --image myrepo/myagent:v1 --server ghcr.io -u user -p token
366
+ $ videosdk agent push # uses image from videosdk.yaml
367
+ """
368
+
369
+ # Resolve image tag: CLI > build.image > agent.image (legacy)
370
+ image = get_config_option(
371
+ image,
372
+ ["build", "image"],
373
+ required=False,
105
374
  )
106
- tag = tag.lower()
107
- ref = Reference.parse_normalized_named(tag)
108
- hostname,name =ref.split_hostname()
109
- registry = registry or hostname
375
+ if image is None:
376
+ image = get_config_option(
377
+ None,
378
+ ["agent", "image"],
379
+ required=True,
380
+ fail_message="--image not provided and 'build.image' or 'agent.image' not found in videosdk.yaml",
381
+ )
382
+ image = image.lower()
383
+ ref = Reference.parse_normalized_named(image)
384
+ hostname, name = ref.split_hostname()
385
+ registry = server or hostname
110
386
 
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]")
387
+ print_header("Pushing Docker Image")
388
+ print_key_value("Image", image)
389
+ print_key_value("Registry", registry)
390
+ print_divider()
114
391
 
115
- # 4. Docker Login (if credentials provided)
392
+ # Docker Login (if credentials provided)
116
393
  if username and password:
117
394
  login_cmd = ["docker", "login", "-u", username, "-p", password]
118
395
  if registry != "docker.io":
119
396
  login_cmd.append(registry)
120
-
121
- console.print(f"Logging into {registry}...")
397
+
398
+ print_info(f"Logging into {registry}...")
122
399
  try:
123
400
  subprocess.run(login_cmd, check=True)
124
- console.print(f"[bold green]Login successful.[/bold green]")
401
+ print_success("Login successful")
125
402
  except subprocess.CalledProcessError:
126
- console.print(f"[bold red]Login failed.[/bold red]")
403
+ print_error("Login failed")
127
404
  sys.exit(1)
128
405
  except FileNotFoundError:
129
- console.print(f"[bold red]Error:[/bold red] 'docker' command not found.")
406
+ print_error("'docker' command not found")
130
407
  sys.exit(1)
131
408
 
132
- # 5. Docker Push
133
- push_cmd = ["docker", "push", tag]
409
+ # Docker Push
410
+ push_cmd = ["docker", "push", image]
134
411
  try:
135
412
  subprocess.run(push_cmd, check=True)
136
- console.print(f"[bold green]Successfully pushed image: {tag}[/bold green]")
413
+ console.print()
414
+ print_success(f"Successfully pushed image: {image}")
415
+ print_info(f"Next step: Deploy your agent")
416
+ console.print(f" [bold]videosdk agent deploy --image {image}[/bold]")
137
417
  except subprocess.CalledProcessError:
138
- console.print(f"[bold red]Failed to push image.[/bold red]")
418
+ print_error("Failed to push image")
139
419
  sys.exit(1)
140
420
  except FileNotFoundError:
141
- console.print(f"[bold red]Error:[/bold red] 'docker' command not found.")
421
+ print_error("'docker' command not found")
142
422
  sys.exit(1)
143
423
 
144
424
 
425
+ # =============================================================================
426
+ # AGENT DEPLOY (creates a new version)
427
+ # =============================================================================
145
428
  @agent_cli.command(name="deploy")
146
- @click.option("--tag", "-t", default=None, help='Name and optionally a tag in the \'name:tag\' format.')
429
+ @click.argument("name", required=False, default=None)
147
430
  @click.option(
148
- "--minReplica",
149
- default=1,
431
+ "--image", "-i", default=None, help="Docker image URL (e.g., myrepo/myagent:v1)"
432
+ )
433
+ @click.option(
434
+ "--version-tag",
435
+ default=None,
436
+ help="Version tag (e.g., main/0.0.2)",
437
+ )
438
+ @click.option(
439
+ "--min-replica",
150
440
  type=click.IntRange(min=0),
151
- help="Minimum replicas (>= 0)",
441
+ help="Minimum number of replicas (default: 1)",
442
+ )
443
+ @click.option(
444
+ "--max-replica",
445
+ type=click.IntRange(min=0, max=10),
446
+ help="Maximum number of replicas (default: 5)",
447
+ )
448
+ @click.option(
449
+ "--profile",
450
+ default="cpu-small",
451
+ help="Compute profile: cpu-small, cpu-medium, cpu-large (default: cpu-small)",
452
+ )
453
+ @click.option(
454
+ "--agent-id",
455
+ default=None,
456
+ help="Agent ID (reads from videosdk.yaml if not provided)",
457
+ )
458
+ @click.option(
459
+ "--deployment-id",
460
+ default=None,
461
+ help="Deployment ID (reads from videosdk.yaml if not provided)",
462
+ )
463
+ @click.option(
464
+ "--env-secret", default=None, help="ID of the environment secret set to use"
465
+ )
466
+ @click.option(
467
+ "--image-pull-secret",
468
+ default=None,
469
+ help="Name of the image pull secret for private registries",
470
+ )
471
+ @click.option(
472
+ "--region", default=None, help="Deployment region (e.g., us-east, eu-west)"
152
473
  )
153
474
  @click.option(
154
- "--maxReplica",
155
- default=5,
156
- type=click.IntRange(min=0, max=50),
157
- help="Maximum replicas (0–50)",
475
+ "--build-id",
476
+ default=None,
477
+ help="Build ID from build logs (reads from videosdk.yaml if logs were enabled)",
158
478
  )
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."""
479
+ def deploy(
480
+ name,
481
+ image,
482
+ version_tag,
483
+ min_replica,
484
+ max_replica,
485
+ profile,
486
+ agent_id,
487
+ deployment_id,
488
+ env_secret,
489
+ image_pull_secret,
490
+ region,
491
+ build_id,
492
+ ):
493
+ """
494
+ Deploy a new version of your agent to VideoSDK cloud.
165
495
 
166
- if minreplica > maxreplica:
167
- raise click.BadParameter(
168
- "--minReplica cannot be greater than --maxReplica"
169
- )
496
+ Creates a new version with the specified configuration. The agent
497
+ will be scaled between min-replica and max-replica based on demand.
498
+
499
+ \b
500
+ Examples:
501
+ $ videosdk agent deploy --image myrepo/myagent:v1
502
+ $ videosdk agent deploy my-version --image myrepo/myagent:v1 --version-tag main/0.0.1
503
+ $ videosdk agent deploy --image myrepo/myagent:v1 --env-secret my-secrets --profile cpu-medium
504
+ $ videosdk agent deploy --build-id b_abc123 # link to build logs
505
+ """
170
506
 
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"
507
+ # Resolve image: CLI > build.image > agent.image (legacy)
508
+ image = get_config_option(
509
+ image,
510
+ ["build", "image"],
511
+ required=False,
177
512
  )
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
513
+ if image is None:
514
+ image = get_config_option(
515
+ None,
516
+ ["agent", "image"],
517
+ required=True,
518
+ fail_message="--image not provided and 'build.image' or 'agent.image' not found in videosdk.yaml",
519
+ )
520
+ ref = Reference.parse_normalized_named(image)
521
+ hostname, img_name = ref.split_hostname()
522
+ image_url = image
182
523
 
524
+ # Resolve agent_id
183
525
  agent_id = get_config_option(
184
526
  agent_id,
185
- ['agent', 'id'],
527
+ ["agent", "id"],
186
528
  required=True,
187
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
529
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
530
+ )
531
+
532
+ # Resolve deployment_id
533
+ deployment_id = get_config_option(
534
+ deployment_id,
535
+ ["deploy", "id"],
536
+ required=True,
537
+ fail_message="--deployment-id not provided and 'deploy.id' not found in videosdk.yaml",
538
+ )
539
+
540
+ # Resolve build_id: CLI > build.logs.id (if logs were enabled)
541
+ build_id = get_config_option(
542
+ build_id,
543
+ ["build", "logs", "id"],
544
+ required=False,
188
545
  )
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
546
 
547
+ agent_name = get_config_option(
548
+ None,
549
+ ["agent", "name"],
550
+ required=False,
551
+ )
552
+
553
+ name = get_config_option(
554
+ name,
555
+ ["deploy", "name"],
556
+ required=False,
557
+ default=f"{agent_name}'s version",
558
+ )
559
+
560
+ min_replica = get_config_option(
561
+ min_replica, ["deploy", "replicas", "min"], required=False, default=1
562
+ )
563
+ max_replica = get_config_option(
564
+ max_replica, ["deploy", "replicas", "max"], required=False, default=3
565
+ )
566
+ if max_replica < min_replica:
567
+ print_error(
568
+ f"Max replicas({max_replica}) cannot be less than min replicas({min_replica})"
569
+ )
570
+ sys.exit(1)
571
+
572
+ profile = get_config_option(profile, ["deploy", "profile"], required=False)
573
+
574
+ region = get_config_option(region, ["deploy", "region"], required=False)
575
+
576
+ env_secret = get_config_option(env_secret, ["secrets", "env"], required=False)
577
+
578
+ image_pull_secret = get_config_option(
579
+ image_pull_secret, ["secrets", "image-pull"], required=False
580
+ )
581
+ print_header("Creating Version")
582
+ if name:
583
+ print_key_value("Version Name", name)
584
+ if version_tag:
585
+ print_key_value("Version Tag", version_tag)
586
+ print_key_value("Agent ID", agent_id)
587
+ print_key_value("Deployment ID", deployment_id)
588
+ print_key_value("Image", image_url)
589
+ if min_replica:
590
+ print_key_value("Min Replicas", min_replica)
591
+ if max_replica:
592
+ print_key_value("Max Replicas", max_replica)
593
+ if profile:
594
+ print_key_value("Profile", profile)
595
+ if region:
596
+ print_key_value("Region", region)
597
+ if env_secret:
598
+ print_key_value("Env Secret", env_secret)
599
+ if image_pull_secret:
600
+ print_key_value("Image Pull Secret", image_pull_secret)
601
+ if build_id:
602
+ print_key_value("Build ID", build_id)
603
+ print_divider()
196
604
  client = DeploymentClient()
197
605
  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]")
606
+ response = asyncio.run(
607
+ run_with_progress(
608
+ client.create_version(
609
+ name=name,
610
+ agent_id=agent_id,
611
+ deployment_id=deployment_id,
612
+ version_tag=version_tag,
613
+ image_url=image_url,
614
+ min_replica=min_replica,
615
+ max_replica=max_replica,
616
+ profile=profile,
617
+ image_cr=hostname,
618
+ image_pull_secret=image_pull_secret,
619
+ env_secret=env_secret,
620
+ region=region,
621
+ build_id=build_id,
622
+ ),
623
+ console=console,
624
+ title="Creating Version",
625
+ duration=5.0,
626
+ )
627
+ )
628
+ version_id = response.get("id") if response else None
629
+ print_success(f"Version created successfully for agent: {agent_id} and versionId: {version_id}")
630
+ if version_id:
631
+ print_key_value("Version ID", version_id)
632
+ print_info("Next step: Check version status")
633
+ console.print(
634
+ f" [bold]videosdk agent version status -v {version_id}[/bold]"
635
+ )
214
636
  except KeyboardInterrupt:
215
- console.print("\n[yellow]Deployment cancelled by user.[/yellow]")
637
+ print_warning("Version creation cancelled by user")
216
638
  raise click.Abort()
217
639
  except Exception as e:
218
- console.print(f"[bold red]Deployment failed:[/bold red] {e}")
640
+ print_error(f"Version creation failed: {e}")
219
641
 
220
642
 
221
- @agent_cli.command(name="deploy-update")
222
- @click.option("--deployment-id","-d",required=True,help="deployment id you want to update")
643
+ @agent_cli.command(name="logs")
223
644
  @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
645
+ "--version-id",
646
+ "-v",
647
+ default=None,
648
+ help="Version ID (uses latest if not provided)",
649
+ )
650
+ @click.option(
651
+ "--agent-id",
652
+ default=None,
653
+ help="Agent ID (reads from videosdk.yaml if not provided)",
654
+ )
655
+ @click.option(
656
+ "--limit",
657
+ "-n",
658
+ default=50,
659
+ type=click.IntRange(min=1, max=1000),
660
+ help="Number of log entries to show (default: 50)",
661
+ )
662
+ def logs(version_id, agent_id, limit):
663
+ """
664
+ View console logs for a version.
665
+
666
+ Shows the latest console output from your agent version.
667
+ Useful for debugging and monitoring agent behavior.
668
+
669
+ \b
670
+ Examples:
671
+ $ videosdk agent logs
672
+ $ videosdk agent logs --agent-id abc123 --version-id ver123
673
+ $ videosdk agent logs --limit 100
674
+ """
675
+ print_header("Agent Deployment Logs")
246
676
 
247
677
  agent_id = get_config_option(
248
678
  agent_id,
249
- ['agent', 'id'],
679
+ ["agent", "id"],
250
680
  required=True,
251
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
681
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
252
682
  )
253
-
254
- client = DeploymentClient()
683
+
684
+ deployment_id = get_config_option(
685
+ None,
686
+ ["deploy", "id"],
687
+ required=True,
688
+ fail_message="'deploy.id' not found in videosdk.yaml",
689
+ )
690
+
691
+ print_key_value("Agent ID", agent_id)
692
+ if version_id:
693
+ print_key_value("Version ID", version_id)
694
+ print_key_value("Limit", str(limit))
695
+ print_divider()
696
+
255
697
  try:
256
- asyncio.run(run_with_progress(
257
- client.agent_deployment_update(
258
- deployment_id=deployment_id,
698
+ asyncio.run(
699
+ version_logs_manager(
259
700
  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()
701
+ deployment_id=deployment_id,
702
+ version_id=version_id,
703
+ limit=limit,
704
+ )
705
+ )
276
706
  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]")
707
+ print_error(str(e))
708
+
709
+
710
+ # =============================================================================
711
+ # VERSION SUBGROUP
712
+ # =============================================================================
713
+ @click.group()
714
+ def version_cli():
715
+ """
716
+ Manage agent versions.
717
+
718
+ View, update, and control the lifecycle of your agent versions.
719
+
720
+ \b
721
+ Commands:
722
+ list List all versions for an agent deployment
723
+ update Update version configuration
724
+ activate Activate a version
725
+ deactivate Deactivate a version
726
+ status Get version status
727
+ describe Get detailed version info
728
+ """
729
+ pass
730
+
731
+
732
+ @version_cli.command(name="list")
733
+ @click.option(
734
+ "--agent-id",
735
+ default=None,
736
+ help="Agent ID (reads from videosdk.yaml if not provided)",
737
+ )
738
+ @click.option(
739
+ "--deployment-id",
740
+ default=None,
741
+ help="Deployment ID (reads from videosdk.yaml if not provided)",
742
+ )
743
+ @click.option(
744
+ "--page",
745
+ default=1,
746
+ type=click.IntRange(min=1),
747
+ help="Page number (default: 1)",
748
+ )
749
+ @click.option(
750
+ "--per-page",
751
+ default=10,
752
+ type=click.IntRange(min=1, max=100),
753
+ help="Items per page (default: 10)",
754
+ )
755
+ @click.option(
756
+ "--sort",
757
+ default="-1",
758
+ type=click.Choice(["1", "-1"]),
759
+ help="Sort order: 1 (oldest first) or -1 (newest first, default)",
760
+ )
761
+ def version_list(agent_id, deployment_id, page, per_page, sort):
762
+ """
763
+ List all versions for an agent.
764
+
765
+ Shows a table of all versions with their status, region, and profile.
766
+
767
+ \b
768
+ Examples:
769
+ $ videosdk agent version list
770
+ $ videosdk agent version list --agent-id abc123
771
+ $ videosdk agent version list --page 2 --per-page 20
772
+ $ videosdk agent version list --sort 1 # oldest first
773
+ """
774
+ print_header("Listing Versions")
289
775
  agent_id = get_config_option(
290
776
  agent_id,
291
- ['agent', 'id'],
777
+ ["agent", "id"],
292
778
  required=True,
293
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
779
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
294
780
  )
295
- client = DeploymentClient()
781
+
782
+ deployment_id = get_config_option(
783
+ deployment_id,
784
+ ["deploy", "id"],
785
+ required=True,
786
+ fail_message="--deployment-id not provided and 'deploy.id' not found in videosdk.yaml",
787
+ )
788
+ print_key_value("Agent ID", agent_id)
789
+ print_key_value("Deployment ID", deployment_id)
790
+
296
791
  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]")
792
+ asyncio.run(
793
+ list_versions_manager(agent_id, deployment_id, page, per_page, int(sort))
794
+ )
304
795
  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]")
796
+ print_error(str(e))
797
+
798
+
799
+ @version_cli.command(name="update")
800
+ @click.option("--version-id", "-v", required=True, help="Version ID to update")
801
+ @click.option("--min-replica", type=click.IntRange(min=0), help="New minimum replicas")
802
+ @click.option(
803
+ "--max-replica", type=click.IntRange(min=0, max=50), help="New maximum replicas"
804
+ )
805
+ @click.option("--profile", help="New compute profile: cpu-small, cpu-medium, cpu-large")
806
+ @click.option("--image-pull-secret", help="New image pull secret name")
807
+ @click.option(
808
+ "--agent-id",
809
+ default=None,
810
+ help="Agent ID (reads from videosdk.yaml if not provided)",
811
+ )
812
+ @click.option("--env-secret", help="New environment secret name")
813
+ def version_update(
814
+ version_id,
815
+ min_replica,
816
+ max_replica,
817
+ profile,
818
+ image_pull_secret,
819
+ agent_id,
820
+ env_secret,
821
+ ):
822
+ """
823
+ Update an existing version configuration.
824
+
825
+ Modify replica counts, profile, or secrets for a running version.
826
+
827
+ \b
828
+ Examples:
829
+ $ videosdk agent version update -v ver123 --min-replica 2 --max-replica 10
830
+ $ videosdk agent version update -v ver123 --profile cpu-large
831
+ $ videosdk agent version update -v ver123 --env-secret new-secrets
832
+ """
833
+ print_header("Updating Version")
834
+
313
835
  agent_id = get_config_option(
314
836
  agent_id,
315
- ['agent', 'id'],
837
+ ["agent", "id"],
316
838
  required=True,
317
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
839
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
318
840
  )
841
+
842
+ if min_replica is not None:
843
+ print_key_value("Min Replicas", str(min_replica))
844
+ if max_replica is not None:
845
+ print_key_value("Max Replicas", str(max_replica))
846
+ if profile:
847
+ print_key_value("Profile", profile)
848
+ if env_secret:
849
+ print_key_value("Env Secret", env_secret)
850
+ if image_pull_secret:
851
+ print_key_value("Image Pull Secret", image_pull_secret)
852
+
319
853
  client = DeploymentClient()
320
854
  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]")
855
+ asyncio.run(
856
+ run_with_progress(
857
+ client.update_version(
858
+ version_id=version_id,
859
+ image_cr=None,
860
+ image_url=None,
861
+ env_secret=env_secret,
862
+ image_pull_secret=image_pull_secret,
863
+ min_replica=min_replica,
864
+ max_replica=max_replica,
865
+ profile=profile,
866
+ ),
867
+ console=console,
868
+ title="Updating Version",
869
+ duration=5.0,
870
+ )
871
+ )
872
+ print_success("Version updated successfully")
873
+ except KeyboardInterrupt:
874
+ print_warning("Version update cancelled by user")
875
+ raise click.Abort()
328
876
  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]")
877
+ print_error(f"Version update failed: {e}")
878
+
879
+
880
+ @version_cli.command(name="activate")
881
+ @click.option("--version-id", "-v", required=True, help="Version ID to activate")
882
+ @click.option(
883
+ "--agent-id",
884
+ default=None,
885
+ help="Agent ID (reads from videosdk.yaml if not provided)",
886
+ )
887
+ def version_activate(version_id, agent_id):
888
+ """
889
+ Activate a version.
890
+
891
+ Enables a previously deactivated version to start receiving traffic.
892
+
893
+ \b
894
+ Example:
895
+ $ videosdk agent version activate -v ver123
896
+ """
897
+ print_header("Activating Version")
898
+
337
899
  agent_id = get_config_option(
338
900
  agent_id,
339
- ['agent', 'id'],
901
+ ["agent", "id"],
340
902
  required=True,
341
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
903
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
342
904
  )
905
+
343
906
  client = DeploymentClient()
344
907
  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]")
908
+ asyncio.run(
909
+ run_with_progress(
910
+ client.activate_version(version_id=version_id),
911
+ console=console,
912
+ title="Activating Version",
913
+ duration=2.0,
914
+ )
915
+ )
916
+ print_success("Version activated successfully")
352
917
  except Exception as e:
353
- console.print(f"[bold red]Failed to start deployment:[/bold red] {e}")
918
+ print_error(f"Failed to activate version: {e}")
919
+
920
+
921
+ @version_cli.command(name="deactivate")
922
+ @click.option("--version-id", "-v", required=True, help="Version ID to deactivate")
923
+ @click.option(
924
+ "--force",
925
+ is_flag=True,
926
+ default=False,
927
+ help="Force deactivate even with active sessions",
928
+ )
929
+ @click.option(
930
+ "--agent-id",
931
+ default=None,
932
+ help="Agent ID (reads from videosdk.yaml if not provided)",
933
+ )
934
+ def version_deactivate(version_id, force, agent_id):
935
+ """
936
+ Deactivate a version.
354
937
 
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]")
938
+ Stops a version from receiving new traffic. Use --force to
939
+ deactivate even if there are active sessions.
940
+
941
+ \b
942
+ Examples:
943
+ $ videosdk agent version deactivate -v ver123
944
+ $ videosdk agent version deactivate -v ver123 --force
945
+ """
946
+ print_header("Deactivating Version")
947
+
948
+ agent_id = get_config_option(
949
+ agent_id,
950
+ ["agent", "id"],
951
+ required=True,
952
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
953
+ )
359
954
 
360
955
  client = DeploymentClient()
361
956
  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]")
957
+ response = asyncio.run(
958
+ run_with_progress(
959
+ client.deactivate_version(version_id=version_id, force=force),
960
+ console=console,
961
+ title="Deactivating Version",
962
+ duration=2.0,
963
+ )
964
+ )
965
+ print_success(response["message"])
369
966
  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(
967
+ print_error(f"Failed to deactivate version: {e}")
968
+
969
+
970
+ @version_cli.command(name="status")
971
+ @click.option("--version-id", "-v", required=True, help="Version ID to check")
972
+ @click.option(
973
+ "--agent-id",
974
+ default=None,
975
+ help="Agent ID (reads from videosdk.yaml if not provided)",
976
+ )
977
+ def version_status(version_id, agent_id):
978
+ """
979
+ Get the current status of a version.
980
+
981
+ Shows whether the version is active, replica counts, and health status.
982
+
983
+ \b
984
+ Example:
985
+ $ videosdk agent version status -v ver123
986
+ """
987
+ print_header("Getting Version Status")
988
+
989
+ agent_id = get_config_option(
381
990
  agent_id,
382
- ['agent', 'id'],
991
+ ["agent", "id"],
383
992
  required=True,
384
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
993
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
385
994
  )
995
+
386
996
  try:
387
- asyncio.run(
388
- list_agents_manager(agent_id,head,tail)
389
- )
390
- console.print(f"[bold green]Agents listed successfully.[/bold green]")
997
+ asyncio.run(describe_version_manager(agent_id, version_id))
998
+ print_info("Next step: Start a session")
999
+ console.print(f" [bold]videosdk agent session start -v {version_id}[/bold]")
391
1000
  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(
1001
+ print_error(str(e))
1002
+
1003
+
1004
+ @version_cli.command(name="describe")
1005
+ @click.option("--version-id", "-v", required=True, help="Version ID to describe")
1006
+ @click.option(
1007
+ "--agent-id",
1008
+ default=None,
1009
+ help="Agent ID (reads from videosdk.yaml if not provided)",
1010
+ )
1011
+ def version_describe(version_id, agent_id):
1012
+ """
1013
+ Get detailed information about a version.
1014
+
1015
+ Shows complete version configuration including image, replicas,
1016
+ profile, secrets, and creation time.
1017
+
1018
+ \b
1019
+ Example:
1020
+ $ videosdk agent version describe -v ver123
1021
+ """
1022
+ print_header("Describing Version")
1023
+
1024
+ agent_id = get_config_option(
401
1025
  agent_id,
402
- ['agent', 'id'],
1026
+ ["agent", "id"],
403
1027
  required=True,
404
- fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml"
1028
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
405
1029
  )
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"
1030
+
1031
+ try:
1032
+ asyncio.run(describe_version_manager(agent_id, version_id))
1033
+ except Exception as e:
1034
+ print_error(str(e))
1035
+
1036
+
1037
+ # Add version subgroup to agent
1038
+ agent_cli.add_command(version_cli, name="version")
1039
+
1040
+
1041
+ # =============================================================================
1042
+ # SESSIONS SUBGROUP
1043
+ # =============================================================================
1044
+ @click.group()
1045
+ def sessions_cli():
1046
+ """
1047
+ Manage agent sessions.
1048
+
1049
+ View and filter sessions for your agents.
1050
+
1051
+ \b
1052
+ Commands:
1053
+ list List all sessions for an agent
1054
+ """
1055
+ pass
1056
+
1057
+
1058
+ @sessions_cli.command(name="list")
1059
+ @click.option(
1060
+ "--agent-id",
1061
+ default=None,
1062
+ help="Agent ID (reads from videosdk.yaml if not provided)",
1063
+ )
1064
+ @click.option("--version-id", "-v", help="Filter by Version ID")
1065
+ @click.option("--room-id", help="Filter by Room ID")
1066
+ @click.option("--session-id", help="Filter by Session ID")
1067
+ @click.option(
1068
+ "--page",
1069
+ default=1,
1070
+ type=click.IntRange(min=1),
1071
+ help="Page number (default: 1)",
1072
+ )
1073
+ @click.option(
1074
+ "--per-page",
1075
+ default=10,
1076
+ type=click.IntRange(min=1, max=100),
1077
+ help="Items per page (default: 10)",
1078
+ )
1079
+ @click.option(
1080
+ "--sort",
1081
+ default=-1,
1082
+ type=click.Choice(["1", "-1"]),
1083
+ help="Sort order: 1 (oldest first) or -1 (newest first, default)",
1084
+ )
1085
+ def sessions_list(agent_id, version_id, room_id, session_id, page, per_page, sort):
1086
+ """
1087
+ List all sessions for an agent.
1088
+
1089
+ Shows a table of sessions with their status, room, and version info.
1090
+ Supports filtering by version, room, and session IDs.
1091
+
1092
+ \b
1093
+ Examples:
1094
+ $ videosdk agent sessions list
1095
+ $ videosdk agent sessions list --agent-id abc123
1096
+ $ videosdk agent sessions list --version-id ver123
1097
+ $ videosdk agent sessions list --page 2 --per-page 20
1098
+ $ videosdk agent sessions list --sort 1 # oldest first
1099
+ """
1100
+ print_header("Listing Sessions")
1101
+
1102
+ agent_id = get_config_option(
1103
+ agent_id,
1104
+ ["agent", "id"],
1105
+ required=True,
1106
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
1107
+ )
1108
+ deployment_id = get_config_option(
1109
+ None,
1110
+ ["deploy", "id"],
1111
+ required=True,
1112
+ fail_message="'deploy.id' not found in videosdk.yaml",
411
1113
  )
1114
+ print_key_value("Agent ID", agent_id)
1115
+ print_key_value("Deployment ID", deployment_id)
412
1116
  try:
413
1117
  asyncio.run(
414
- describe_agent_manager(agent_id,deployment_id)
1118
+ list_sessions_manager(
1119
+ agent_id=agent_id,
1120
+ deployment_id=deployment_id,
1121
+ version_id=version_id,
1122
+ room_id=room_id,
1123
+ session_id=session_id,
1124
+ page=page,
1125
+ per_page=per_page,
1126
+ sort=int(sort),
1127
+ )
415
1128
  )
416
1129
  except Exception as e:
417
- console.print(f"[bold red]Error:[/bold red] {e}")
1130
+ print_error(str(e))
1131
+
1132
+
1133
+ # Add sessions subgroup to agent
1134
+ agent_cli.add_command(sessions_cli, name="sessions")
1135
+
1136
+
1137
+ # =============================================================================
1138
+ # SECRETS SUBGROUP
1139
+ # =============================================================================
1140
+ @click.group(invoke_without_command=True)
1141
+ # @click.argument("name", required=False, default=None)
1142
+ @click.pass_context
1143
+ # def secrets_cli(ctx, name):
1144
+ def secrets_cli(ctx):
1145
+ """
1146
+ Manage agent secrets.
1147
+
1148
+ Create, update, and manage secret sets for your agents.
1149
+ Secrets are securely stored and injected as environment variables.
418
1150
 
1151
+ \b
1152
+ Usage:
1153
+ videosdk agent secrets list List all secrets
1154
+ videosdk agent secrets create <name> [--file .env] Create a secret
1155
+ videosdk agent secrets add <name> Add keys to secret
1156
+ videosdk agent secrets remove <name> Remove keys from secret
1157
+ videosdk agent secrets describe <name> Show secret details
1158
+ videosdk agent secrets delete <name> Delete a secret
1159
+ """
1160
+ ctx.ensure_object(dict)
419
1161
 
1162
+ # # Handle "videosdk agent secrets list" - when "list" is passed as name
1163
+ # if name == "list" and ctx.invoked_subcommand is None:
1164
+ # try:
1165
+ # print_header("Listing Secrets")
1166
+ # asyncio.run(list_secret_manager())
1167
+ # except Exception as e:
1168
+ # print_error(str(e))
1169
+ # ctx.exit(0)
1170
+
1171
+ # # If name is provided and subcommand is "list", show error
1172
+ # if name and ctx.invoked_subcommand == "list":
1173
+ # print_error(
1174
+ # "The 'list' command does not take a secret name. Use 'videosdk agent secrets list'"
1175
+ # )
1176
+ # ctx.exit(1)
1177
+
1178
+ # # If name is provided but no subcommand, show error
1179
+ # if name and ctx.invoked_subcommand is None:
1180
+ # print_error(
1181
+ # f"Missing subcommand for secret '{name}'. "
1182
+ # "Use one of: create, add, remove, describe, delete"
1183
+ # )
1184
+ # console.print()
1185
+ # console.print("Examples:")
1186
+ # console.print(f" videosdk agent secrets create {name} ")
1187
+ # console.print(f" videosdk agent secrets describe {name} ")
1188
+ # console.print(f" videosdk agent secrets delete {name} ")
1189
+ # ctx.exit(1)
1190
+
1191
+ # ctx.obj["secret_name"] = name
1192
+
1193
+
1194
+ @secrets_cli.command(name="list")
1195
+ @click.pass_context
1196
+ def secrets_list(ctx):
1197
+ """
1198
+ List all secret sets.
1199
+
1200
+ Shows a table of all secrets with their names, IDs, and types.
1201
+
1202
+ \b
1203
+ Example:
1204
+ $ videosdk agent secrets list
1205
+ """
1206
+ # Check if a name was provided (should not be)
1207
+ if ctx.obj.get("secret_name"):
1208
+ print_error(
1209
+ "The 'list' command does not take a secret name. Use 'videosdk agent secrets list'"
1210
+ )
1211
+ ctx.exit(1)
1212
+
1213
+ try:
1214
+ print_header("Listing Secrets")
1215
+ asyncio.run(list_secret_manager())
1216
+ except Exception as e:
1217
+ print_error(str(e))
1218
+
1219
+
1220
+ @secrets_cli.command(name="create")
1221
+ @click.argument("name")
1222
+ @click.option(
1223
+ "--file",
1224
+ "-f",
1225
+ "file_path",
1226
+ default=None,
1227
+ help="Path to .env file with key=value pairs",
1228
+ )
1229
+ @click.option("--region", default=None, help="Region for storing secrets")
1230
+ @click.pass_context
1231
+ def secrets_create(ctx, name, file_path, region):
1232
+ """
1233
+ Create a new secret set.
1234
+
1235
+ Creates a new secret set with the given name. Provide secrets via
1236
+ a .env file or enter them interactively.
1237
+
1238
+ \b
1239
+ Examples:
1240
+ $ videosdk agent secrets create my-secrets --file .env
1241
+ $ videosdk agent secrets create my-secrets # interactive mode
1242
+ """
1243
+ # name = ctx.obj.get("secret_name")
1244
+ if not name:
1245
+ print_error("Secret name is required")
1246
+ return
1247
+ try:
1248
+ print_header("Creating Secret")
1249
+ response = asyncio.run(secret_set_manager(name, file_path))
1250
+ print_success(f"Secret '{name}' created successfully")
1251
+
1252
+ # Extract secret ID from response
1253
+ secret_id = None
1254
+ if response:
1255
+ # Try different possible response formats
1256
+ secret_id = (
1257
+ response.get("secretId")
1258
+ or response.get("id")
1259
+ or response.get("data", {}).get("secretId")
1260
+ or response.get("data", {}).get("id")
1261
+ )
1262
+
1263
+ if secret_id:
1264
+ # Save secret ID to videosdk.yaml
1265
+ from videosdk_cli.utils.videosdk_yaml_helper import (
1266
+ update_agent_config,
1267
+ SecretsConfig,
1268
+ )
1269
+
1270
+ secrets_config = SecretsConfig(env=secret_id)
1271
+ update_agent_config(
1272
+ app_dir=Path(os.getcwd()),
1273
+ secrets_config=secrets_config,
1274
+ )
1275
+ print_success(f"Secret ID saved to videosdk.yaml: {secret_id}")
1276
+ else:
1277
+ print_warning(
1278
+ "Could not extract secret ID from response. Secret created but not saved to yaml."
1279
+ )
1280
+ except click.Abort:
1281
+ print_warning("Cancelled. No secrets were saved.")
1282
+ except Exception as e:
1283
+ print_error(str(e))
1284
+
1285
+
1286
+ @secrets_cli.command(name="add")
1287
+ @click.argument("name")
1288
+ @click.pass_context
1289
+ def secrets_add(ctx, name):
1290
+ """
1291
+ Add keys to an existing secret set.
1292
+
1293
+ Interactively add new key-value pairs to an existing secret.
1294
+
1295
+ \b
1296
+ Example:
1297
+ $ videosdk agent secrets add my-secrets
1298
+ """
1299
+ # name = ctx.obj.get("secret_name")
1300
+ if not name:
1301
+ print_error("Secret name is required")
1302
+ return
1303
+ try:
1304
+ print_header("Adding to Secret")
1305
+ asyncio.run(add_secret_set(name))
1306
+ print_success(f"Keys added to secret '{name}' successfully")
1307
+ except Exception as e:
1308
+ print_error(str(e))
1309
+
1310
+
1311
+ @secrets_cli.command(name="remove")
1312
+ @click.argument("name")
1313
+ @click.pass_context
1314
+ def secrets_remove(ctx, name):
1315
+ """
1316
+ Remove keys from a secret set.
1317
+
1318
+ Interactively select and remove specific keys from a secret.
1319
+
1320
+ \b
1321
+ Example:
1322
+ $ videosdk agent secrets remove my-secrets
1323
+ """
1324
+ # name = ctx.obj.get("secret_name")
1325
+ if not name:
1326
+ print_error("Secret name is required")
1327
+ return
1328
+ try:
1329
+ print_header("Removing Keys from Secret")
1330
+ asyncio.run(remove_secret_set_key(name))
1331
+ except Exception as e:
1332
+ print_error(str(e))
1333
+
1334
+
1335
+ @secrets_cli.command(name="describe")
1336
+ @click.argument("name")
1337
+ @click.pass_context
1338
+ def secrets_describe(ctx, name):
1339
+ """
1340
+ Show details of a secret set.
1341
+
1342
+ Displays the secret name, ID, type, and list of keys (values hidden).
1343
+
1344
+ \b
1345
+ Example:
1346
+ $ videosdk agent secrets describe my-secrets
1347
+ """
1348
+ # name = ctx.obj.get("secret_name")
1349
+ if not name:
1350
+ print_error("Secret name is required")
1351
+ return
1352
+ try:
1353
+ print_header("Describing Secret")
1354
+ asyncio.run(describe_secret_set(name))
1355
+ except Exception as e:
1356
+ print_error(str(e))
1357
+
1358
+
1359
+ @secrets_cli.command(name="delete")
1360
+ @click.argument("name")
1361
+ @click.pass_context
1362
+ def secrets_delete(ctx, name):
1363
+ """
1364
+ Delete a secret set.
1365
+
1366
+ Permanently deletes the secret set and all its keys.
1367
+ This action cannot be undone.
1368
+
1369
+ \b
1370
+ Example:
1371
+ $ videosdk agent secrets delete my-secrets
1372
+ """
1373
+ # name = ctx.obj.get("secret_name")
1374
+ if not name:
1375
+ print_error("Secret name is required")
1376
+ return
1377
+ try:
1378
+ print_header("Deleting Secret")
1379
+ asyncio.run(remove_secret_set(name))
1380
+ print_success(f"Secret '{name}' deleted successfully")
1381
+ except Exception as e:
1382
+ print_error(str(e))
1383
+
1384
+
1385
+ # Add secrets subgroup to agent
1386
+ agent_cli.add_command(secrets_cli, name="secrets")
1387
+
1388
+
1389
+ # =============================================================================
1390
+ # IMAGE PULL SECRET
1391
+ # =============================================================================
420
1392
  @agent_cli.command(name="image-pull-secret")
421
- @click.option("--name",required=True, help='Secret set name')
422
- def image_pull_secret(name):
1393
+ @click.argument("name")
1394
+ @click.option("--region", default=None, help="Region for storing the secret")
1395
+ def image_pull_secret(name, region):
1396
+ """
1397
+ Create an image pull secret for private container registries.
1398
+
1399
+ Stores registry credentials securely for pulling private Docker images.
1400
+ You will be prompted for server, username, and password.
1401
+
1402
+ \b
1403
+ Examples:
1404
+ $ videosdk agent image-pull-secret my-registry-creds
1405
+ $ videosdk agent image-pull-secret ghcr-secret --region us-east
1406
+ """
1407
+ try:
1408
+ print_header("Creating Image Pull Secret")
1409
+ asyncio.run(image_pull_secret_manager(name))
1410
+ print_success(f"Image pull secret '{name}' created successfully")
1411
+ except Exception as e:
1412
+ print_error(str(e))
1413
+
1414
+
1415
+ # =============================================================================
1416
+ # SESSION SUBGROUP
1417
+ # =============================================================================
1418
+ @click.group()
1419
+ def session_cli():
1420
+ """
1421
+ Manage agent sessions.
1422
+
1423
+ Start and stop individual agent sessions in rooms.
1424
+
1425
+ \b
1426
+ Commands:
1427
+ start Start an agent in a room
1428
+ stop Stop an agent session
1429
+ """
1430
+ pass
1431
+
1432
+
1433
+ @session_cli.command(name="start")
1434
+ @click.option("--version-id", "-v", default=None, help="Version ID to use")
1435
+ @click.option(
1436
+ "--room-id",
1437
+ "-r",
1438
+ default=None,
1439
+ help="Room ID to join (creates new room if not provided)",
1440
+ )
1441
+ @click.option(
1442
+ "--agent-id",
1443
+ "-a",
1444
+ default=None,
1445
+ help="Agent ID (reads from videosdk.yaml if not provided)",
1446
+ )
1447
+ def session_start(version_id, room_id, agent_id):
1448
+ """
1449
+ Start an agent session in a room.
1450
+
1451
+ Dispatches an agent to join the specified room. If no room-id is
1452
+ provided, a new room will be created automatically.
1453
+
1454
+ \b
1455
+ Examples:
1456
+ $ videosdk agent session start -r room-abc # uses latest version of agent if not provided
1457
+ $ videosdk agent session start -v ver123 # creates new room
1458
+ $ videosdk agent session start -v ver123 -r room-abc
1459
+ $ videosdk agent session start # uses latest version
1460
+ """
1461
+ print_header("Starting Session")
1462
+
1463
+ agent_id = get_config_option(
1464
+ agent_id,
1465
+ ["agent", "id"],
1466
+ required=True,
1467
+ fail_message="--agent-id not provided and 'agent.id' not found in videosdk.yaml",
1468
+ )
1469
+
1470
+ client = DeploymentClient()
1471
+ try:
1472
+ result = asyncio.run(
1473
+ run_with_progress(
1474
+ client.agent_start(
1475
+ version_id=version_id, agent_id=agent_id, meeting_id=room_id
1476
+ ),
1477
+ console=console,
1478
+ title="Starting Session",
1479
+ duration=30.0,
1480
+ )
1481
+ )
1482
+ print_success("Session started successfully")
1483
+
1484
+ # Extract room_id from result
1485
+ started_room_id = result.get("roomId") if result else room_id
1486
+ if started_room_id:
1487
+ print_key_value("Room ID", started_room_id)
1488
+ print_info("Useful commands:")
1489
+ console.print(f" [bold]View logs:[/bold] videosdk agent version logs")
1490
+ console.print(
1491
+ f" [bold]Stop session:[/bold] videosdk agent session stop -r {started_room_id}"
1492
+ )
1493
+ except Exception as e:
1494
+ print_error(f"Failed to start session: {e}")
1495
+
1496
+
1497
+ @session_cli.command(name="stop")
1498
+ @click.option("--room-id", "-r", default=None, help="Room ID of the session")
1499
+ @click.option("--session-id", "-s", default=None, help="Session ID to stop")
1500
+ def session_stop(room_id, session_id):
1501
+ """
1502
+ Stop an agent session.
1503
+ \b
1504
+ Examples:
1505
+ $ videosdk agent session stop -r room-abc
1506
+ $ videosdk agent session stop -s session-123
1507
+ """
1508
+ print_header("Stopping Session")
1509
+
1510
+ if not room_id and not session_id:
1511
+ print_error("Either --room-id or --session-id is required")
1512
+ sys.exit(1)
1513
+
1514
+ client = DeploymentClient()
423
1515
  try:
424
1516
  asyncio.run(
425
- image_pull_secret_manager(name)
1517
+ run_with_progress(
1518
+ client.agent_stop(meeting_id=room_id, session_id=session_id),
1519
+ console=console,
1520
+ title="Ending Session",
1521
+ duration=2.0,
1522
+ )
426
1523
  )
1524
+ print_success("Session ended successfully")
427
1525
  except Exception as e:
428
- console.print(f"[bold red]Error:[/bold red] {e}")
1526
+ print_error(f"Failed to stop session: {e}")
1527
+
429
1528
 
430
- agent_cli.add_command(secret_set)
1529
+ # Add session subgroup to agent
1530
+ agent_cli.add_command(session_cli, name="session")