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/main.py CHANGED
@@ -3,67 +3,98 @@ import asyncio
3
3
  import nest_asyncio
4
4
  import sys
5
5
  from dotenv import load_dotenv
6
+
6
7
  load_dotenv()
7
8
  from pathlib import Path
8
- from videosdk_cli.auth import handle_auth, handle_logout
9
- from videosdk_cli.projects import list_projects
9
+ from videosdk_cli.auth import auth_cli
10
10
  from videosdk_cli.templates import template_cli
11
11
  from videosdk_cli.run_agent import run_agent
12
12
  from videosdk_cli.utils import analytics
13
13
  from videosdk_cli.build import agent_cli
14
+ from videosdk_cli.utils.ui.theme import (
15
+ console,
16
+ print_banner,
17
+ print_error,
18
+ print_warning,
19
+ PRIMARY,
20
+ )
14
21
  import atexit
15
- from dotenv import load_dotenv
22
+
16
23
  load_dotenv()
17
24
  nest_asyncio.apply()
18
25
 
19
26
  CURRENT_CMD = " ".join([Path(sys.argv[0]).name] + sys.argv[1:])
20
27
 
28
+
21
29
  def run_async(coro):
22
30
  loop = asyncio.get_event_loop()
23
31
  return loop.run_until_complete(coro)
24
32
 
25
- @click.group()
33
+
34
+ class VideoSDKCLI(click.Group):
35
+ """Custom Click Group that shows banner on help."""
36
+
37
+ def format_help(self, ctx, formatter):
38
+ print_banner(mini=False)
39
+ super().format_help(ctx, formatter)
40
+
41
+
42
+ @click.group(cls=VideoSDKCLI)
43
+ @click.version_option(version="0.2.0", prog_name="VideoSDK CLI")
26
44
  def cli():
27
- """VideoSDK CLI tool"""
45
+ """
46
+ Build and deploy AI agents with VideoSDK cloud infrastructure.
47
+
48
+ \b
49
+ GETTING STARTED
50
+ $ videosdk auth login # Authenticate
51
+ $ videosdk agent init --name bot # Create agent
52
+ $ videosdk agent build # Build Docker image
53
+ $ videosdk agent push # Push to registry
54
+ $ videosdk agent deploy # Deploy to cloud
55
+
56
+ \b
57
+ COMMON COMMANDS
58
+ auth Login/logout from VideoSDK
59
+ agent Manage agents, deployments, secrets, sessions
60
+ template Create projects from templates
61
+ run Run agent locally
62
+
63
+ \b
64
+ RESOURCES
65
+ Docs: https://docs.videosdk.live
66
+ Discord: https://discord.gg/HUe45teT
67
+ """
28
68
  pass
29
69
 
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
70
 
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
71
+ # Add auth group (videosdk auth login / videosdk auth logout)
72
+ cli.add_command(auth_cli, name="auth")
59
73
 
74
+ # Add template group (videosdk template list / videosdk template get)
60
75
  cli.add_command(template_cli, name="template")
76
+
77
+ # Add agent group with all subcommands
61
78
  cli.add_command(agent_cli, name="agent")
62
79
 
80
+
63
81
  @cli.command()
64
82
  @click.argument("project_name")
65
83
  def run(project_name):
66
- """Run a VideoSDK project"""
84
+ """
85
+ Run a VideoSDK agent project locally.
86
+
87
+ Starts the agent in console mode for local development and testing.
88
+ Automatically creates a virtual environment and installs requirements.
89
+
90
+ \b
91
+ Arguments:
92
+ PROJECT_NAME Name of the project directory to run
93
+
94
+ \b
95
+ Example:
96
+ $ videosdk run my-assistant
97
+ """
67
98
  try:
68
99
  run_agent(project_name)
69
100
  except KeyboardInterrupt:
@@ -71,6 +102,7 @@ def run(project_name):
71
102
  except Exception as e:
72
103
  raise e
73
104
 
105
+
74
106
  def log_command_at_exit():
