trinity-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.
- trinity_cli-0.1.0/PKG-INFO +119 -0
- trinity_cli-0.1.0/README.md +92 -0
- trinity_cli-0.1.0/pyproject.toml +47 -0
- trinity_cli-0.1.0/setup.cfg +4 -0
- trinity_cli-0.1.0/trinity_cli/__init__.py +3 -0
- trinity_cli-0.1.0/trinity_cli/client.py +107 -0
- trinity_cli-0.1.0/trinity_cli/commands/__init__.py +0 -0
- trinity_cli-0.1.0/trinity_cli/commands/agents.py +96 -0
- trinity_cli-0.1.0/trinity_cli/commands/auth.py +177 -0
- trinity_cli-0.1.0/trinity_cli/commands/chat.py +64 -0
- trinity_cli-0.1.0/trinity_cli/commands/health.py +31 -0
- trinity_cli-0.1.0/trinity_cli/commands/profiles.py +57 -0
- trinity_cli-0.1.0/trinity_cli/commands/schedules.py +44 -0
- trinity_cli-0.1.0/trinity_cli/commands/skills.py +42 -0
- trinity_cli-0.1.0/trinity_cli/commands/tags.py +31 -0
- trinity_cli-0.1.0/trinity_cli/config.py +193 -0
- trinity_cli-0.1.0/trinity_cli/main.py +79 -0
- trinity_cli-0.1.0/trinity_cli/output.py +62 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/PKG-INFO +119 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/SOURCES.txt +22 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/dependency_links.txt +1 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/entry_points.txt +2 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/requires.txt +3 -0
- trinity_cli-0.1.0/trinity_cli.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trinity-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for the Trinity Autonomous Agent Orchestration Platform
|
|
5
|
+
Author-email: Ability AI <hello@ability.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/abilityai/trinity
|
|
8
|
+
Project-URL: Documentation, https://github.com/abilityai/trinity/blob/main/docs/CLI.md
|
|
9
|
+
Project-URL: Repository, https://github.com/abilityai/trinity
|
|
10
|
+
Project-URL: Issues, https://github.com/abilityai/trinity/issues
|
|
11
|
+
Keywords: trinity,ai,agents,orchestration,cli
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: click>=8.0
|
|
25
|
+
Requires-Dist: httpx>=0.24
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
|
|
28
|
+
# Trinity CLI
|
|
29
|
+
|
|
30
|
+
Command-line interface for the [Trinity](https://github.com/abilityai/trinity) Autonomous Agent Orchestration Platform.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# With pip
|
|
36
|
+
pip install trinity-cli
|
|
37
|
+
|
|
38
|
+
# With pipx (recommended — isolated environment)
|
|
39
|
+
pipx install trinity-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Connect to your Trinity instance
|
|
46
|
+
trinity init
|
|
47
|
+
|
|
48
|
+
# List your agents
|
|
49
|
+
trinity agents list
|
|
50
|
+
|
|
51
|
+
# Chat with an agent
|
|
52
|
+
trinity chat my-agent "Hello, what can you do?"
|
|
53
|
+
|
|
54
|
+
# Check fleet health
|
|
55
|
+
trinity health fleet
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Multi-Instance Profiles
|
|
59
|
+
|
|
60
|
+
Manage multiple Trinity instances (local dev, staging, production):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# First instance (created during init)
|
|
64
|
+
trinity init
|
|
65
|
+
|
|
66
|
+
# Add another instance
|
|
67
|
+
trinity init --profile production
|
|
68
|
+
|
|
69
|
+
# Switch between instances
|
|
70
|
+
trinity profile use production
|
|
71
|
+
trinity profile list
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Commands
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `trinity init` | Connect to a Trinity instance |
|
|
79
|
+
| `trinity login` | Re-authenticate with stored instance |
|
|
80
|
+
| `trinity agents list` | List all agents |
|
|
81
|
+
| `trinity agents create <name>` | Create a new agent |
|
|
82
|
+
| `trinity agents start <name>` | Start an agent |
|
|
83
|
+
| `trinity agents stop <name>` | Stop an agent |
|
|
84
|
+
| `trinity chat <agent> "msg"` | Chat with an agent |
|
|
85
|
+
| `trinity history <agent>` | View chat history |
|
|
86
|
+
| `trinity logs <agent>` | View agent logs |
|
|
87
|
+
| `trinity health fleet` | Fleet health overview |
|
|
88
|
+
| `trinity health agent <name>` | Single agent health |
|
|
89
|
+
| `trinity skills list` | Browse skill library |
|
|
90
|
+
| `trinity schedules list <agent>` | View agent schedules |
|
|
91
|
+
| `trinity profile list` | List configured profiles |
|
|
92
|
+
| `trinity profile use <name>` | Switch active profile |
|
|
93
|
+
|
|
94
|
+
## Output Formats
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# JSON (default)
|
|
98
|
+
trinity agents list
|
|
99
|
+
|
|
100
|
+
# Table
|
|
101
|
+
trinity agents list --format table
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Environment Variables
|
|
105
|
+
|
|
106
|
+
| Variable | Description |
|
|
107
|
+
|----------|-------------|
|
|
108
|
+
| `TRINITY_URL` | Override instance URL |
|
|
109
|
+
| `TRINITY_API_KEY` | Override auth token |
|
|
110
|
+
| `TRINITY_PROFILE` | Override active profile |
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
- [Full CLI docs](https://github.com/abilityai/trinity/blob/main/docs/CLI.md)
|
|
115
|
+
- [Trinity Platform](https://github.com/abilityai/trinity)
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Trinity CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for the [Trinity](https://github.com/abilityai/trinity) Autonomous Agent Orchestration Platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# With pip
|
|
9
|
+
pip install trinity-cli
|
|
10
|
+
|
|
11
|
+
# With pipx (recommended — isolated environment)
|
|
12
|
+
pipx install trinity-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Connect to your Trinity instance
|
|
19
|
+
trinity init
|
|
20
|
+
|
|
21
|
+
# List your agents
|
|
22
|
+
trinity agents list
|
|
23
|
+
|
|
24
|
+
# Chat with an agent
|
|
25
|
+
trinity chat my-agent "Hello, what can you do?"
|
|
26
|
+
|
|
27
|
+
# Check fleet health
|
|
28
|
+
trinity health fleet
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Multi-Instance Profiles
|
|
32
|
+
|
|
33
|
+
Manage multiple Trinity instances (local dev, staging, production):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# First instance (created during init)
|
|
37
|
+
trinity init
|
|
38
|
+
|
|
39
|
+
# Add another instance
|
|
40
|
+
trinity init --profile production
|
|
41
|
+
|
|
42
|
+
# Switch between instances
|
|
43
|
+
trinity profile use production
|
|
44
|
+
trinity profile list
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---------|-------------|
|
|
51
|
+
| `trinity init` | Connect to a Trinity instance |
|
|
52
|
+
| `trinity login` | Re-authenticate with stored instance |
|
|
53
|
+
| `trinity agents list` | List all agents |
|
|
54
|
+
| `trinity agents create <name>` | Create a new agent |
|
|
55
|
+
| `trinity agents start <name>` | Start an agent |
|
|
56
|
+
| `trinity agents stop <name>` | Stop an agent |
|
|
57
|
+
| `trinity chat <agent> "msg"` | Chat with an agent |
|
|
58
|
+
| `trinity history <agent>` | View chat history |
|
|
59
|
+
| `trinity logs <agent>` | View agent logs |
|
|
60
|
+
| `trinity health fleet` | Fleet health overview |
|
|
61
|
+
| `trinity health agent <name>` | Single agent health |
|
|
62
|
+
| `trinity skills list` | Browse skill library |
|
|
63
|
+
| `trinity schedules list <agent>` | View agent schedules |
|
|
64
|
+
| `trinity profile list` | List configured profiles |
|
|
65
|
+
| `trinity profile use <name>` | Switch active profile |
|
|
66
|
+
|
|
67
|
+
## Output Formats
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# JSON (default)
|
|
71
|
+
trinity agents list
|
|
72
|
+
|
|
73
|
+
# Table
|
|
74
|
+
trinity agents list --format table
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Environment Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Description |
|
|
80
|
+
|----------|-------------|
|
|
81
|
+
| `TRINITY_URL` | Override instance URL |
|
|
82
|
+
| `TRINITY_API_KEY` | Override auth token |
|
|
83
|
+
| `TRINITY_PROFILE` | Override active profile |
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
- [Full CLI docs](https://github.com/abilityai/trinity/blob/main/docs/CLI.md)
|
|
88
|
+
- [Trinity Platform](https://github.com/abilityai/trinity)
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trinity-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI for the Trinity Autonomous Agent Orchestration Platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Ability AI", email = "hello@ability.ai"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["trinity", "ai", "agents", "orchestration", "cli"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries",
|
|
26
|
+
"Topic :: System :: Systems Administration",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"click>=8.0",
|
|
30
|
+
"httpx>=0.24",
|
|
31
|
+
"rich>=13.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/abilityai/trinity"
|
|
36
|
+
Documentation = "https://github.com/abilityai/trinity/blob/main/docs/CLI.md"
|
|
37
|
+
Repository = "https://github.com/abilityai/trinity"
|
|
38
|
+
Issues = "https://github.com/abilityai/trinity/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
trinity = "trinity_cli.main:cli"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
include = ["trinity_cli*"]
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
trinity_cli = ["py.typed"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""HTTP client for the Trinity Backend API."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from .config import get_api_key, get_instance_url
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _profile_from_context() -> Optional[str]:
|
|
13
|
+
"""Try to get the --profile value from the current Click context."""
|
|
14
|
+
try:
|
|
15
|
+
ctx = click.get_current_context(silent=True)
|
|
16
|
+
if ctx:
|
|
17
|
+
root = ctx.find_root()
|
|
18
|
+
if root.obj and "profile" in root.obj:
|
|
19
|
+
return root.obj["profile"]
|
|
20
|
+
except RuntimeError:
|
|
21
|
+
pass
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TrinityAPIError(Exception):
|
|
26
|
+
def __init__(self, status_code: int, detail: str):
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.detail = detail
|
|
29
|
+
super().__init__(f"HTTP {status_code}: {detail}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TrinityClient:
|
|
33
|
+
"""Thin HTTP wrapper around the Trinity FastAPI backend."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None,
|
|
36
|
+
profile: Optional[str] = None):
|
|
37
|
+
resolved_profile = profile or _profile_from_context()
|
|
38
|
+
self.base_url = base_url or get_instance_url(resolved_profile)
|
|
39
|
+
self.token = token or get_api_key(resolved_profile)
|
|
40
|
+
if not self.base_url:
|
|
41
|
+
print("Error: No Trinity instance configured. Run 'trinity init' or 'trinity login' first.", file=sys.stderr)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
def _headers(self) -> dict:
|
|
45
|
+
h = {"Content-Type": "application/json"}
|
|
46
|
+
if self.token:
|
|
47
|
+
h["Authorization"] = f"Bearer {self.token}"
|
|
48
|
+
return h
|
|
49
|
+
|
|
50
|
+
def _handle_response(self, resp: httpx.Response) -> Any:
|
|
51
|
+
if resp.status_code == 401:
|
|
52
|
+
print("Error: Authentication failed. Run 'trinity login' to re-authenticate.", file=sys.stderr)
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
if resp.status_code >= 400:
|
|
55
|
+
try:
|
|
56
|
+
detail = resp.json().get("detail", resp.text)
|
|
57
|
+
except Exception:
|
|
58
|
+
detail = resp.text
|
|
59
|
+
raise TrinityAPIError(resp.status_code, str(detail))
|
|
60
|
+
if resp.status_code == 204:
|
|
61
|
+
return None
|
|
62
|
+
return resp.json()
|
|
63
|
+
|
|
64
|
+
def get(self, path: str, params: Optional[dict] = None) -> Any:
|
|
65
|
+
with httpx.Client(timeout=30) as c:
|
|
66
|
+
resp = c.get(f"{self.base_url}{path}", headers=self._headers(), params=params)
|
|
67
|
+
return self._handle_response(resp)
|
|
68
|
+
|
|
69
|
+
def post(self, path: str, json: Optional[dict] = None) -> Any:
|
|
70
|
+
with httpx.Client(timeout=60) as c:
|
|
71
|
+
resp = c.post(f"{self.base_url}{path}", headers=self._headers(), json=json)
|
|
72
|
+
return self._handle_response(resp)
|
|
73
|
+
|
|
74
|
+
def put(self, path: str, json: Optional[dict] = None) -> Any:
|
|
75
|
+
with httpx.Client(timeout=30) as c:
|
|
76
|
+
resp = c.put(f"{self.base_url}{path}", headers=self._headers(), json=json)
|
|
77
|
+
return self._handle_response(resp)
|
|
78
|
+
|
|
79
|
+
def delete(self, path: str) -> Any:
|
|
80
|
+
with httpx.Client(timeout=30) as c:
|
|
81
|
+
resp = c.delete(f"{self.base_url}{path}", headers=self._headers())
|
|
82
|
+
return self._handle_response(resp)
|
|
83
|
+
|
|
84
|
+
def post_form(self, path: str, data: dict) -> Any:
|
|
85
|
+
"""POST with form-encoded body (for OAuth2 token endpoint)."""
|
|
86
|
+
with httpx.Client(timeout=30) as c:
|
|
87
|
+
headers = {}
|
|
88
|
+
if self.token:
|
|
89
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
90
|
+
resp = c.post(f"{self.base_url}{path}", data=data, headers=headers)
|
|
91
|
+
return self._handle_response(resp)
|
|
92
|
+
|
|
93
|
+
def post_unauthenticated(self, path: str, json: Optional[dict] = None) -> Any:
|
|
94
|
+
"""POST without auth header (for login/registration flows)."""
|
|
95
|
+
with httpx.Client(timeout=30) as c:
|
|
96
|
+
resp = c.post(
|
|
97
|
+
f"{self.base_url}{path}",
|
|
98
|
+
headers={"Content-Type": "application/json"},
|
|
99
|
+
json=json,
|
|
100
|
+
)
|
|
101
|
+
return self._handle_response(resp)
|
|
102
|
+
|
|
103
|
+
def get_unauthenticated(self, path: str) -> Any:
|
|
104
|
+
"""GET without auth header."""
|
|
105
|
+
with httpx.Client(timeout=30) as c:
|
|
106
|
+
resp = c.get(f"{self.base_url}{path}")
|
|
107
|
+
return self._handle_response(resp)
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Agent management commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from ..client import TrinityClient, TrinityAPIError
|
|
6
|
+
from ..output import format_output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def agents():
|
|
11
|
+
"""Manage agents."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@agents.command("list")
|
|
16
|
+
@click.option("--format", "fmt", type=click.Choice(["json", "table"]), default="json", help="Output format")
|
|
17
|
+
def list_agents(fmt):
|
|
18
|
+
"""List all agents."""
|
|
19
|
+
client = TrinityClient()
|
|
20
|
+
data = client.get("/api/agents")
|
|
21
|
+
if fmt == "table" and isinstance(data, list):
|
|
22
|
+
# Slim down for table view
|
|
23
|
+
rows = [
|
|
24
|
+
{
|
|
25
|
+
"name": a.get("name", ""),
|
|
26
|
+
"status": a.get("status", ""),
|
|
27
|
+
"template": a.get("template", ""),
|
|
28
|
+
"type": a.get("type", ""),
|
|
29
|
+
}
|
|
30
|
+
for a in data
|
|
31
|
+
]
|
|
32
|
+
format_output(rows, fmt)
|
|
33
|
+
else:
|
|
34
|
+
format_output(data, fmt)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@agents.command("get")
|
|
38
|
+
@click.argument("name")
|
|
39
|
+
@click.option("--format", "fmt", type=click.Choice(["json", "table"]), default="json", help="Output format")
|
|
40
|
+
def get_agent(name, fmt):
|
|
41
|
+
"""Get agent details."""
|
|
42
|
+
client = TrinityClient()
|
|
43
|
+
data = client.get(f"/api/agents/{name}")
|
|
44
|
+
format_output(data, fmt)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@agents.command("create")
|
|
48
|
+
@click.argument("name")
|
|
49
|
+
@click.option("--template", default=None, help="Template (e.g. github:Org/repo)")
|
|
50
|
+
@click.option("--format", "fmt", type=click.Choice(["json", "table"]), default="json", help="Output format")
|
|
51
|
+
def create_agent(name, template, fmt):
|
|
52
|
+
"""Create a new agent."""
|
|
53
|
+
client = TrinityClient()
|
|
54
|
+
payload = {"name": name}
|
|
55
|
+
if template:
|
|
56
|
+
payload["template"] = template
|
|
57
|
+
data = client.post("/api/agents", json=payload)
|
|
58
|
+
format_output(data, fmt)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@agents.command("delete")
|
|
62
|
+
@click.argument("name")
|
|
63
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this agent?")
|
|
64
|
+
def delete_agent(name):
|
|
65
|
+
"""Delete an agent."""
|
|
66
|
+
client = TrinityClient()
|
|
67
|
+
client.delete(f"/api/agents/{name}")
|
|
68
|
+
click.echo(f"Deleted agent '{name}'")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@agents.command("start")
|
|
72
|
+
@click.argument("name")
|
|
73
|
+
def start_agent(name):
|
|
74
|
+
"""Start an agent container."""
|
|
75
|
+
client = TrinityClient()
|
|
76
|
+
client.post(f"/api/agents/{name}/start")
|
|
77
|
+
click.echo(f"Started agent '{name}'")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@agents.command("stop")
|
|
81
|
+
@click.argument("name")
|
|
82
|
+
def stop_agent(name):
|
|
83
|
+
"""Stop an agent container."""
|
|
84
|
+
client = TrinityClient()
|
|
85
|
+
client.post(f"/api/agents/{name}/stop")
|
|
86
|
+
click.echo(f"Stopped agent '{name}'")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@agents.command("rename")
|
|
90
|
+
@click.argument("name")
|
|
91
|
+
@click.argument("new_name")
|
|
92
|
+
def rename_agent(name, new_name):
|
|
93
|
+
"""Rename an agent."""
|
|
94
|
+
client = TrinityClient()
|
|
95
|
+
client.put(f"/api/agents/{name}/rename", json={"new_name": new_name})
|
|
96
|
+
click.echo(f"Renamed '{name}' -> '{new_name}'")
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Authentication commands: login, logout, status, init."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from ..client import TrinityClient, TrinityAPIError
|
|
6
|
+
from ..config import (
|
|
7
|
+
clear_auth, get_instance_url, get_user, load_config,
|
|
8
|
+
profile_name_from_url, set_auth, _resolve_profile_name,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_profile_name(ctx: click.Context) -> str | None:
|
|
13
|
+
"""Extract the --profile value from the root context."""
|
|
14
|
+
root = ctx.find_root()
|
|
15
|
+
return root.obj.get("profile") if root.obj else None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.option("--instance", help="Trinity instance URL (e.g. https://trinity.example.com)")
|
|
20
|
+
@click.option("--profile", "profile_opt", default=None,
|
|
21
|
+
help="Profile name to store credentials under (default: hostname)")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def login(ctx, instance, profile_opt):
|
|
24
|
+
"""Log in to a Trinity instance with email verification."""
|
|
25
|
+
profile_name = profile_opt or _get_profile_name(ctx)
|
|
26
|
+
url = instance or get_instance_url(profile_name)
|
|
27
|
+
if not url:
|
|
28
|
+
url = click.prompt("Trinity instance URL")
|
|
29
|
+
url = url.rstrip("/")
|
|
30
|
+
|
|
31
|
+
client = TrinityClient(base_url=url, token="none")
|
|
32
|
+
|
|
33
|
+
email = click.prompt("Email")
|
|
34
|
+
|
|
35
|
+
# Request verification code
|
|
36
|
+
try:
|
|
37
|
+
client.post_unauthenticated("/api/auth/email/request", {"email": email})
|
|
38
|
+
except TrinityAPIError as e:
|
|
39
|
+
click.echo(f"Error requesting code: {e.detail}", err=True)
|
|
40
|
+
raise SystemExit(1)
|
|
41
|
+
|
|
42
|
+
click.echo(f"Verification code sent to {email}")
|
|
43
|
+
code = click.prompt("Enter 6-digit code")
|
|
44
|
+
|
|
45
|
+
# Verify code and get token
|
|
46
|
+
try:
|
|
47
|
+
result = client.post_unauthenticated("/api/auth/email/verify", {
|
|
48
|
+
"email": email,
|
|
49
|
+
"code": code,
|
|
50
|
+
})
|
|
51
|
+
except TrinityAPIError as e:
|
|
52
|
+
click.echo(f"Verification failed: {e.detail}", err=True)
|
|
53
|
+
raise SystemExit(1)
|
|
54
|
+
|
|
55
|
+
token = result["access_token"]
|
|
56
|
+
user = result.get("user")
|
|
57
|
+
|
|
58
|
+
# Determine profile name: explicit > global flag > derive from URL
|
|
59
|
+
target_profile = profile_name or profile_name_from_url(url)
|
|
60
|
+
set_auth(url, token, user, profile_name=target_profile)
|
|
61
|
+
name = user.get("name") or user.get("email") or user.get("username") if user else email
|
|
62
|
+
click.echo(f"Logged in as {name} [profile: {target_profile}]")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@click.command()
|
|
66
|
+
@click.pass_context
|
|
67
|
+
def logout(ctx):
|
|
68
|
+
"""Clear stored credentials for the current profile."""
|
|
69
|
+
profile_name = _get_profile_name(ctx)
|
|
70
|
+
clear_auth(profile_name)
|
|
71
|
+
resolved = _resolve_profile_name(profile_name)
|
|
72
|
+
click.echo(f"Logged out [profile: {resolved}]")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@click.command()
|
|
76
|
+
@click.pass_context
|
|
77
|
+
def status(ctx):
|
|
78
|
+
"""Show current login status and instance info."""
|
|
79
|
+
profile_name = _get_profile_name(ctx)
|
|
80
|
+
resolved = _resolve_profile_name(profile_name)
|
|
81
|
+
url = get_instance_url(profile_name)
|
|
82
|
+
|
|
83
|
+
click.echo(f"Profile: {resolved}")
|
|
84
|
+
|
|
85
|
+
if not url:
|
|
86
|
+
click.echo("Instance: Not configured. Run 'trinity init' or 'trinity login'.")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
user = get_user(profile_name)
|
|
90
|
+
config = load_config()
|
|
91
|
+
profile_data = config.get("profiles", {}).get(resolved, {})
|
|
92
|
+
|
|
93
|
+
click.echo(f"Instance: {url}")
|
|
94
|
+
if user:
|
|
95
|
+
click.echo(f"User: {user.get('email') or user.get('username')}")
|
|
96
|
+
click.echo(f"Role: {user.get('role', 'unknown')}")
|
|
97
|
+
elif profile_data.get("token"):
|
|
98
|
+
click.echo("User: (API key auth)")
|
|
99
|
+
else:
|
|
100
|
+
click.echo("User: Not logged in")
|
|
101
|
+
|
|
102
|
+
# Check connectivity
|
|
103
|
+
try:
|
|
104
|
+
client = TrinityClient(base_url=url, token=profile_data.get("token", "none"))
|
|
105
|
+
client.get_unauthenticated("/api/auth/mode")
|
|
106
|
+
click.echo("Status: Connected")
|
|
107
|
+
except Exception:
|
|
108
|
+
click.echo("Status: Unreachable")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@click.command()
|
|
112
|
+
@click.option("--profile", "profile_opt", default=None,
|
|
113
|
+
help="Profile name (default: derived from instance hostname)")
|
|
114
|
+
@click.pass_context
|
|
115
|
+
def init(ctx, profile_opt):
|
|
116
|
+
"""Set up Trinity CLI: configure instance, request access, and log in.
|
|
117
|
+
|
|
118
|
+
One command to go from zero to authenticated. Creates a named profile
|
|
119
|
+
for the instance (defaults to hostname).
|
|
120
|
+
"""
|
|
121
|
+
url = click.prompt("Trinity instance URL", default="http://localhost:8000")
|
|
122
|
+
url = url.rstrip("/")
|
|
123
|
+
|
|
124
|
+
client = TrinityClient(base_url=url, token="none")
|
|
125
|
+
|
|
126
|
+
# Verify instance is reachable
|
|
127
|
+
try:
|
|
128
|
+
client.get_unauthenticated("/api/auth/mode")
|
|
129
|
+
except Exception:
|
|
130
|
+
click.echo(f"Cannot reach {url}. Check the URL and try again.", err=True)
|
|
131
|
+
raise SystemExit(1)
|
|
132
|
+
|
|
133
|
+
click.echo(f"Connected to {url}")
|
|
134
|
+
|
|
135
|
+
# Determine profile name
|
|
136
|
+
profile_name = profile_opt or _get_profile_name(ctx) or profile_name_from_url(url)
|
|
137
|
+
|
|
138
|
+
email = click.prompt("Email")
|
|
139
|
+
|
|
140
|
+
# Request access (auto-approve endpoint)
|
|
141
|
+
try:
|
|
142
|
+
client.post_unauthenticated("/api/access/request", {"email": email})
|
|
143
|
+
click.echo("Access granted")
|
|
144
|
+
except TrinityAPIError as e:
|
|
145
|
+
if e.status_code == 409:
|
|
146
|
+
click.echo("Already registered")
|
|
147
|
+
else:
|
|
148
|
+
click.echo(f"Access request failed: {e.detail}", err=True)
|
|
149
|
+
raise SystemExit(1)
|
|
150
|
+
|
|
151
|
+
# Send verification code
|
|
152
|
+
try:
|
|
153
|
+
client.post_unauthenticated("/api/auth/email/request", {"email": email})
|
|
154
|
+
except TrinityAPIError as e:
|
|
155
|
+
click.echo(f"Error requesting code: {e.detail}", err=True)
|
|
156
|
+
raise SystemExit(1)
|
|
157
|
+
|
|
158
|
+
click.echo(f"Verification code sent to {email}")
|
|
159
|
+
code = click.prompt("Enter 6-digit code")
|
|
160
|
+
|
|
161
|
+
# Verify and get token
|
|
162
|
+
try:
|
|
163
|
+
result = client.post_unauthenticated("/api/auth/email/verify", {
|
|
164
|
+
"email": email,
|
|
165
|
+
"code": code,
|
|
166
|
+
})
|
|
167
|
+
except TrinityAPIError as e:
|
|
168
|
+
click.echo(f"Verification failed: {e.detail}", err=True)
|
|
169
|
+
raise SystemExit(1)
|
|
170
|
+
|
|
171
|
+
token = result["access_token"]
|
|
172
|
+
user = result.get("user")
|
|
173
|
+
|
|
174
|
+
set_auth(url, token, user, profile_name=profile_name)
|
|
175
|
+
name = user.get("name") or user.get("email") or user.get("username") if user else email
|
|
176
|
+
click.echo(f"Logged in as {name} [profile: {profile_name}]")
|
|
177
|
+
click.echo(f"\nTrinity CLI is ready. Try 'trinity agents list'.")
|