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/auth.py +95 -30
- videosdk_cli/build.py +1387 -287
- videosdk_cli/main.py +69 -36
- videosdk_cli/projects.py +36 -19
- videosdk_cli/templates.py +87 -32
- videosdk_cli/utils/analytics.py +4 -3
- videosdk_cli/utils/api_client.py +26 -24
- videosdk_cli/utils/apis/deployment_client.py +247 -86
- videosdk_cli/utils/apis/videosdk_auth_api_client.py +5 -6
- videosdk_cli/utils/auth_api_client.py +2 -2
- videosdk_cli/utils/manager/agent_manager.py +275 -107
- videosdk_cli/utils/template_helper.py +102 -28
- videosdk_cli/utils/ui/progress_runner.py +15 -7
- videosdk_cli/utils/ui/theme.py +144 -0
- videosdk_cli/utils/videosdk_yaml_helper.py +173 -23
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/METADATA +2 -2
- videosdkagent_cli-0.0.5.dist-info/RECORD +28 -0
- videosdk_cli/secret_set.py +0 -82
- videosdkagent_cli-0.0.1.dist-info/RECORD +0 -28
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/WHEEL +0 -0
- {videosdkagent_cli-0.0.1.dist-info → videosdkagent_cli-0.0.5.dist-info}/entry_points.txt +0 -0
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
120
|
+
console.print(f"\n[{PRIMARY}]Command interrupted by user[/{PRIMARY}]")
|
|
88
121
|
except Exception as e:
|
|
89
|
-
|
|
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
|
|
5
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
128
|
+
console.print(
|
|
129
|
+
"[yellow]Auth token cleared. Please run 'videosdk auth' again.[/yellow]"
|
|
130
|
+
)
|
|
116
131
|
else:
|
|
117
|
-
console.print(
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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",
|
|
74
|
+
"--template",
|
|
75
|
+
"-t",
|
|
62
76
|
required=True,
|
|
63
|
-
help="Template
|
|
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
|
-
"""
|
|
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(
|
|
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(
|
|
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(
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
135
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
221
|
+
console.print(
|
|
222
|
+
f"[green]✅ Project '[bold cyan]{app_name}[/bold cyan]' created successfully![/green]"
|
|
223
|
+
)
|
videosdk_cli/utils/analytics.py
CHANGED
|
@@ -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
|
|
51
|
+
from videosdk_cli.utils.template_helper import API_SERVER_URL
|
|
51
52
|
|
|
52
|
-
url = f"{
|
|
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:
|
videosdk_cli/utils/api_client.py
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import aiohttp
|
|
2
2
|
import logging
|
|
3
|
-
from videosdk_cli.utils.config_manager import
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|