75
107
  """Ensures analytics logging happens on exit (normal or KeyboardInterrupt)"""
76
108
  try:
@@ -78,12 +110,13 @@ def log_command_at_exit():
78
110
  except Exception as e:
79
111
  print(f"Failed to log analytics: {e}")
80
112
 
113
+
81
114
  # atexit.register(log_command_at_exit)
82
115
 
83
116
  if __name__ == "__main__":
84
117
  try:
85
118
  cli()
86
119
  except KeyboardInterrupt:
87
- click.echo("\n[bold yellow]Command interrupted by user[/bold yellow]")
120
+ console.print(f"\n[{PRIMARY}]Command interrupted by user[/{PRIMARY}]")
88
121
  except Exception as e:
89
- click.echo(f"[bold red]Error:[/bold red] {e}")
122
+ print_error(str(e))
videosdk_cli/projects.py CHANGED
@@ -1,8 +1,16 @@
1
1
  import asyncio
2
2
  from rich.console import Console
3
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
4
+ from videosdk_cli.utils.config_manager import (
5
+ get_config_value,
6
+ set_config_value,
7
+ save_config,
8
+ )
9
+ from videosdk_cli.utils.api_client import (
10
+ VideoSDKClient,
11
+ VideoSDKTokenClient,
12
+ AuthenticationError,
13
+ )
6
14
  from InquirerPy import inquirer
7
15
 
8
16
  console = Console()
@@ -10,14 +18,8 @@ console = Console()
10
18
 
11
19
  async def list_projects():
