@agentsdock/server 1.0.7-beta.5
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.
- package/LICENSE +202 -0
- package/NOTICE +19 -0
- package/README.md +60 -0
- package/npm/cli.cjs +289 -0
- package/package.json +32 -0
- package/server/LICENSE +202 -0
- package/server/NOTICE +19 -0
- package/server/VERSION +1 -0
- package/server/activation_transaction.py +2458 -0
- package/server/agent_server.py +93226 -0
- package/server/agentsdock_chats.py +1237 -0
- package/server/agentsdock_emergency.py +338 -0
- package/server/agentsdock_jobs.py +640 -0
- package/server/agentsdock_mail.py +325 -0
- package/server/agentsdock_publish.py +404 -0
- package/server/agentsdock_team.py +793 -0
- package/server/agentsdock_team_hub/__init__.py +33 -0
- package/server/agentsdock_team_hub/auth.py +710 -0
- package/server/agentsdock_team_hub/cli.py +647 -0
- package/server/agentsdock_team_hub/database.py +310 -0
- package/server/agentsdock_team_hub/mail_hint_streams.py +176 -0
- package/server/agentsdock_team_hub/mail_hints.py +201 -0
- package/server/agentsdock_team_hub/migrations/0001_identity_auth.sql +690 -0
- package/server/agentsdock_team_hub/migrations/0002_teamspace_ledger.sql +1336 -0
- package/server/agentsdock_team_hub/migrations/0003_service_runtime.sql +361 -0
- package/server/agentsdock_team_hub/migrations/0004_managed_host_binding.sql +22 -0
- package/server/agentsdock_team_hub/migrations/0005_tailnet_bootstrap_delegations.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0006_team_network_mailbox.sql +423 -0
- package/server/agentsdock_team_hub/migrations/0007_local_agent_mail.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0008_managed_server_session.sql +73 -0
- package/server/agentsdock_team_hub/migrations/0009_team_messages.sql +324 -0
- package/server/agentsdock_team_hub/migrations/0010_team_attachment_orphan_reclamation.sql +52 -0
- package/server/agentsdock_team_hub/migrations/0011_human_admin_paging.sql +65 -0
- package/server/agentsdock_team_hub/migrations/0012_network_content_deletions.sql +69 -0
- package/server/agentsdock_team_hub/migrations/0013_team_message_revisions.sql +48 -0
- package/server/agentsdock_team_hub/migrations/0014_managed_network_owner.sql +50 -0
- package/server/agentsdock_team_hub/migrations/0015_team_message_inbox_dismissals.sql +3 -0
- package/server/agentsdock_team_hub/migrations/0016_team_message_all_servers.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0017_skill_announcement_deletions.sql +16 -0
- package/server/agentsdock_team_hub/migrations/0018_team_mail_subjects.sql +8 -0
- package/server/agentsdock_team_hub/migrations/0019_team_mailbox_state.sql +5 -0
- package/server/agentsdock_team_hub/migrations/0020_team_mail_arrivals.sql +44 -0
- package/server/agentsdock_team_hub/migrations/0021_team_mail_threads.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0022_team_bulletin_changes.sql +49 -0
- package/server/agentsdock_team_hub/migrations/0023_team_message_search.sql +93 -0
- package/server/agentsdock_team_hub/migrations/__init__.py +1 -0
- package/server/agentsdock_team_hub/notification_hints.py +306 -0
- package/server/agentsdock_team_hub/secure_peer.py +11215 -0
- package/server/agentsdock_team_hub/secure_peer_hub.py +1372 -0
- package/server/agentsdock_team_hub/security.py +318 -0
- package/server/agentsdock_team_hub/service.py +1737 -0
- package/server/agentsdock_team_hub/store.py +16276 -0
- package/server/chat_mailbox.py +448 -0
- package/server/claude_background_reconciliation.py +92 -0
- package/server/claude_goals.py +245 -0
- package/server/claude_history_provenance.py +137 -0
- package/server/claude_history_repair.py +1601 -0
- package/server/claude_sdk_client.py +3731 -0
- package/server/claude_side_question.py +212 -0
- package/server/codex_app_server.py +3030 -0
- package/server/codex_auth.py +93 -0
- package/server/codex_history_repair.py +1247 -0
- package/server/codex_provider.py +1120 -0
- package/server/codex_side_question.py +301 -0
- package/server/cursor_agent_client.py +654 -0
- package/server/cursor_process_guard.py +347 -0
- package/server/execution_activation.py +610 -0
- package/server/execution_control.py +233 -0
- package/server/execution_durability.py +184 -0
- package/server/execution_http.py +256 -0
- package/server/execution_install.py +792 -0
- package/server/execution_legacy_runner.py +406 -0
- package/server/execution_maintenance.py +137 -0
- package/server/execution_manage.py +615 -0
- package/server/execution_ownership.py +83 -0
- package/server/execution_preparation.py +362 -0
- package/server/execution_recovery.py +845 -0
- package/server/execution_recovery_status.py +195 -0
- package/server/execution_service.py +387 -0
- package/server/execution_transport.py +622 -0
- package/server/execution_uninstall.py +406 -0
- package/server/execution_update_status.py +49 -0
- package/server/install.sh +7074 -0
- package/server/interactive_chat_controls.py +153 -0
- package/server/interactive_chat_native.py +66 -0
- package/server/interactive_chat_projection.py +152 -0
- package/server/interactive_chat_runtime.py +132 -0
- package/server/interactive_chat_share_routes.py +534 -0
- package/server/interactive_chat_share_web.py +12 -0
- package/server/interactive_chat_shares.py +238 -0
- package/server/provider_commands.py +425 -0
- package/server/public_chat_share_routes.py +426 -0
- package/server/public_chat_shares.py +759 -0
- package/server/public_chat_transcript.py +357 -0
- package/server/pyproject.toml +29 -0
- package/server/release-public-key.pem +3 -0
- package/server/secure_peer_delivery.py +1236 -0
- package/server/secure_peer_runtime.py +7694 -0
- package/server/shared_chat_video_stream.py +262 -0
- package/server/shared_chat_videos.py +155 -0
- package/server/side_questions.py +594 -0
- package/server/team_hub_host.py +1354 -0
- package/server/team_mail_grants.py +136 -0
- package/server/team_mail_runtime.py +302 -0
- package/server/team_mail_websocket.py +211 -0
- package/server/title_generation.py +333 -0
- package/server/uninstall.sh +359 -0
- package/server/update_handoff.py +237 -0
- package/server/update_preparation.py +264 -0
- package/server/update_recovery.py +140 -0
- package/server/update_runner.py +1819 -0
- package/server/uv.lock +1101 -0
- package/server/workspace_git.py +545 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Capability-scoped Team Network mail CLI for AgentsDock agents."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import hashlib
|
|
8
|
+
import ipaddress
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.parse
|
|
14
|
+
import urllib.request
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
MAIL_BODY_MAX_BYTES = 8_192
|
|
20
|
+
PROVIDER_RUNTIME_VALUE_MAX_BYTES = 4096
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MailCLIError(RuntimeError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NoRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|
28
|
+
def redirect_request(self, *_args: Any, **_kwargs: Any) -> None:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _canonical_http_origin(value: str, label: str) -> tuple[str, bool]:
|
|
33
|
+
raw = value.strip()
|
|
34
|
+
try:
|
|
35
|
+
parsed = urllib.parse.urlsplit(raw)
|
|
36
|
+
port = parsed.port or 80
|
|
37
|
+
except ValueError as exc:
|
|
38
|
+
raise MailCLIError(f"{label} must be an HTTP origin") from exc
|
|
39
|
+
if (
|
|
40
|
+
parsed.scheme.lower() != "http"
|
|
41
|
+
or not parsed.hostname
|
|
42
|
+
or parsed.username is not None
|
|
43
|
+
or parsed.password is not None
|
|
44
|
+
or parsed.path not in {"", "/"}
|
|
45
|
+
or parsed.query
|
|
46
|
+
or parsed.fragment
|
|
47
|
+
):
|
|
48
|
+
raise MailCLIError(f"{label} must be an HTTP origin")
|
|
49
|
+
host = parsed.hostname.lower()
|
|
50
|
+
try:
|
|
51
|
+
address = ipaddress.ip_address(host)
|
|
52
|
+
if isinstance(address, ipaddress.IPv6Address) and address.ipv4_mapped:
|
|
53
|
+
address = address.ipv4_mapped
|
|
54
|
+
loopback = address.is_loopback
|
|
55
|
+
host = address.compressed
|
|
56
|
+
url_host = f"[{host}]" if isinstance(address, ipaddress.IPv6Address) else host
|
|
57
|
+
except ValueError:
|
|
58
|
+
loopback = host == "localhost"
|
|
59
|
+
url_host = host
|
|
60
|
+
return f"http://{url_host}:{port}", loopback
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _authority_server_origin(authority_file: str | None) -> str:
|
|
64
|
+
path = _selected_authority_path(authority_file)
|
|
65
|
+
try:
|
|
66
|
+
if path.stat().st_mode & 0o077:
|
|
67
|
+
raise MailCLIError("authority file permissions are unsafe")
|
|
68
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
69
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
70
|
+
raise MailCLIError(f"could not read authority file: {exc}") from exc
|
|
71
|
+
return _bounded_identity_value(
|
|
72
|
+
payload.get("provider_server_origin"),
|
|
73
|
+
"authority provider_server_origin",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _loopback_server_url() -> str:
|
|
78
|
+
raw_server_url = os.environ.get("AGENTSDOCK_SERVER_URL", "").strip()
|
|
79
|
+
if not raw_server_url:
|
|
80
|
+
raise MailCLIError("missing AgentsDock agent environment")
|
|
81
|
+
server_origin, loopback = _canonical_http_origin(
|
|
82
|
+
raw_server_url,
|
|
83
|
+
"AGENTSDOCK_SERVER_URL",
|
|
84
|
+
)
|
|
85
|
+
runtime_origin = _bounded_identity_value(
|
|
86
|
+
os.environ.get("AGENTSDOCK_PROVIDER_SERVER_ORIGIN"),
|
|
87
|
+
"AGENTSDOCK_PROVIDER_SERVER_ORIGIN",
|
|
88
|
+
)
|
|
89
|
+
if runtime_origin:
|
|
90
|
+
canonical_runtime, _runtime_loopback = _canonical_http_origin(
|
|
91
|
+
runtime_origin,
|
|
92
|
+
"AGENTSDOCK_PROVIDER_SERVER_ORIGIN",
|
|
93
|
+
)
|
|
94
|
+
if canonical_runtime != server_origin:
|
|
95
|
+
raise MailCLIError(
|
|
96
|
+
"AGENTSDOCK_SERVER_URL conflicts with the live provider origin"
|
|
97
|
+
)
|
|
98
|
+
if loopback:
|
|
99
|
+
return raw_server_url.rstrip("/")
|
|
100
|
+
authority_origin = _authority_server_origin(None)
|
|
101
|
+
if not authority_origin:
|
|
102
|
+
raise MailCLIError(
|
|
103
|
+
"non-loopback AGENTSDOCK_SERVER_URL must match the authority origin"
|
|
104
|
+
)
|
|
105
|
+
canonical_authority, _authority_loopback = _canonical_http_origin(
|
|
106
|
+
authority_origin,
|
|
107
|
+
"authority provider_server_origin",
|
|
108
|
+
)
|
|
109
|
+
if canonical_authority != server_origin:
|
|
110
|
+
raise MailCLIError(
|
|
111
|
+
"non-loopback AGENTSDOCK_SERVER_URL must match the authority origin"
|
|
112
|
+
)
|
|
113
|
+
return server_origin
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _bounded_identity_value(value: str | None, label: str) -> str:
|
|
117
|
+
clean = str(value or "").strip()
|
|
118
|
+
try:
|
|
119
|
+
size = len(clean.encode("utf-8"))
|
|
120
|
+
except UnicodeEncodeError as exc:
|
|
121
|
+
raise MailCLIError(f"{label} is not valid UTF-8") from exc
|
|
122
|
+
if size > PROVIDER_RUNTIME_VALUE_MAX_BYTES:
|
|
123
|
+
raise MailCLIError(f"{label} exceeds the provider runtime limit")
|
|
124
|
+
return clean
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _selected_authority_path(authority_file: str | None) -> Path:
|
|
128
|
+
explicit = _bounded_identity_value(authority_file, "--authority-file")
|
|
129
|
+
ambient = _bounded_identity_value(
|
|
130
|
+
os.environ.get("AGENTSDOCK_PROVIDER_AUTHORITY_FILE"),
|
|
131
|
+
"AGENTSDOCK_PROVIDER_AUTHORITY_FILE",
|
|
132
|
+
)
|
|
133
|
+
if explicit and ambient:
|
|
134
|
+
explicit_key = os.path.abspath(os.path.expanduser(explicit))
|
|
135
|
+
ambient_key = os.path.abspath(os.path.expanduser(ambient))
|
|
136
|
+
if explicit_key != ambient_key:
|
|
137
|
+
raise MailCLIError(
|
|
138
|
+
"--authority-file conflicts with the live provider authority"
|
|
139
|
+
)
|
|
140
|
+
selected = explicit or ambient
|
|
141
|
+
if not selected:
|
|
142
|
+
raise MailCLIError("--authority-file is required")
|
|
143
|
+
return Path(selected).expanduser()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _provider_authority(authority_file: str | None) -> tuple[str, str]:
|
|
147
|
+
path = _selected_authority_path(authority_file)
|
|
148
|
+
try:
|
|
149
|
+
if path.stat().st_mode & 0o077:
|
|
150
|
+
raise MailCLIError("authority file permissions are unsafe")
|
|
151
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
152
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
153
|
+
raise MailCLIError(f"could not read authority file: {exc}") from exc
|
|
154
|
+
capability = str(payload.get("provider_capability") or payload.get("capability") or "")
|
|
155
|
+
source_session_id = str(payload.get("source_session_id") or "").strip()
|
|
156
|
+
if not capability or not source_session_id:
|
|
157
|
+
raise MailCLIError("authority file is invalid")
|
|
158
|
+
environment_chat_id = _bounded_identity_value(
|
|
159
|
+
os.environ.get("AGENTSDOCK_CHAT_ID"),
|
|
160
|
+
"AGENTSDOCK_CHAT_ID",
|
|
161
|
+
)
|
|
162
|
+
if environment_chat_id and environment_chat_id != source_session_id:
|
|
163
|
+
raise MailCLIError(
|
|
164
|
+
"AGENTSDOCK_CHAT_ID does not match the authority file"
|
|
165
|
+
)
|
|
166
|
+
return capability, source_session_id
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _request_json(
|
|
170
|
+
method: str,
|
|
171
|
+
path: str,
|
|
172
|
+
capability: str,
|
|
173
|
+
payload: dict[str, Any] | None = None,
|
|
174
|
+
) -> dict[str, Any]:
|
|
175
|
+
body = None if payload is None else json.dumps(payload).encode("utf-8")
|
|
176
|
+
request = urllib.request.Request(
|
|
177
|
+
f"{_loopback_server_url()}{path}",
|
|
178
|
+
data=body,
|
|
179
|
+
headers={
|
|
180
|
+
"Accept": "application/json",
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
"X-AgentsDock-Provider-Capability": capability,
|
|
183
|
+
},
|
|
184
|
+
method=method,
|
|
185
|
+
)
|
|
186
|
+
opener = urllib.request.build_opener(
|
|
187
|
+
urllib.request.ProxyHandler({}),
|
|
188
|
+
NoRedirectHandler(),
|
|
189
|
+
)
|
|
190
|
+
try:
|
|
191
|
+
with opener.open(request, timeout=30) as response:
|
|
192
|
+
result = json.loads(response.read().decode("utf-8"))
|
|
193
|
+
except urllib.error.HTTPError as exc:
|
|
194
|
+
raw = exc.read().decode("utf-8", errors="replace")
|
|
195
|
+
try:
|
|
196
|
+
detail = json.loads(raw).get("detail") or raw
|
|
197
|
+
except json.JSONDecodeError:
|
|
198
|
+
detail = raw
|
|
199
|
+
raise MailCLIError(
|
|
200
|
+
f"server rejected Team Network mail ({exc.code}): {detail or exc.reason}"
|
|
201
|
+
) from exc
|
|
202
|
+
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
|
203
|
+
raise MailCLIError(
|
|
204
|
+
f"could not reach AgentsServer: {getattr(exc, 'reason', exc)}"
|
|
205
|
+
) from exc
|
|
206
|
+
if not isinstance(result, dict):
|
|
207
|
+
raise MailCLIError("AgentsServer returned an invalid response")
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def list_routes(args: argparse.Namespace) -> dict[str, Any]:
|
|
212
|
+
capability, _session_id = _provider_authority(args.authority_file)
|
|
213
|
+
result = _request_json("GET", "/api/agent/team-mail/routes", capability)
|
|
214
|
+
routes = result.get("routes")
|
|
215
|
+
if (
|
|
216
|
+
not isinstance(routes, list)
|
|
217
|
+
or any(not isinstance(route, dict) for route in routes)
|
|
218
|
+
):
|
|
219
|
+
raise MailCLIError("AgentsServer returned an invalid Team Network mail route list")
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def send(args: argparse.Namespace) -> dict[str, Any]:
|
|
224
|
+
capability, _session_id = _provider_authority(args.authority_file)
|
|
225
|
+
if sys.stdin.isatty():
|
|
226
|
+
raise MailCLIError("Team Network mail body must be provided on stdin")
|
|
227
|
+
input_stream = getattr(sys.stdin, "buffer", sys.stdin)
|
|
228
|
+
raw_message = input_stream.read(MAIL_BODY_MAX_BYTES + 1)
|
|
229
|
+
if isinstance(raw_message, str):
|
|
230
|
+
raw_message = raw_message.encode("utf-8")
|
|
231
|
+
if len(raw_message) > MAIL_BODY_MAX_BYTES:
|
|
232
|
+
raise MailCLIError("Team Network mail body exceeds the configured size limit")
|
|
233
|
+
try:
|
|
234
|
+
message = raw_message.decode("utf-8").strip()
|
|
235
|
+
except UnicodeDecodeError as exc:
|
|
236
|
+
raise MailCLIError("Team Network mail body must be valid UTF-8") from exc
|
|
237
|
+
if not message:
|
|
238
|
+
raise MailCLIError("Team Network mail body on stdin must not be empty")
|
|
239
|
+
route_id = str(args.route or "").strip()
|
|
240
|
+
kind = str(args.kind or "message")
|
|
241
|
+
if kind != "message":
|
|
242
|
+
raise MailCLIError("AgentsDock agent mail permits messages only")
|
|
243
|
+
stable_key = "mail_cli_" + hashlib.sha256(
|
|
244
|
+
f"{capability}\0{route_id}\0{kind}\0{message}".encode("utf-8")
|
|
245
|
+
).hexdigest()
|
|
246
|
+
result = _request_json(
|
|
247
|
+
"POST",
|
|
248
|
+
f"/api/agent/team-mail/routes/{urllib.parse.quote(route_id, safe='')}",
|
|
249
|
+
capability,
|
|
250
|
+
{
|
|
251
|
+
"kind": kind,
|
|
252
|
+
"message": message,
|
|
253
|
+
"idempotency_key": args.idempotency_key or stable_key,
|
|
254
|
+
},
|
|
255
|
+
)
|
|
256
|
+
if set(result) != {"ok", "route_id", "kind", "accepted", "duplicate"}:
|
|
257
|
+
raise MailCLIError("AgentsServer returned an invalid Team Network mail receipt")
|
|
258
|
+
if (
|
|
259
|
+
result.get("ok") is not True
|
|
260
|
+
or result.get("route_id") != route_id
|
|
261
|
+
or result.get("kind") != kind
|
|
262
|
+
or result.get("accepted") is not True
|
|
263
|
+
or type(result.get("duplicate")) is not bool
|
|
264
|
+
):
|
|
265
|
+
raise MailCLIError("AgentsServer returned an invalid Team Network mail receipt")
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def parser() -> argparse.ArgumentParser:
|
|
270
|
+
root = argparse.ArgumentParser(
|
|
271
|
+
description="Send passive Team Network mail using this live agent turn.",
|
|
272
|
+
allow_abbrev=False,
|
|
273
|
+
)
|
|
274
|
+
root.add_argument(
|
|
275
|
+
"--authority-file",
|
|
276
|
+
help="mode-0600 per-run AgentsDock provider authority file",
|
|
277
|
+
)
|
|
278
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
279
|
+
list_command = commands.add_parser(
|
|
280
|
+
"list", help="list this turn's opaque mail routes", allow_abbrev=False
|
|
281
|
+
)
|
|
282
|
+
list_command.set_defaults(handler=list_routes)
|
|
283
|
+
send_command = commands.add_parser(
|
|
284
|
+
"send",
|
|
285
|
+
help="send one passive mailbox item with its UTF-8 body on stdin",
|
|
286
|
+
allow_abbrev=False,
|
|
287
|
+
)
|
|
288
|
+
send_command.add_argument("--route", required=True, help="opaque route from list")
|
|
289
|
+
send_command.add_argument(
|
|
290
|
+
"--kind",
|
|
291
|
+
choices=("message",),
|
|
292
|
+
default="message",
|
|
293
|
+
)
|
|
294
|
+
send_command.add_argument("--idempotency-key", help=argparse.SUPPRESS)
|
|
295
|
+
send_command.set_defaults(handler=send)
|
|
296
|
+
return root
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def main(argv: list[str] | None = None) -> int:
|
|
300
|
+
previous_authority_file = os.environ.get(
|
|
301
|
+
"AGENTSDOCK_PROVIDER_AUTHORITY_FILE"
|
|
302
|
+
)
|
|
303
|
+
try:
|
|
304
|
+
args = parser().parse_args(argv)
|
|
305
|
+
selected_authority = _selected_authority_path(args.authority_file)
|
|
306
|
+
os.environ["AGENTSDOCK_PROVIDER_AUTHORITY_FILE"] = str(
|
|
307
|
+
selected_authority
|
|
308
|
+
)
|
|
309
|
+
result = args.handler(args)
|
|
310
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
311
|
+
return 0
|
|
312
|
+
except MailCLIError as exc:
|
|
313
|
+
print(f"agentsdock-mail: {exc}", file=sys.stderr)
|
|
314
|
+
return 2
|
|
315
|
+
finally:
|
|
316
|
+
if previous_authority_file is None:
|
|
317
|
+
os.environ.pop("AGENTSDOCK_PROVIDER_AUTHORITY_FILE", None)
|
|
318
|
+
else:
|
|
319
|
+
os.environ[
|
|
320
|
+
"AGENTSDOCK_PROVIDER_AUTHORITY_FILE"
|
|
321
|
+
] = previous_authority_file
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
if __name__ == "__main__":
|
|
325
|
+
raise SystemExit(main())
|