moss-agent-cli 0.1.0__tar.gz
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.
- moss_agent_cli-0.1.0/PKG-INFO +164 -0
- moss_agent_cli-0.1.0/README.md +151 -0
- moss_agent_cli-0.1.0/moss_agent_cli/__init__.py +3 -0
- moss_agent_cli-0.1.0/moss_agent_cli/cli.py +21 -0
- moss_agent_cli-0.1.0/moss_agent_cli/commands/__init__.py +1 -0
- moss_agent_cli-0.1.0/moss_agent_cli/commands/deploy.py +108 -0
- moss_agent_cli-0.1.0/moss_agent_cli/core/__init__.py +1 -0
- moss_agent_cli-0.1.0/moss_agent_cli/core/deployer.py +315 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/PKG-INFO +164 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/SOURCES.txt +14 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/dependency_links.txt +1 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/entry_points.txt +2 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/requires.txt +4 -0
- moss_agent_cli-0.1.0/moss_agent_cli.egg-info/top_level.txt +1 -0
- moss_agent_cli-0.1.0/pyproject.toml +28 -0
- moss_agent_cli-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moss-agent-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for deploying Moss voice agents
|
|
5
|
+
Author-email: Moss Team <support@moss.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer>=0.9.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
13
|
+
|
|
14
|
+
# Moss Agent CLI
|
|
15
|
+
|
|
16
|
+
Command-line tool for deploying voice agents to the Moss platform.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install moss-agent-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install from source:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd moss-agent-cli
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Deploy Command
|
|
34
|
+
|
|
35
|
+
Deploy your agent to the Moss platform:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
moss-agent deploy
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Required Environment Variables
|
|
42
|
+
|
|
43
|
+
Set these environment variables or pass as CLI options:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export MOSS_PROJECT_ID="your-project-id"
|
|
47
|
+
export MOSS_PROJECT_KEY="your-project-key"
|
|
48
|
+
export MOSS_VOICE_AGENT_ID="your-voice-agent-id"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or pass as options:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
moss-agent deploy \
|
|
55
|
+
--project-id "your-project-id" \
|
|
56
|
+
--project-key "your-project-key" \
|
|
57
|
+
--voice-agent-id "your-voice-agent-id"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Agent Structure
|
|
61
|
+
|
|
62
|
+
Your agent directory must contain an entry point file that imports from `moss_voice_agent_manager`:
|
|
63
|
+
|
|
64
|
+
**Simple structure:**
|
|
65
|
+
```
|
|
66
|
+
my-agent/
|
|
67
|
+
├── agent.py # Entry point (uses MossAgentSession)
|
|
68
|
+
├── requirements.txt # Optional: Additional dependencies
|
|
69
|
+
└── tools/ # Optional: Custom tools
|
|
70
|
+
└── my_tools.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Or with main.py:**
|
|
74
|
+
```
|
|
75
|
+
my-agent/
|
|
76
|
+
├── main.py # Entry point
|
|
77
|
+
├── requirements.txt
|
|
78
|
+
└── ...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Or with src structure:**
|
|
82
|
+
```
|
|
83
|
+
my-agent/
|
|
84
|
+
├── src/
|
|
85
|
+
│ └── my_agent/
|
|
86
|
+
│ └── main.py # Entry point
|
|
87
|
+
├── requirements.txt
|
|
88
|
+
└── ...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Example agent.py
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from moss_voice_agent_manager import MossAgentSession
|
|
95
|
+
|
|
96
|
+
def get_weather(city: str) -> str:
|
|
97
|
+
"""Get weather for a city."""
|
|
98
|
+
return f"Weather in {city} is sunny"
|
|
99
|
+
|
|
100
|
+
session = MossAgentSession(
|
|
101
|
+
function_tools=[get_weather],
|
|
102
|
+
max_tool_steps=10,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
session.run()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CLI Options
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
moss-agent deploy [OPTIONS] [DIRECTORY]
|
|
113
|
+
|
|
114
|
+
Arguments:
|
|
115
|
+
DIRECTORY Agent directory to deploy (defaults to current directory)
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--project-id, -p TEXT Moss project ID (or set MOSS_PROJECT_ID env var)
|
|
119
|
+
--project-key, -k TEXT Moss project key (or set MOSS_PROJECT_KEY env var)
|
|
120
|
+
--voice-agent-id, -v TEXT Voice agent ID (or set MOSS_VOICE_AGENT_ID env var)
|
|
121
|
+
--api-url TEXT Moss platform API URL (defaults to production)
|
|
122
|
+
--help Show this message and exit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## What Gets Deployed
|
|
126
|
+
|
|
127
|
+
When you run `moss-agent deploy`, the CLI:
|
|
128
|
+
|
|
129
|
+
1. **Validates** your agent structure
|
|
130
|
+
2. **Packages** your agent directory (excluding .env, __pycache__, .git, etc.)
|
|
131
|
+
3. **Uploads** the package to Moss platform
|
|
132
|
+
4. **Deploys** to LiveKit Cloud
|
|
133
|
+
|
|
134
|
+
Your agent code is deployed as-is - no modification or generation.
|
|
135
|
+
|
|
136
|
+
## Excluded Files
|
|
137
|
+
|
|
138
|
+
The following files/directories are automatically excluded from deployment:
|
|
139
|
+
|
|
140
|
+
- `.env` - Environment variables (secrets)
|
|
141
|
+
- `__pycache__/` - Python cache
|
|
142
|
+
- `.git/` - Git repository
|
|
143
|
+
- `*.pyc` - Compiled Python files
|
|
144
|
+
- `.venv/`, `venv/` - Virtual environments
|
|
145
|
+
- `.DS_Store` - macOS metadata
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
Install in development mode:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd moss-agent-cli
|
|
153
|
+
pip install -e .
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Run the CLI:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
moss-agent deploy
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Moss Agent CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for deploying voice agents to the Moss platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install moss-agent-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install from source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd moss-agent-cli
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Deploy Command
|
|
21
|
+
|
|
22
|
+
Deploy your agent to the Moss platform:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
moss-agent deploy
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Required Environment Variables
|
|
29
|
+
|
|
30
|
+
Set these environment variables or pass as CLI options:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export MOSS_PROJECT_ID="your-project-id"
|
|
34
|
+
export MOSS_PROJECT_KEY="your-project-key"
|
|
35
|
+
export MOSS_VOICE_AGENT_ID="your-voice-agent-id"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or pass as options:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
moss-agent deploy \
|
|
42
|
+
--project-id "your-project-id" \
|
|
43
|
+
--project-key "your-project-key" \
|
|
44
|
+
--voice-agent-id "your-voice-agent-id"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Agent Structure
|
|
48
|
+
|
|
49
|
+
Your agent directory must contain an entry point file that imports from `moss_voice_agent_manager`:
|
|
50
|
+
|
|
51
|
+
**Simple structure:**
|
|
52
|
+
```
|
|
53
|
+
my-agent/
|
|
54
|
+
├── agent.py # Entry point (uses MossAgentSession)
|
|
55
|
+
├── requirements.txt # Optional: Additional dependencies
|
|
56
|
+
└── tools/ # Optional: Custom tools
|
|
57
|
+
└── my_tools.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Or with main.py:**
|
|
61
|
+
```
|
|
62
|
+
my-agent/
|
|
63
|
+
├── main.py # Entry point
|
|
64
|
+
├── requirements.txt
|
|
65
|
+
└── ...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Or with src structure:**
|
|
69
|
+
```
|
|
70
|
+
my-agent/
|
|
71
|
+
├── src/
|
|
72
|
+
│ └── my_agent/
|
|
73
|
+
│ └── main.py # Entry point
|
|
74
|
+
├── requirements.txt
|
|
75
|
+
└── ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Example agent.py
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from moss_voice_agent_manager import MossAgentSession
|
|
82
|
+
|
|
83
|
+
def get_weather(city: str) -> str:
|
|
84
|
+
"""Get weather for a city."""
|
|
85
|
+
return f"Weather in {city} is sunny"
|
|
86
|
+
|
|
87
|
+
session = MossAgentSession(
|
|
88
|
+
function_tools=[get_weather],
|
|
89
|
+
max_tool_steps=10,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
session.run()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## CLI Options
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
moss-agent deploy [OPTIONS] [DIRECTORY]
|
|
100
|
+
|
|
101
|
+
Arguments:
|
|
102
|
+
DIRECTORY Agent directory to deploy (defaults to current directory)
|
|
103
|
+
|
|
104
|
+
Options:
|
|
105
|
+
--project-id, -p TEXT Moss project ID (or set MOSS_PROJECT_ID env var)
|
|
106
|
+
--project-key, -k TEXT Moss project key (or set MOSS_PROJECT_KEY env var)
|
|
107
|
+
--voice-agent-id, -v TEXT Voice agent ID (or set MOSS_VOICE_AGENT_ID env var)
|
|
108
|
+
--api-url TEXT Moss platform API URL (defaults to production)
|
|
109
|
+
--help Show this message and exit
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## What Gets Deployed
|
|
113
|
+
|
|
114
|
+
When you run `moss-agent deploy`, the CLI:
|
|
115
|
+
|
|
116
|
+
1. **Validates** your agent structure
|
|
117
|
+
2. **Packages** your agent directory (excluding .env, __pycache__, .git, etc.)
|
|
118
|
+
3. **Uploads** the package to Moss platform
|
|
119
|
+
4. **Deploys** to LiveKit Cloud
|
|
120
|
+
|
|
121
|
+
Your agent code is deployed as-is - no modification or generation.
|
|
122
|
+
|
|
123
|
+
## Excluded Files
|
|
124
|
+
|
|
125
|
+
The following files/directories are automatically excluded from deployment:
|
|
126
|
+
|
|
127
|
+
- `.env` - Environment variables (secrets)
|
|
128
|
+
- `__pycache__/` - Python cache
|
|
129
|
+
- `.git/` - Git repository
|
|
130
|
+
- `*.pyc` - Compiled Python files
|
|
131
|
+
- `.venv/`, `venv/` - Virtual environments
|
|
132
|
+
- `.DS_Store` - macOS metadata
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
Install in development mode:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
cd moss-agent-cli
|
|
140
|
+
pip install -e .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Run the CLI:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
moss-agent deploy
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Main CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from .commands.deploy import deploy_command
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(
|
|
9
|
+
name="moss-agent",
|
|
10
|
+
help="Moss Agent CLI - Deploy voice agents to Moss platform",
|
|
11
|
+
add_completion=False,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
# Register commands
|
|
17
|
+
app.command(name="deploy")(deploy_command)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands."""
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Deploy command implementation."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
|
|
12
|
+
from ..core.deployer import deploy_agent_package
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
# Load .env file from current directory if it exists
|
|
17
|
+
load_dotenv()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def deploy_command(
|
|
21
|
+
directory: Optional[Path] = typer.Argument(
|
|
22
|
+
None,
|
|
23
|
+
help="Agent directory to deploy (defaults to current directory)",
|
|
24
|
+
),
|
|
25
|
+
project_id: Optional[str] = typer.Option(
|
|
26
|
+
None,
|
|
27
|
+
"--project-id",
|
|
28
|
+
"-p",
|
|
29
|
+
help="Moss project ID (or set MOSS_PROJECT_ID env var)",
|
|
30
|
+
),
|
|
31
|
+
project_key: Optional[str] = typer.Option(
|
|
32
|
+
None,
|
|
33
|
+
"--project-key",
|
|
34
|
+
"-k",
|
|
35
|
+
help="Moss project key (or set MOSS_PROJECT_KEY env var)",
|
|
36
|
+
),
|
|
37
|
+
voice_agent_id: Optional[str] = typer.Option(
|
|
38
|
+
None,
|
|
39
|
+
"--voice-agent-id",
|
|
40
|
+
"-v",
|
|
41
|
+
help="Voice agent ID (or set MOSS_VOICE_AGENT_ID env var)",
|
|
42
|
+
),
|
|
43
|
+
api_url: Optional[str] = typer.Option(
|
|
44
|
+
None,
|
|
45
|
+
"--api-url",
|
|
46
|
+
help="Moss platform API URL (defaults to production)",
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Deploy agent to Moss platform."""
|
|
50
|
+
|
|
51
|
+
# Resolve directory
|
|
52
|
+
agent_dir = directory or Path.cwd()
|
|
53
|
+
if not agent_dir.exists():
|
|
54
|
+
console.print(f"[red]Error:[/red] Directory not found: {agent_dir}")
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
# Load credentials from env or arguments
|
|
58
|
+
project_id = project_id or os.getenv("MOSS_PROJECT_ID")
|
|
59
|
+
project_key = project_key or os.getenv("MOSS_PROJECT_KEY")
|
|
60
|
+
voice_agent_id = voice_agent_id or os.getenv("MOSS_VOICE_AGENT_ID")
|
|
61
|
+
api_url = api_url or os.getenv("MOSS_PLATFORM_API_URL", "https://service.usemoss.dev")
|
|
62
|
+
|
|
63
|
+
# Validate credentials
|
|
64
|
+
if not project_id or not project_key or not voice_agent_id:
|
|
65
|
+
console.print("[red]Error:[/red] Missing required credentials")
|
|
66
|
+
console.print("\nProvide via options or environment variables:")
|
|
67
|
+
console.print(" --project-id / MOSS_PROJECT_ID")
|
|
68
|
+
console.print(" --project-key / MOSS_PROJECT_KEY")
|
|
69
|
+
console.print(" --voice-agent-id / MOSS_VOICE_AGENT_ID")
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
|
|
72
|
+
console.print(f"\n[bold]Deploying agent from:[/bold] {agent_dir}")
|
|
73
|
+
console.print(f"[dim]Project ID:[/dim] {project_id}")
|
|
74
|
+
console.print(f"[dim]Voice Agent ID:[/dim] {voice_agent_id}\n")
|
|
75
|
+
|
|
76
|
+
# Deploy with progress indicator
|
|
77
|
+
with Progress(
|
|
78
|
+
SpinnerColumn(),
|
|
79
|
+
TextColumn("[progress.description]{task.description}"),
|
|
80
|
+
console=console,
|
|
81
|
+
) as progress:
|
|
82
|
+
task = progress.add_task("Deploying agent...", total=None)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
result = deploy_agent_package(
|
|
86
|
+
agent_dir=agent_dir,
|
|
87
|
+
project_id=project_id,
|
|
88
|
+
project_key=project_key,
|
|
89
|
+
voice_agent_id=voice_agent_id,
|
|
90
|
+
api_url=api_url,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
progress.update(task, completed=True)
|
|
94
|
+
|
|
95
|
+
if result.get("ok"):
|
|
96
|
+
console.print("[green]✓[/green] Deployment successful!")
|
|
97
|
+
if result.get("deployment_url"):
|
|
98
|
+
console.print(f"\n[bold]Deployment URL:[/bold] {result['deployment_url']}")
|
|
99
|
+
else:
|
|
100
|
+
console.print("[red]✗[/red] Deployment failed")
|
|
101
|
+
if result.get("error"):
|
|
102
|
+
console.print(f"\n[red]Error:[/red] {result['error']}")
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
progress.update(task, completed=True)
|
|
107
|
+
console.print(f"\n[red]Error:[/red] {exc}")
|
|
108
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core CLI logic."""
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Agent deployment logic."""
|
|
2
|
+
|
|
3
|
+
import tarfile
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_agent_structure(agent_dir: Path) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Validate that the agent directory has required files.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
agent_dir: Path to agent directory
|
|
17
|
+
|
|
18
|
+
Raises:
|
|
19
|
+
ValueError: If validation fails
|
|
20
|
+
"""
|
|
21
|
+
# Check for either agent.py or main.py (or src/*/main.py pattern)
|
|
22
|
+
agent_py = agent_dir / "agent.py"
|
|
23
|
+
main_py = agent_dir / "main.py"
|
|
24
|
+
|
|
25
|
+
# Also check for src/*/main.py pattern (like src/moss_voice_agent/main.py)
|
|
26
|
+
src_main_files = list(agent_dir.glob("src/*/main.py"))
|
|
27
|
+
|
|
28
|
+
entry_point = None
|
|
29
|
+
if agent_py.exists():
|
|
30
|
+
entry_point = agent_py
|
|
31
|
+
elif main_py.exists():
|
|
32
|
+
entry_point = main_py
|
|
33
|
+
elif src_main_files:
|
|
34
|
+
entry_point = src_main_files[0]
|
|
35
|
+
|
|
36
|
+
if not entry_point:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"Missing entry point file. Agent directory must contain one of:\n"
|
|
39
|
+
" - agent.py (at root)\n"
|
|
40
|
+
" - main.py (at root)\n"
|
|
41
|
+
" - src/*/main.py (in src subdirectory)"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Check that entry point uses moss_voice_agent_manager
|
|
45
|
+
entry_content = entry_point.read_text()
|
|
46
|
+
if "moss_voice_agent_manager" not in entry_content:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"{entry_point.name} must import from moss_voice_agent_manager"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def package_agent(agent_dir: Path) -> Path:
|
|
53
|
+
"""
|
|
54
|
+
Package agent directory into a tarball.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
agent_dir: Path to agent directory
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Path to created tarball
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If packaging fails
|
|
64
|
+
"""
|
|
65
|
+
# Create temporary tarball
|
|
66
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
67
|
+
tarball_path = temp_dir / f"agent-{agent_dir.name}.tar.gz"
|
|
68
|
+
|
|
69
|
+
# Files to exclude
|
|
70
|
+
exclude_patterns = {
|
|
71
|
+
".env",
|
|
72
|
+
"__pycache__",
|
|
73
|
+
".git",
|
|
74
|
+
"*.pyc",
|
|
75
|
+
".venv",
|
|
76
|
+
"venv",
|
|
77
|
+
".DS_Store",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def should_exclude(file_path: Path) -> bool:
|
|
81
|
+
"""Check if file should be excluded from package."""
|
|
82
|
+
name = file_path.name
|
|
83
|
+
# Check exact matches
|
|
84
|
+
if name in exclude_patterns:
|
|
85
|
+
return True
|
|
86
|
+
# Check pattern matches (*.pyc, etc.)
|
|
87
|
+
for pattern in exclude_patterns:
|
|
88
|
+
if "*" in pattern:
|
|
89
|
+
ext = pattern.replace("*", "")
|
|
90
|
+
if name.endswith(ext):
|
|
91
|
+
return True
|
|
92
|
+
# Check if in excluded directory
|
|
93
|
+
for part in file_path.parts:
|
|
94
|
+
if part in exclude_patterns:
|
|
95
|
+
return True
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
with tarfile.open(tarball_path, "w:gz") as tar:
|
|
100
|
+
for item in agent_dir.rglob("*"):
|
|
101
|
+
if item.is_file() and not should_exclude(item):
|
|
102
|
+
arcname = item.relative_to(agent_dir)
|
|
103
|
+
tar.add(item, arcname=arcname)
|
|
104
|
+
|
|
105
|
+
return tarball_path
|
|
106
|
+
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
raise ValueError(f"Failed to package agent: {exc}") from exc
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def upload_agent_package(
|
|
112
|
+
tarball_path: Path,
|
|
113
|
+
project_id: str,
|
|
114
|
+
project_key: str,
|
|
115
|
+
voice_agent_id: str,
|
|
116
|
+
api_url: str,
|
|
117
|
+
poll_status: bool = True,
|
|
118
|
+
) -> Dict[str, Any]:
|
|
119
|
+
"""
|
|
120
|
+
Upload agent package to Moss platform.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
tarball_path: Path to agent tarball
|
|
124
|
+
project_id: Moss project ID
|
|
125
|
+
project_key: Moss project key
|
|
126
|
+
voice_agent_id: Voice agent ID
|
|
127
|
+
api_url: API base URL
|
|
128
|
+
poll_status: Whether to poll for deployment status
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Deployment response
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
httpx.HTTPError: If upload fails
|
|
135
|
+
"""
|
|
136
|
+
endpoint = f"{api_url}/api/voice-agent/upload"
|
|
137
|
+
print(f"DEBUG: Uploading to {endpoint}")
|
|
138
|
+
|
|
139
|
+
headers = {
|
|
140
|
+
"X-Project-Id": project_id,
|
|
141
|
+
"X-Project-Key": project_key,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
with open(tarball_path, "rb") as f:
|
|
145
|
+
files = {
|
|
146
|
+
"agent_package": ("agent.tar.gz", f, "application/gzip")
|
|
147
|
+
}
|
|
148
|
+
data = {
|
|
149
|
+
"voice_agent_id": voice_agent_id,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
with httpx.Client(timeout=30.0) as client: # Quick upload timeout
|
|
153
|
+
response = client.post(
|
|
154
|
+
endpoint,
|
|
155
|
+
headers=headers,
|
|
156
|
+
files=files,
|
|
157
|
+
data=data,
|
|
158
|
+
)
|
|
159
|
+
response.raise_for_status()
|
|
160
|
+
result = response.json()
|
|
161
|
+
|
|
162
|
+
# If async deployment (202 status), poll for status
|
|
163
|
+
if response.status_code == 202 and poll_status:
|
|
164
|
+
deployment_id = result.get("deployment_id")
|
|
165
|
+
if deployment_id:
|
|
166
|
+
print(f"DEBUG: Polling status for deployment {deployment_id}")
|
|
167
|
+
final_status = poll_deployment_status(
|
|
168
|
+
deployment_id=deployment_id,
|
|
169
|
+
project_id=project_id,
|
|
170
|
+
project_key=project_key,
|
|
171
|
+
api_url=api_url,
|
|
172
|
+
)
|
|
173
|
+
return final_status
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def poll_deployment_status(
|
|
179
|
+
deployment_id: str,
|
|
180
|
+
project_id: str,
|
|
181
|
+
project_key: str,
|
|
182
|
+
api_url: str,
|
|
183
|
+
max_wait: int = 600, # 10 minutes max
|
|
184
|
+
poll_interval: int = 5, # Check every 5 seconds
|
|
185
|
+
) -> Dict[str, Any]:
|
|
186
|
+
"""
|
|
187
|
+
Poll deployment status until completion.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
deployment_id: Deployment ID to check
|
|
191
|
+
project_id: Moss project ID
|
|
192
|
+
project_key: Moss project key
|
|
193
|
+
api_url: API base URL
|
|
194
|
+
max_wait: Maximum seconds to wait
|
|
195
|
+
poll_interval: Seconds between polls
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Final deployment status
|
|
199
|
+
"""
|
|
200
|
+
import time
|
|
201
|
+
|
|
202
|
+
endpoint = f"{api_url}/api/voice-agent/deployment-status/{deployment_id}"
|
|
203
|
+
headers = {
|
|
204
|
+
"X-Project-Id": project_id,
|
|
205
|
+
"X-Project-Key": project_key,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
start_time = time.time()
|
|
209
|
+
|
|
210
|
+
with httpx.Client(timeout=10.0) as client:
|
|
211
|
+
while True:
|
|
212
|
+
elapsed = time.time() - start_time
|
|
213
|
+
if elapsed > max_wait:
|
|
214
|
+
return {
|
|
215
|
+
"ok": False,
|
|
216
|
+
"error": f"Deployment timed out after {max_wait} seconds",
|
|
217
|
+
"status": "timeout",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
response = client.get(endpoint, headers=headers)
|
|
222
|
+
response.raise_for_status()
|
|
223
|
+
status_data = response.json()
|
|
224
|
+
|
|
225
|
+
status_value = status_data.get("status")
|
|
226
|
+
print(f"DEBUG: Status = {status_value}")
|
|
227
|
+
|
|
228
|
+
if status_value == "completed":
|
|
229
|
+
return {
|
|
230
|
+
"ok": True,
|
|
231
|
+
"deployment_url": status_data.get("deployment_url"),
|
|
232
|
+
"status": "completed",
|
|
233
|
+
}
|
|
234
|
+
elif status_value == "failed":
|
|
235
|
+
return {
|
|
236
|
+
"ok": False,
|
|
237
|
+
"error": status_data.get("error", "Deployment failed"),
|
|
238
|
+
"status": "failed",
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Still in progress, wait and retry
|
|
242
|
+
time.sleep(poll_interval)
|
|
243
|
+
|
|
244
|
+
except httpx.HTTPError as exc:
|
|
245
|
+
print(f"DEBUG: Polling error: {exc}")
|
|
246
|
+
time.sleep(poll_interval)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def deploy_agent_package(
|
|
250
|
+
agent_dir: Path,
|
|
251
|
+
project_id: str,
|
|
252
|
+
project_key: str,
|
|
253
|
+
voice_agent_id: str,
|
|
254
|
+
api_url: str,
|
|
255
|
+
) -> Dict[str, Any]:
|
|
256
|
+
"""
|
|
257
|
+
Deploy agent package to Moss platform.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
agent_dir: Path to agent directory
|
|
261
|
+
project_id: Moss project ID
|
|
262
|
+
project_key: Moss project key
|
|
263
|
+
voice_agent_id: Voice agent ID
|
|
264
|
+
api_url: API base URL
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Deployment response
|
|
268
|
+
"""
|
|
269
|
+
print(f"DEBUG: Starting deployment with API URL: {api_url}")
|
|
270
|
+
|
|
271
|
+
# Validate agent structure
|
|
272
|
+
validate_agent_structure(agent_dir)
|
|
273
|
+
print("DEBUG: Validation passed")
|
|
274
|
+
|
|
275
|
+
# Package agent
|
|
276
|
+
tarball_path = package_agent(agent_dir)
|
|
277
|
+
print(f"DEBUG: Packaged to {tarball_path}")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
# Upload to platform
|
|
281
|
+
result = upload_agent_package(
|
|
282
|
+
tarball_path=tarball_path,
|
|
283
|
+
project_id=project_id,
|
|
284
|
+
project_key=project_key,
|
|
285
|
+
voice_agent_id=voice_agent_id,
|
|
286
|
+
api_url=api_url,
|
|
287
|
+
)
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
except httpx.HTTPError as exc:
|
|
291
|
+
print(f"DEBUG: HTTP Error: {exc}")
|
|
292
|
+
print(f"DEBUG: Exception type: {type(exc)}")
|
|
293
|
+
error_msg = str(exc)
|
|
294
|
+
if hasattr(exc, "response") and exc.response is not None:
|
|
295
|
+
print(f"DEBUG: Response status: {exc.response.status_code}")
|
|
296
|
+
print(f"DEBUG: Response text: {exc.response.text[:500]}")
|
|
297
|
+
try:
|
|
298
|
+
error_detail = exc.response.json().get("detail", error_msg)
|
|
299
|
+
error_msg = error_detail
|
|
300
|
+
except Exception as e:
|
|
301
|
+
print(f"DEBUG: Could not parse JSON: {e}")
|
|
302
|
+
pass
|
|
303
|
+
return {"ok": False, "error": error_msg}
|
|
304
|
+
|
|
305
|
+
except Exception as exc:
|
|
306
|
+
print(f"DEBUG: Unexpected error: {exc}")
|
|
307
|
+
print(f"DEBUG: Exception type: {type(exc)}")
|
|
308
|
+
import traceback
|
|
309
|
+
traceback.print_exc()
|
|
310
|
+
return {"ok": False, "error": str(exc)}
|
|
311
|
+
|
|
312
|
+
finally:
|
|
313
|
+
# Clean up tarball
|
|
314
|
+
if tarball_path.exists():
|
|
315
|
+
tarball_path.unlink()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moss-agent-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for deploying Moss voice agents
|
|
5
|
+
Author-email: Moss Team <support@moss.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer>=0.9.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
13
|
+
|
|
14
|
+
# Moss Agent CLI
|
|
15
|
+
|
|
16
|
+
Command-line tool for deploying voice agents to the Moss platform.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install moss-agent-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install from source:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd moss-agent-cli
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Deploy Command
|
|
34
|
+
|
|
35
|
+
Deploy your agent to the Moss platform:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
moss-agent deploy
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Required Environment Variables
|
|
42
|
+
|
|
43
|
+
Set these environment variables or pass as CLI options:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export MOSS_PROJECT_ID="your-project-id"
|
|
47
|
+
export MOSS_PROJECT_KEY="your-project-key"
|
|
48
|
+
export MOSS_VOICE_AGENT_ID="your-voice-agent-id"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or pass as options:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
moss-agent deploy \
|
|
55
|
+
--project-id "your-project-id" \
|
|
56
|
+
--project-key "your-project-key" \
|
|
57
|
+
--voice-agent-id "your-voice-agent-id"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Agent Structure
|
|
61
|
+
|
|
62
|
+
Your agent directory must contain an entry point file that imports from `moss_voice_agent_manager`:
|
|
63
|
+
|
|
64
|
+
**Simple structure:**
|
|
65
|
+
```
|
|
66
|
+
my-agent/
|
|
67
|
+
├── agent.py # Entry point (uses MossAgentSession)
|
|
68
|
+
├── requirements.txt # Optional: Additional dependencies
|
|
69
|
+
└── tools/ # Optional: Custom tools
|
|
70
|
+
└── my_tools.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Or with main.py:**
|
|
74
|
+
```
|
|
75
|
+
my-agent/
|
|
76
|
+
├── main.py # Entry point
|
|
77
|
+
├── requirements.txt
|
|
78
|
+
└── ...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Or with src structure:**
|
|
82
|
+
```
|
|
83
|
+
my-agent/
|
|
84
|
+
├── src/
|
|
85
|
+
│ └── my_agent/
|
|
86
|
+
│ └── main.py # Entry point
|
|
87
|
+
├── requirements.txt
|
|
88
|
+
└── ...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Example agent.py
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from moss_voice_agent_manager import MossAgentSession
|
|
95
|
+
|
|
96
|
+
def get_weather(city: str) -> str:
|
|
97
|
+
"""Get weather for a city."""
|
|
98
|
+
return f"Weather in {city} is sunny"
|
|
99
|
+
|
|
100
|
+
session = MossAgentSession(
|
|
101
|
+
function_tools=[get_weather],
|
|
102
|
+
max_tool_steps=10,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
session.run()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CLI Options
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
moss-agent deploy [OPTIONS] [DIRECTORY]
|
|
113
|
+
|
|
114
|
+
Arguments:
|
|
115
|
+
DIRECTORY Agent directory to deploy (defaults to current directory)
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--project-id, -p TEXT Moss project ID (or set MOSS_PROJECT_ID env var)
|
|
119
|
+
--project-key, -k TEXT Moss project key (or set MOSS_PROJECT_KEY env var)
|
|
120
|
+
--voice-agent-id, -v TEXT Voice agent ID (or set MOSS_VOICE_AGENT_ID env var)
|
|
121
|
+
--api-url TEXT Moss platform API URL (defaults to production)
|
|
122
|
+
--help Show this message and exit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## What Gets Deployed
|
|
126
|
+
|
|
127
|
+
When you run `moss-agent deploy`, the CLI:
|
|
128
|
+
|
|
129
|
+
1. **Validates** your agent structure
|
|
130
|
+
2. **Packages** your agent directory (excluding .env, __pycache__, .git, etc.)
|
|
131
|
+
3. **Uploads** the package to Moss platform
|
|
132
|
+
4. **Deploys** to LiveKit Cloud
|
|
133
|
+
|
|
134
|
+
Your agent code is deployed as-is - no modification or generation.
|
|
135
|
+
|
|
136
|
+
## Excluded Files
|
|
137
|
+
|
|
138
|
+
The following files/directories are automatically excluded from deployment:
|
|
139
|
+
|
|
140
|
+
- `.env` - Environment variables (secrets)
|
|
141
|
+
- `__pycache__/` - Python cache
|
|
142
|
+
- `.git/` - Git repository
|
|
143
|
+
- `*.pyc` - Compiled Python files
|
|
144
|
+
- `.venv/`, `venv/` - Virtual environments
|
|
145
|
+
- `.DS_Store` - macOS metadata
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
Install in development mode:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd moss-agent-cli
|
|
153
|
+
pip install -e .
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Run the CLI:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
moss-agent deploy
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
moss_agent_cli/__init__.py
|
|
4
|
+
moss_agent_cli/cli.py
|
|
5
|
+
moss_agent_cli.egg-info/PKG-INFO
|
|
6
|
+
moss_agent_cli.egg-info/SOURCES.txt
|
|
7
|
+
moss_agent_cli.egg-info/dependency_links.txt
|
|
8
|
+
moss_agent_cli.egg-info/entry_points.txt
|
|
9
|
+
moss_agent_cli.egg-info/requires.txt
|
|
10
|
+
moss_agent_cli.egg-info/top_level.txt
|
|
11
|
+
moss_agent_cli/commands/__init__.py
|
|
12
|
+
moss_agent_cli/commands/deploy.py
|
|
13
|
+
moss_agent_cli/core/__init__.py
|
|
14
|
+
moss_agent_cli/core/deployer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moss_agent_cli
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "moss-agent-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI tool for deploying Moss voice agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Moss Team", email = "support@moss.dev"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"typer>=0.9.0",
|
|
17
|
+
"rich>=13.0.0",
|
|
18
|
+
"httpx>=0.27.0",
|
|
19
|
+
"python-dotenv>=1.0.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
moss-agent = "moss_agent_cli.cli:app"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["."]
|
|
27
|
+
include = ["moss_agent_cli*"]
|
|
28
|
+
exclude = ["tests*"]
|