12
20
  """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
21
  try:
20
- client = VideoSDKClient(auth_token=auth_token)
22
+ client = VideoSDKClient()
21
23
  data = await client.fetch_projects()
22
24
 
23
25
  projects = data.get("data", [])
@@ -33,13 +35,17 @@ async def list_projects():
33
35
  table.add_column("Status")
34
36
 
35
37
  for i, p in enumerate(projects, start=1):
36
- status = "[green]Active[/green]" if not p.get("isDeactived") else "[red]Inactive[/red]"
38
+ status = (
39
+ "[green]Active[/green]"
40
+ if not p.get("isDeactived")
41
+ else "[red]Inactive[/red]"
42
+ )
37
43
  table.add_row(
38
44
  str(i),
39
45
  p.get("name"),
40
46
  p.get("createdAt"),
41
47
  p.get("keyPair", {}).get("apiKey"),
42
- status
48
+ status,
43
49
  )
44
50
 
45
51
  console.print(table)
@@ -56,7 +62,9 @@ async def list_projects():
56
62
  selected = projects[selected_index]
57
63
 
58
64
  set_config_value("selected_project_name", selected["name"])
59
- set_config_value("selected_project_api_key", selected.get("keyPair", {}).get("apiKey"))
65
+ set_config_value(
66
+ "selected_project_api_key", selected.get("keyPair", {}).get("apiKey")
67
+ )
60
68
  console.print(f"[green]✅ Selected project:[/green] {selected['name']}")
61
69
 
62
70
  await gen_token()
@@ -76,10 +84,14 @@ async def gen_token():
76
84
  auth_token = get_config_value("auth_token")
77
85
 
78
86
  if not api_key or not project_name:
79
- console.print("[red]Project details not found. Please run:[/red] videosdk projects")
87
+ console.print(
88
+ "[red]Project details not found. Please run:[/red] videosdk projects"
89
+ )
80
90
  return
81
91
  if not auth_token:
82
- console.print("[red]Authentication details not found. Please run:[/red] videosdk auth")
92
+ console.print(
93
+ "[red]Authentication details not found. Please run:[/red] videosdk auth"
94
+ )
83
95
  return
84
96
 
85
97
  try:
@@ -93,7 +105,9 @@ async def gen_token():
93
105
  console.print(f"[green]✅ Generated token:[/green]")
94
106
  set_config_value("VIDEOSDK_AUTH_TOKEN", token)
95
107
  else:
96
- console.print("[red]❌ Token not found. Check your API key or auth token.[/red]")
108
+ console.print(
109
+ "[red]❌ Token not found. Check your API key or auth token.[/red]"
110
+ )
97
111
 
98
112
  except AuthenticationError as e:
99
113
  console.print(f"[red]Authentication error: {e}[/red]")
@@ -106,12 +120,15 @@ async def gen_token():
106
120
  async def handle_auth_reset():
107
121
  """Ask user before resetting auth token."""
108
122
  reset = await inquirer.confirm(
109
- message="Do you want to reset your authentication token?",
110
- default=False
123
+ message="Do you want to reset your authentication token?", default=False
111
124
  ).execute_async()
112
125
 
113
126
  if reset:
114
127
  save_config(None)
115
- console.print("[yellow]Auth token cleared. Please run 'videosdk auth' again.[/yellow]")
128
+ console.print(
129
+ "[yellow]Auth token cleared. Please run 'videosdk auth' again.[/yellow]"
130
+ )
116
131
  else:
117
- console.print("[yellow]Auth token not cleared. You may experience errors until re-authenticated.[/yellow]")
132
+ console.print(
133
+ "[yellow]Auth token not cleared. You may experience errors until re-authenticated.[/yellow]"
134
+ )
videosdk_cli/templates.py CHANGED
@@ -11,20 +11,39 @@ from videosdk_cli.utils.videosdk_yaml_helper import AgentConfig
11
11
 
12
12
  console = Console()
13
13
 
14
+
14
15
  @click.group()
15
16
  def template_cli():
16
- """Manage, download, and create projects from official VideoSDK templates."""
17
+ """
18
+ Manage and create projects from VideoSDK templates.
19
+
20
+ Templates provide pre-configured AI agent setups with different
21
+ pipeline types (cascading, real-time) and provider combinations.
22
+
23
+ \b
24
+ Commands:
25
+ list Show all available templates
26
+ get Create a new project from a template
27
+ """
17
28
  pass
18
29
 
19
30
 
20
31
  @template_cli.command(name="list")
21
32
  @click.option(
22
- "--refresh", "-r",
23
- is_flag=True,
24
- help="Force refresh template metadata from server"
33
+ "--refresh", "-r", is_flag=True, help="Force refresh template metadata from server"
25
34
  )
26
35
  def list_templates(refresh: bool):
27
- """List all available templates."""
36
+ """
37
+ List all available templates.
38
+
39
+ Shows a table of all VideoSDK agent templates with their IDs,
40
+ filenames, and descriptions.
41
+
42
+ \b
43
+ Examples:
44
+ $ videosdk template list
45
+ $ videosdk template list --refresh # force refresh from server
46
+ """
28
47
  asyncio.run(list_templates_async(refresh))
29
48
 
30
49
 
@@ -43,28 +62,37 @@ async def list_templates_async(force_refresh: bool = False):
43
62
  table.add_column("Template id", style="cyan", no_wrap=True)
44
63
  table.add_column("File Name", style="green", no_wrap=True)
45
64
  table.add_column("Description", style="white")
46
- # table.add_column("Mode", style="magenta")
47
- # table.add_column("Pipeline Type", style="blue")
48
65
 
49
66
  for name, tpl in templates.items():
50
- table.add_row(
51
- name,
52
- tpl.file_name,
53
- tpl.description
54
- # tpl.mode,
55
- # tpl.pipe_type
56
- )
67
+ table.add_row(name, tpl.file_name, tpl.description)
57
68
 
58
69
  console.print(table)
70
+
71
+
59
72
  @template_cli.command(name="get")
60
73
  @click.option(
61
- "--template", "-t",
74
+ "--template",
75
+ "-t",
62
76
  required=True,
63
- help="Template name to create project from"
77
+ help="Template ID to create project from (see 'template list')",
64
78
  )
65
79
  @click.argument("app_name")
66
80
  def create_project(template: str, app_name: str):
67
- """Create a new project from a template."""
81
+ """
82
+ Create a new project from a template.
83
+
84
+ Downloads the template code and sets up a complete project directory
85
+ with configuration files, requirements, and environment variables.
86
+
87
+ \b
88
+ Arguments:
89
+ APP_NAME Name of the project directory to create
90
+
91
+ \b
92
+ Examples:
93
+ $ videosdk template get -t ai-agent-cascading-pipeline my-assistant
94
+ $ videosdk template get -t ai-agent-real-time-pipeline voice-bot
95
+ """
68
96
  asyncio.run(create_project_async(template, app_name))
69
97
 
70
98
 
@@ -76,12 +104,16 @@ async def create_project_async(template_id: str, app_name: str):
76
104
  tpl = kv.get_template(template_id)
77
105
 
78
106
  if not tpl:
79
- console.print(f"[bold red]Error:[/bold red] Template '[cyan]{template_id}[/cyan]' not found in cache.")
107
+ console.print(
108
+ f"[bold red]Error:[/bold red] Template '[cyan]{template_id}[/cyan]' not found in cache."
109
+ )
80
110
  return
81
111
 
82
112
  app_dir = Path(app_name)
83
113
  if app_dir.exists():
84
- console.print(f"[bold red]Error:[/bold red] Directory '[cyan]{app_name}[/cyan]' already exists.")
114
+ console.print(
115
+ f"[bold red]Error:[/bold red] Directory '[cyan]{app_name}[/cyan]' already exists."
116
+ )
85
117
  return
86
118
 
87
119
  console.print(f"[cyan]→ Mode:[/cyan] {tpl.mode}")
@@ -109,17 +141,27 @@ async def create_project_async(template_id: str, app_name: str):
109
141
  provider_modules.append(module)
110
142
 
111
143
  elif tpl.mode == "custom" and tpl.pipe_type == "cascading":
112
- STT = await template_helper.select_provider_arrow("Select STT provider:", tpl.STT_options)
113
- LLM = await template_helper.select_provider_arrow("Select LLM provider:", tpl.LLM_options)
114
- TTS = await template_helper.select_provider_arrow("Select TTS provider:", tpl.TTS_options)
144
+ STT = await template_helper.select_provider_arrow(
145
+ "Select STT provider:", tpl.STT_options
146
+ )
147
+ LLM = await template_helper.select_provider_arrow(
148
+ "Select LLM provider:", tpl.LLM_options
149
+ )
150
+ TTS = await template_helper.select_provider_arrow(
151
+ "Select TTS provider:", tpl.TTS_options
152
+ )
115
153
  providers_dict = {"STT": STT, "LLM": LLM, "TTS": TTS}
116
- imports, provider_modules = template_helper.generate_cascading_providers(providers_dict)
154
+ imports, provider_modules = template_helper.generate_cascading_providers(
155
+ providers_dict
156
+ )
117
157
 
118
158
  api_keys = await template_helper.prompt_provider_api_keys(providers_dict)
119
159
 
120
160
  env_path = await template_helper.prompt_existing_env()
121
161
  if env_path and not env_path.exists():
122
- console.print("[red]Provided environment path does not exist. A new venv will be created.[/red]")
162
+ console.print(
163
+ "[red]Provided environment path does not exist. A new venv will be created.[/red]"
164
+ )
123
165
  env_path = None
124
166
 
125
167
  with Progress(
@@ -127,23 +169,32 @@ async def create_project_async(template_id: str, app_name: str):
127
169
  TextColumn("[progress.description]{task.description}"),
128
170
  transient=True,
129
171
  ) as progress:
130
- progress.add_task(description=f"Creating project [cyan]{app_name}[/cyan]...", total=None)
172
+ progress.add_task(
173
+ description=f"Creating project [cyan]{app_name}[/cyan]...", total=None
174
+ )
131
175
  try:
132
176
  app_dir.mkdir(parents=True, exist_ok=True)
133
177
  template_helper.write_env_file(app_dir, providers_dict, api_keys)
134
- template_helper.write_requirements(app_dir, template_helper.STATIC_REQUIREMENTS + provider_modules)
135
- videosdk_yaml_helper.update_agent_config(app_dir, AgentConfig(templateId=template_id))
178
+ template_helper.write_requirements(
179
+ app_dir, template_helper.STATIC_REQUIREMENTS + provider_modules
180
+ )
181
+ videosdk_yaml_helper.update_agent_config(
182
+ app_dir, AgentConfig(templateId=template_id)
183
+ )
136
184
  except Exception as e:
137
185
  console.print(f"[bold red]Error creating project files: {e}[/bold red]")
138
186
  return
139
187
 
140
188
  try:
141
189
  import aiohttp
190
+
142
191
  async with aiohttp.ClientSession() as session:
143
192
  url = f"{template_helper.CDN_BASE_URL}/{tpl.file_name}"
144
193
  async with session.get(url) as resp:
145
194
  if resp.status == 404:
146
- console.print(f"[bold red]Template '{template_id}' not found on server[/bold red]")
195
+ console.print(
196
+ f"[bold red]Template '{template_id}' not found on server[/bold red]"
197
+ )
147
198
  return
148
199
  resp.raise_for_status()
149
200
  template_code = await resp.text()
@@ -152,7 +203,9 @@ async def create_project_async(template_id: str, app_name: str):
152
203
  return
153
204
 
154
205
  imports_str = "\n".join(imports) if imports else ""
155
- template_helper.write_template_file(app_dir, tpl.file_name, imports_str, template_code)
206
+ template_helper.write_template_file(
207
+ app_dir, tpl.file_name, imports_str, template_code
208
+ )
156
209
 
157
210
  template_helper.write_config(
158
211
  app_dir=app_dir,
@@ -161,8 +214,10 @@ async def create_project_async(template_id: str, app_name: str):
161
214
  description=tpl.description,
162
215
  providers=providers_dict,
163
216
  env_path=str(env_path) if env_path else None,
164
- auto_configured=(tpl.mode=="preconfig"),
165
- requirements_installed=False
217
+ auto_configured=(tpl.mode == "preconfig"),
218
+ requirements_installed=False,
166
219
  )
167
220
 
168
- console.print(f"[green]✅ Project '[bold cyan]{app_name}[/bold cyan]' created successfully![/green]")
221
+ console.print(
222
+ f"[green]✅ Project '[bold cyan]{app_name}[/bold cyan]' created successfully![/green]"
223
+ )
@@ -10,6 +10,7 @@ ANALYTICS_FILE = CONFIG_DIR / "analytics.json"
10
10
 
11
11
  _pending_analytics = []
12
12
 
13
+
13
14
  async def log_command(command: str):
14
15
  """
