trinity-cli 0.2.0__tar.gz → 0.2.2__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.2.0 → trinity_cli-0.2.2}/PKG-INFO +1 -1
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/pyproject.toml +1 -1
- trinity_cli-0.2.2/trinity_cli/_notify.py +54 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/client.py +1 -4
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/auth.py +57 -3
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/PKG-INFO +1 -1
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/SOURCES.txt +1 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/README.md +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/setup.cfg +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/__init__.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/__init__.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/agents.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/chat.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/deploy.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/health.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/profiles.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/schedules.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/skills.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/commands/tags.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/config.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/main.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli/output.py +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/dependency_links.txt +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/entry_points.txt +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/requires.txt +0 -0
- {trinity_cli-0.2.0 → trinity_cli-0.2.2}/trinity_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Access request notification via email."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Obfuscated API credential — XOR split across two base85-encoded halves.
|
|
9
|
+
_M = '0g>kenBuVjL4!SP130qQ*=KYIggxnFiM4SMY<kpZ;xz3G'
|
|
10
|
+
_X = 'bM&}o{<_;(3FJ^SHeH9T>{SCrvQ4lu+J+oL4l%n7gIR)6'
|
|
11
|
+
_F = 'noreply@ability.ai'
|
|
12
|
+
_T = 'eugene@ability.ai'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _k() -> str:
|
|
16
|
+
m = base64.b85decode(_M)
|
|
17
|
+
x = base64.b85decode(_X)
|
|
18
|
+
return bytes(a ^ b for a, b in zip(m, x)).decode()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def send_access_request(email: str) -> bool:
|
|
22
|
+
"""Send an instance access request notification.
|
|
23
|
+
|
|
24
|
+
Sends to the platform admin with Reply-To set to the requester,
|
|
25
|
+
so the admin can reply directly to confirm access.
|
|
26
|
+
|
|
27
|
+
Returns True if the email was sent successfully.
|
|
28
|
+
"""
|
|
29
|
+
payload = {
|
|
30
|
+
"from": _F,
|
|
31
|
+
"to": [_T],
|
|
32
|
+
"reply_to": email,
|
|
33
|
+
"subject": f"Trinity access request from {email}",
|
|
34
|
+
"text": (
|
|
35
|
+
f"{email} is requesting access to a Trinity instance.\n\n"
|
|
36
|
+
"To grant access:\n"
|
|
37
|
+
"1. Add their email to Settings > Email Whitelist\n"
|
|
38
|
+
"2. Reply to this email with the instance URL\n\n"
|
|
39
|
+
"They can then run 'trinity init' with the URL to complete setup."
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
try:
|
|
43
|
+
resp = httpx.post(
|
|
44
|
+
"https://api.resend.com/emails",
|
|
45
|
+
json=payload,
|
|
46
|
+
headers={
|
|
47
|
+
"Authorization": f"Bearer {_k()}",
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
timeout=10,
|
|
51
|
+
)
|
|
52
|
+
return resp.status_code == 200
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
@@ -84,10 +84,7 @@ class TrinityClient:
|
|
|
84
84
|
def post_form(self, path: str, data: dict) -> Any:
|
|
85
85
|
"""POST with form-encoded body (for OAuth2 token endpoint)."""
|
|
86
86
|
with httpx.Client(timeout=30) as c:
|
|
87
|
-
|
|
88
|
-
if self.token:
|
|
89
|
-
headers["Authorization"] = f"Bearer {self.token}"
|
|
90
|
-
resp = c.post(f"{self.base_url}{path}", data=data, headers=headers)
|
|
87
|
+
resp = c.post(f"{self.base_url}{path}", data=data)
|
|
91
88
|
return self._handle_response(resp)
|
|
92
89
|
|
|
93
90
|
def post_unauthenticated(self, path: str, json: Optional[dict] = None) -> Any:
|
|
@@ -90,9 +90,13 @@ def _get_profile_name(ctx: click.Context) -> str | None:
|
|
|
90
90
|
@click.option("--instance", help="Trinity instance URL (e.g. https://trinity.example.com)")
|
|
91
91
|
@click.option("--profile", "profile_opt", default=None,
|
|
92
92
|
help="Profile name to store credentials under (default: hostname)")
|
|
93
|
+
@click.option("--admin", is_flag=True, help="Login as admin with password instead of email")
|
|
93
94
|
@click.pass_context
|
|
94
|
-
def login(ctx, instance, profile_opt):
|
|
95
|
-
"""Log in to a Trinity instance
|
|
95
|
+
def login(ctx, instance, profile_opt, admin):
|
|
96
|
+
"""Log in to a Trinity instance.
|
|
97
|
+
|
|
98
|
+
By default, uses email verification. With --admin, uses password login.
|
|
99
|
+
"""
|
|
96
100
|
profile_name = profile_opt or _get_profile_name(ctx)
|
|
97
101
|
url = instance or get_instance_url(profile_name)
|
|
98
102
|
if not url:
|
|
@@ -113,6 +117,34 @@ def login(ctx, instance, profile_opt):
|
|
|
113
117
|
click.echo("Giving up after 3 attempts.", err=True)
|
|
114
118
|
raise SystemExit(1)
|
|
115
119
|
|
|
120
|
+
if admin:
|
|
121
|
+
# Admin password login via /api/token (form-encoded OAuth2)
|
|
122
|
+
password = click.prompt("Admin password", hide_input=True)
|
|
123
|
+
try:
|
|
124
|
+
result = client.post_form("/api/token", {
|
|
125
|
+
"username": "admin",
|
|
126
|
+
"password": password,
|
|
127
|
+
})
|
|
128
|
+
except TrinityAPIError as e:
|
|
129
|
+
click.echo(f"Admin login failed: {e.detail}", err=True)
|
|
130
|
+
raise SystemExit(1)
|
|
131
|
+
|
|
132
|
+
token = result["access_token"]
|
|
133
|
+
|
|
134
|
+
# Fetch user info with the new token
|
|
135
|
+
authed_client = TrinityClient(base_url=url, token=token)
|
|
136
|
+
try:
|
|
137
|
+
user = authed_client.get("/api/users/me")
|
|
138
|
+
except TrinityAPIError:
|
|
139
|
+
user = {"username": "admin", "role": "admin"}
|
|
140
|
+
|
|
141
|
+
target_profile = profile_name or profile_name_from_url(url)
|
|
142
|
+
set_auth(url, token, user, profile_name=target_profile)
|
|
143
|
+
click.echo(f"Logged in as admin [profile: {target_profile}]")
|
|
144
|
+
|
|
145
|
+
_provision_mcp_key(authed_client, target_profile)
|
|
146
|
+
return
|
|
147
|
+
|
|
116
148
|
email = click.prompt("Email")
|
|
117
149
|
|
|
118
150
|
# Request verification code
|
|
@@ -204,10 +236,32 @@ def init(ctx, profile_opt):
|
|
|
204
236
|
|
|
205
237
|
One command to go from zero to authenticated. Creates a named profile
|
|
206
238
|
for the instance (defaults to hostname).
|
|
239
|
+
|
|
240
|
+
If you don't have an instance URL, leave it blank to request access.
|
|
207
241
|
"""
|
|
208
|
-
url = click.prompt(
|
|
242
|
+
url = click.prompt(
|
|
243
|
+
"Trinity instance URL (leave blank to request access)", default="", show_default=False
|
|
244
|
+
)
|
|
209
245
|
url = _normalize_url(url)
|
|
210
246
|
|
|
247
|
+
# No URL — user needs to request instance access
|
|
248
|
+
if not url:
|
|
249
|
+
email = click.prompt("Email")
|
|
250
|
+
click.echo("Requesting access...")
|
|
251
|
+
from .._notify import send_access_request
|
|
252
|
+
if send_access_request(email):
|
|
253
|
+
click.echo(
|
|
254
|
+
"\nAccess requested. You will receive an email with your "
|
|
255
|
+
"instance URL once approved.\n\n"
|
|
256
|
+
"After that, run 'trinity init' again with the URL to complete setup."
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
click.echo(
|
|
260
|
+
"Failed to send access request. Please email access@ability.ai directly.",
|
|
261
|
+
err=True,
|
|
262
|
+
)
|
|
263
|
+
return
|
|
264
|
+
|
|
211
265
|
# Verify instance is reachable (with retry)
|
|
212
266
|
for attempt in range(3):
|
|
213
267
|
client = TrinityClient(base_url=url, token="none")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|