15
16
  Log a CLI command for analytics.
@@ -24,7 +25,7 @@ async def log_command(command: str):
24
25
  "user_id": user_id,
25
26
  "username": username,
26
27
  "api_key": api_key,
27
- "command": command
28
+ "command": command,
28
29
  }
29
30
 
30
31
  if ANALYTICS_FILE.exists():
@@ -47,9 +48,9 @@ async def send_analytics_to_server(data):
47
48
  Send analytics to your server endpoint.
48
49
  """
49
50
  import aiohttp
50
- from videosdk_cli.utils.template_helper import CDN_BASE_URL
51
+ from videosdk_cli.utils.template_helper import API_SERVER_URL
51
52
 
52
- url = f"{CDN_BASE_URL}/analytics/collect/"
53
+ url = f"{API_SERVER_URL}/analytics/collect/"
53
54
  try:
54
55
  async with aiohttp.ClientSession() as session:
55
56
  async with session.post(url, json=data) as resp:
@@ -1,30 +1,35 @@
1
1
  import aiohttp
2
2
  import logging
3
- from videosdk_cli.utils.config_manager import load_config, save_config, set_config_value
3
+ from videosdk_cli.utils.config_manager import (
4
+ load_config,
5
+ save_config,
6
+ set_config_value,
7
+ get_config_value,
8
+ )
4
9
  from InquirerPy import inquirer
5
10
 
6
11
  logger = logging.getLogger(__name__)
7
12
  logger.setLevel(logging.INFO)
8
13
 
9
14
 
10
-
11
15
  class AuthenticationError(Exception):
12
16
  """Raised when the stored authentication is invalid or expired."""
13
- pass
14
17
 
18
+ pass
15
19
 
16
20
 
17
21
  class VideoSDKClient:
18
- def __init__(self, auth_token):
19
- self.auth_token = auth_token
20
- self.url = "https://api.videosdk.live/v1/apikeys"
22
+ def __init__(self):
23
+ from videosdk_cli.utils.template_helper import API_BASE_URL
24
+
25
+ self.auth_token = get_config_value("VIDEOSDK_AUTH_TOKEN") or ""
26
+ self.url = API_BASE_URL + "/v1/apikeys"
21
27
  self.headers = {
22
28
  "Authorization": self.auth_token,
23
- "Content-Type": "application/json"
29
+ "Content-Type": "application/json",
24
30
  }
25
31
  self.res_data = None
26
32
 
27
-
28
33
  async def fetch_projects(self):
29
34
  async with aiohttp.ClientSession() as session:
30
35
  async with session.get(self.url, headers=self.headers) as resp:
@@ -41,50 +46,49 @@ class VideoSDKClient:
41
46
  self.res_data = await resp.json()
42
47
  return self.res_data
43
48
 
44
-
45
49
  def _reset_credentials(self):
46
50
  """Clear stored auth_token."""
47
51
  logger.info("Resetting stored credentials...")
48
52
  try:
49
53
  save_config(None)
50
-
54
+
51
55
  except Exception:
52
56
  config = load_config()
53
57
  config = None
54
58
  save_config(config)
55
59
 
56
60
 
57
-
58
61
  class VideoSDKTokenClient:
59
62
  def __init__(self, auth_token, api_key, expiresIn):
60
- self.url = f"https://api.videosdk.live/v1/apikeys/{api_key}/token"
61
- self.headers = {
62
- "Authorization": auth_token,
63
- "Content-Type": "application/json"
64
- }
63
+ from videosdk_cli.utils.template_helper import API_BASE_URL
64
+
65
+ self.url = API_BASE_URL + f"/v1/apikeys/{api_key}/token"
66
+ self.headers = {"Authorization": auth_token, "Content-Type": "application/json"}
65
67
  self.payload = {
66
68
  "apiKey": api_key,
67
69
  "permissions": ["allow_join"],
68
- "expiresIn": expiresIn
70
+ "expiresIn": expiresIn,
69
71
  }
70
72
  self.res_data = None
71
73
 
72
-
73
74
  async def fetch_token(self):
74
75
  async with aiohttp.ClientSession() as session:
75
- async with session.post(self.url, headers=self.headers, json=self.payload) as resp:
76
+ async with session.post(
77
+ self.url, headers=self.headers, json=self.payload
78
+ ) as resp:
76
79
  text = await resp.text()
77
80
  if 400 <= resp.status < 500:
78
81
  logger.warning("Session expired or invalid credentials.")
79
82
  reset = ask_reset_credentials()
80
83
  if reset:
81
84
  self._reset_credentials()
82
- raise AuthenticationError(f"Authentication failed ({resp.status}): {text}")
85
+ raise AuthenticationError(
86
+ f"Authentication failed ({resp.status}): {text}"
87
+ )
83
88
  resp.raise_for_status()
84
89
  self.res_data = await resp.json()
85
90
  return self.res_data
86
91
 
87
-
88
92
  def _reset_credentials(self):
89
93
  """Clear stored auth_token."""
90
94
  logger.info("Resetting stored credentials...")
@@ -96,12 +100,10 @@ class VideoSDKTokenClient:
96
100
  save_config(config)
97
101
 
98
102
 
99
-
100
103
  def ask_reset_credentials():
101
104
  """Prompt user before clearing saved auth token."""
102
105
  result = inquirer.confirm(
103
106
  message="Request failed. Do you want to reset your stored authentication token?",
104
- default=False
107
+ default=False,
105
108
  ).execute()
106
109
  return result
107
-