roviers 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.
- roviers-0.1.0/.gitignore +6 -0
- roviers-0.1.0/PKG-INFO +71 -0
- roviers-0.1.0/README.md +59 -0
- roviers-0.1.0/pyproject.toml +27 -0
- roviers-0.1.0/roviers/__init__.py +15 -0
- roviers-0.1.0/roviers/__main__.py +5 -0
- roviers-0.1.0/roviers/cli.py +28 -0
- roviers-0.1.0/roviers/client.py +250 -0
- roviers-0.1.0/roviers/installer.py +115 -0
roviers-0.1.0/.gitignore
ADDED
roviers-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: roviers
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client SDK for Roviers: High-Throughput Service Intake & Claim-Check Vault
|
|
5
|
+
Author: Sunil K Sundaram
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: httpx>=0.25.0
|
|
9
|
+
Provides-Extra: agents
|
|
10
|
+
Requires-Dist: langchain-core>=0.2.0; extra == 'agents'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Roviers Python SDK 🤖
|
|
14
|
+
|
|
15
|
+
Broker & Agent Asynchronous Task & Claim-Check Vault Client for AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed microservices.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install ./clients/python
|
|
21
|
+
# Or in editable mode:
|
|
22
|
+
pip install -e ./clients/python
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
from roviers import RoviersBrokerClient
|
|
30
|
+
|
|
31
|
+
async def main():
|
|
32
|
+
# 1. Connect to Roviers Broker
|
|
33
|
+
broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080")
|
|
34
|
+
|
|
35
|
+
# 2. Issue or load Agent Token
|
|
36
|
+
await broker.issue_agent_token(
|
|
37
|
+
agent_id="AGENT-RESEARCH-01",
|
|
38
|
+
agent_name="Autonomous Research Agent",
|
|
39
|
+
save_path="agent_token.json"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# 3. Submit heavy asynchronous request (returns in ~2ms with Receipt Token)
|
|
43
|
+
receipt = await broker.submit(
|
|
44
|
+
service_name="service.finance.risk_model",
|
|
45
|
+
payload={"portfolio_id": "port_9921", "simulations": 10000}
|
|
46
|
+
)
|
|
47
|
+
print(f"Receipt received: #{receipt.receipt_id} (Status: {receipt.status})")
|
|
48
|
+
|
|
49
|
+
# 4. Wait for real-time SSE signal and redeem outcome from Broker Vault
|
|
50
|
+
outcome = await broker.wait_for_outcome(receipt.receipt_id)
|
|
51
|
+
print("Vault Outcome:", outcome.result)
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## LangGraph / CrewAI Agent Tool Integration
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from roviers import RoviersBrokerClient, RoviersTool
|
|
61
|
+
|
|
62
|
+
broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080", token="agent_token.json")
|
|
63
|
+
|
|
64
|
+
# Expose as a non-blocking agent tool
|
|
65
|
+
deep_research_tool = RoviersTool(
|
|
66
|
+
broker=broker,
|
|
67
|
+
service_name="service.ai.deep_research",
|
|
68
|
+
name="deep_research",
|
|
69
|
+
description="Runs deep multi-source research asynchronously without blocking agent loops"
|
|
70
|
+
)
|
|
71
|
+
```
|
roviers-0.1.0/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Roviers Python SDK 🤖
|
|
2
|
+
|
|
3
|
+
Broker & Agent Asynchronous Task & Claim-Check Vault Client for AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed microservices.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ./clients/python
|
|
9
|
+
# Or in editable mode:
|
|
10
|
+
pip install -e ./clients/python
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from roviers import RoviersBrokerClient
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
# 1. Connect to Roviers Broker
|
|
21
|
+
broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080")
|
|
22
|
+
|
|
23
|
+
# 2. Issue or load Agent Token
|
|
24
|
+
await broker.issue_agent_token(
|
|
25
|
+
agent_id="AGENT-RESEARCH-01",
|
|
26
|
+
agent_name="Autonomous Research Agent",
|
|
27
|
+
save_path="agent_token.json"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# 3. Submit heavy asynchronous request (returns in ~2ms with Receipt Token)
|
|
31
|
+
receipt = await broker.submit(
|
|
32
|
+
service_name="service.finance.risk_model",
|
|
33
|
+
payload={"portfolio_id": "port_9921", "simulations": 10000}
|
|
34
|
+
)
|
|
35
|
+
print(f"Receipt received: #{receipt.receipt_id} (Status: {receipt.status})")
|
|
36
|
+
|
|
37
|
+
# 4. Wait for real-time SSE signal and redeem outcome from Broker Vault
|
|
38
|
+
outcome = await broker.wait_for_outcome(receipt.receipt_id)
|
|
39
|
+
print("Vault Outcome:", outcome.result)
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## LangGraph / CrewAI Agent Tool Integration
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from roviers import RoviersBrokerClient, RoviersTool
|
|
49
|
+
|
|
50
|
+
broker = RoviersBrokerClient(base_url="http://127.0.0.1:8080", token="agent_token.json")
|
|
51
|
+
|
|
52
|
+
# Expose as a non-blocking agent tool
|
|
53
|
+
deep_research_tool = RoviersTool(
|
|
54
|
+
broker=broker,
|
|
55
|
+
service_name="service.ai.deep_research",
|
|
56
|
+
name="deep_research",
|
|
57
|
+
description="Runs deep multi-source research asynchronously without blocking agent loops"
|
|
58
|
+
)
|
|
59
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "roviers"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client SDK for Roviers: High-Throughput Service Intake & Claim-Check Vault"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Sunil K Sundaram" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.25.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
roviers = "roviers.cli:main"
|
|
21
|
+
roviers-server = "roviers.installer:run_server"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
agents = [
|
|
25
|
+
"langchain-core>=0.2.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Roviers Python SDK - Broker & Agent Asynchronous Task & Claim-Check Vault Engine.
|
|
3
|
+
Optimized for Autonomous AI Agents, LLM Orchestrators (LangGraph, CrewAI, AutoGen), and distributed workers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .client import RoviersBrokerClient, RoviersClient, RoviersReceipt, RoviersOutcome, RoviersTool
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"RoviersBrokerClient",
|
|
10
|
+
"RoviersClient",
|
|
11
|
+
"RoviersReceipt",
|
|
12
|
+
"RoviersOutcome",
|
|
13
|
+
"RoviersTool",
|
|
14
|
+
]
|
|
15
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Roviers Python CLI Entrypoints
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from .installer import install_roviers, run_server, get_binary_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
if len(sys.argv) > 1 and sys.argv[1] == "install":
|
|
11
|
+
version = sys.argv[2] if len(sys.argv) > 2 else "latest"
|
|
12
|
+
install_roviers(version=version)
|
|
13
|
+
elif len(sys.argv) > 1 and sys.argv[1] == "server":
|
|
14
|
+
run_server(sys.argv[2:])
|
|
15
|
+
elif len(sys.argv) > 1 and sys.argv[1] == "which":
|
|
16
|
+
path = get_binary_path("roviers-server")
|
|
17
|
+
print(f"roviers-server binary: {path} (exists: {path.is_file()})")
|
|
18
|
+
else:
|
|
19
|
+
print("Roviers CLI & Server Manager (Python)")
|
|
20
|
+
print("\nCommands:")
|
|
21
|
+
print(" roviers install [version] Download & install native binaries")
|
|
22
|
+
print(" roviers server [args...] Start native roviers-server")
|
|
23
|
+
print(" roviers which Display installed binary paths")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
main()
|
|
28
|
+
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Roviers Python Client Library
|
|
3
|
+
Broker & Agent Paradigm for Asynchronous Service Intake & Claim-Check Vault Engine
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, AsyncGenerator, Dict, Optional, Union
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class RoviersReceipt:
|
|
16
|
+
receipt_id: str
|
|
17
|
+
agent_id: str
|
|
18
|
+
service_name: str
|
|
19
|
+
issued_at: int
|
|
20
|
+
status: str
|
|
21
|
+
estimated_ready_secs: Optional[int] = None
|
|
22
|
+
callback_url: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_dict(cls, data: Dict[str, Any]) -> RoviersReceipt:
|
|
26
|
+
return cls(
|
|
27
|
+
receipt_id=str(data["receipt_id"]),
|
|
28
|
+
agent_id=data.get("agent_id") or data.get("citizen_id") or data.get("client_id", ""),
|
|
29
|
+
service_name=data["service_name"],
|
|
30
|
+
issued_at=data["issued_at"],
|
|
31
|
+
status=data["status"],
|
|
32
|
+
estimated_ready_secs=data.get("estimated_ready_secs"),
|
|
33
|
+
callback_url=data.get("callback_url"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class RoviersOutcome:
|
|
39
|
+
receipt_id: str
|
|
40
|
+
agent_id: str
|
|
41
|
+
service_name: str
|
|
42
|
+
completed_at: int
|
|
43
|
+
result: Any
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_dict(cls, data: Dict[str, Any]) -> RoviersOutcome:
|
|
47
|
+
return cls(
|
|
48
|
+
receipt_id=str(data["receipt_id"]),
|
|
49
|
+
agent_id=data.get("agent_id") or data.get("citizen_id") or data.get("client_id", ""),
|
|
50
|
+
service_name=data["service_name"],
|
|
51
|
+
completed_at=data["completed_at"],
|
|
52
|
+
result=data["result"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RoviersBrokerClient:
|
|
57
|
+
"""
|
|
58
|
+
Client for interacting with Roviers Broker ingress and Outcome Vault.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
base_url: str = "http://127.0.0.1:8080",
|
|
64
|
+
token: Optional[Union[Dict[str, Any], str]] = None,
|
|
65
|
+
timeout: float = 10.0,
|
|
66
|
+
):
|
|
67
|
+
self.base_url = base_url.rstrip("/")
|
|
68
|
+
self.timeout = timeout
|
|
69
|
+
self.token_data: Optional[Dict[str, Any]] = None
|
|
70
|
+
|
|
71
|
+
if isinstance(token, str):
|
|
72
|
+
with open(token, "r", encoding="utf-8") as f:
|
|
73
|
+
self.token_data = json.load(f)
|
|
74
|
+
elif isinstance(token, dict):
|
|
75
|
+
self.token_data = token
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def agent_id(self) -> str:
|
|
79
|
+
if not self.token_data:
|
|
80
|
+
raise ValueError("Agent token has not been loaded or configured.")
|
|
81
|
+
creds = self.token_data["credentials"]
|
|
82
|
+
return creds.get("agent_id") or creds.get("citizen_id") or creds.get("client_id", "")
|
|
83
|
+
|
|
84
|
+
async def issue_agent_token(
|
|
85
|
+
self,
|
|
86
|
+
agent_id: str,
|
|
87
|
+
agent_name: str = "Autonomous Agent",
|
|
88
|
+
validity_days: int = 365,
|
|
89
|
+
save_path: Optional[str] = None,
|
|
90
|
+
) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Requests an official Ed25519-signed agent credential from the Roviers Broker.
|
|
93
|
+
"""
|
|
94
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
95
|
+
resp = await client.post(
|
|
96
|
+
f"{self.base_url}/api/v1/auth/agent",
|
|
97
|
+
json={
|
|
98
|
+
"agent_id": agent_id,
|
|
99
|
+
"agent_name": agent_name,
|
|
100
|
+
"validity_days": validity_days,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
resp.raise_for_status()
|
|
104
|
+
token = resp.json()
|
|
105
|
+
|
|
106
|
+
self.token_data = token
|
|
107
|
+
if save_path:
|
|
108
|
+
with open(save_path, "w", encoding="utf-8") as f:
|
|
109
|
+
json.dump(token, f, indent=2)
|
|
110
|
+
|
|
111
|
+
return token
|
|
112
|
+
|
|
113
|
+
# Backwards compatibility alias
|
|
114
|
+
issue_token = issue_agent_token
|
|
115
|
+
|
|
116
|
+
async def submit(
|
|
117
|
+
self,
|
|
118
|
+
service_name: str,
|
|
119
|
+
payload: Dict[str, Any],
|
|
120
|
+
callback_url: Optional[str] = None,
|
|
121
|
+
) -> RoviersReceipt:
|
|
122
|
+
"""
|
|
123
|
+
Dispatches a service request to the Broker ingress.
|
|
124
|
+
Returns a Claim-Check Receipt immediately (HTTP 202 Accepted).
|
|
125
|
+
"""
|
|
126
|
+
if not self.token_data:
|
|
127
|
+
raise ValueError("Agent token must be provided before submitting requests.")
|
|
128
|
+
|
|
129
|
+
body = {
|
|
130
|
+
"service_name": service_name,
|
|
131
|
+
"agent_token": self.token_data,
|
|
132
|
+
"payload": payload,
|
|
133
|
+
"callback_url": callback_url,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
137
|
+
resp = await client.post(
|
|
138
|
+
f"{self.base_url}/api/v1/services/{service_name}",
|
|
139
|
+
json=body,
|
|
140
|
+
)
|
|
141
|
+
resp.raise_for_status()
|
|
142
|
+
return RoviersReceipt.from_dict(resp.json())
|
|
143
|
+
|
|
144
|
+
async def get_receipt(self, receipt_id: str) -> RoviersReceipt:
|
|
145
|
+
"""
|
|
146
|
+
Inspects the current state of a receipt (Queued, Processing, ReadyForPickup, Fulfilled).
|
|
147
|
+
"""
|
|
148
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
149
|
+
resp = await client.get(f"{self.base_url}/api/v1/receipts/{receipt_id}")
|
|
150
|
+
resp.raise_for_status()
|
|
151
|
+
return RoviersReceipt.from_dict(resp.json())
|
|
152
|
+
|
|
153
|
+
async def pickup(self, receipt_id: str) -> Optional[RoviersOutcome]:
|
|
154
|
+
"""
|
|
155
|
+
Presents the Receipt ID and cryptographic proof of agent identity to redeem the outcome from the Vault.
|
|
156
|
+
Returns None if outcome is still being processed (HTTP 202).
|
|
157
|
+
"""
|
|
158
|
+
if not self.token_data:
|
|
159
|
+
raise ValueError("Token must be loaded before picking up outcomes.")
|
|
160
|
+
|
|
161
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
162
|
+
resp = await client.post(
|
|
163
|
+
f"{self.base_url}/api/v1/receipts/{receipt_id}/pickup",
|
|
164
|
+
json={"agent_token": self.token_data},
|
|
165
|
+
)
|
|
166
|
+
if resp.status_code == 202:
|
|
167
|
+
return None
|
|
168
|
+
resp.raise_for_status()
|
|
169
|
+
return RoviersOutcome.from_dict(resp.json())
|
|
170
|
+
|
|
171
|
+
async def complete(self, receipt_id: str, result: Any) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Worker capability: deposits a finished execution outcome into the Vault.
|
|
174
|
+
"""
|
|
175
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
176
|
+
resp = await client.post(
|
|
177
|
+
f"{self.base_url}/api/v1/receipts/{receipt_id}/complete",
|
|
178
|
+
json={"result": result},
|
|
179
|
+
)
|
|
180
|
+
resp.raise_for_status()
|
|
181
|
+
|
|
182
|
+
async def listen_signals(
|
|
183
|
+
self,
|
|
184
|
+
timeout_secs: Optional[float] = None,
|
|
185
|
+
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
186
|
+
"""
|
|
187
|
+
Subscribes to the real-time Server-Sent Events (SSE) signal stream.
|
|
188
|
+
Yields completion signals the instant any task for this agent is ready.
|
|
189
|
+
"""
|
|
190
|
+
aid = self.agent_id
|
|
191
|
+
url = f"{self.base_url}/api/v1/agents/{aid}/stream"
|
|
192
|
+
|
|
193
|
+
timeout = httpx.Timeout(timeout_secs if timeout_secs else None, connect=10.0)
|
|
194
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
195
|
+
async with client.stream("GET", url) as stream:
|
|
196
|
+
async for line in stream.aiter_lines():
|
|
197
|
+
if line.startswith("data:"):
|
|
198
|
+
raw = line[5:].strip()
|
|
199
|
+
if raw:
|
|
200
|
+
yield json.loads(raw)
|
|
201
|
+
|
|
202
|
+
async def wait_for_outcome(
|
|
203
|
+
self,
|
|
204
|
+
receipt_id: str,
|
|
205
|
+
timeout_secs: float = 60.0,
|
|
206
|
+
) -> RoviersOutcome:
|
|
207
|
+
"""
|
|
208
|
+
High-level helper: waits for the specific receipt outcome using zero-polling SSE signals,
|
|
209
|
+
then automatically redeems and returns the outcome from the Broker Vault.
|
|
210
|
+
"""
|
|
211
|
+
# First check if it's already ready
|
|
212
|
+
immediate = await self.pickup(receipt_id)
|
|
213
|
+
if immediate is not None:
|
|
214
|
+
return immediate
|
|
215
|
+
|
|
216
|
+
loop = asyncio.get_running_loop()
|
|
217
|
+
end_time = loop.time() + timeout_secs
|
|
218
|
+
|
|
219
|
+
async for signal in self.listen_signals(timeout_secs=timeout_secs):
|
|
220
|
+
if signal.get("receipt_id") == receipt_id:
|
|
221
|
+
outcome = await self.pickup(receipt_id)
|
|
222
|
+
if outcome is not None:
|
|
223
|
+
return outcome
|
|
224
|
+
|
|
225
|
+
if loop.time() > end_time:
|
|
226
|
+
break
|
|
227
|
+
|
|
228
|
+
raise TimeoutError(f"Timed out waiting for receipt #{receipt_id} outcome.")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Backwards compatibility alias
|
|
232
|
+
RoviersClient = RoviersBrokerClient
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class RoviersTool:
|
|
236
|
+
"""
|
|
237
|
+
Adapter for AI Agent Frameworks (LangGraph, CrewAI, AutoGen).
|
|
238
|
+
Wraps heavy tasks into non-blocking claim-check operations.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, broker: RoviersBrokerClient, service_name: str, name: str, description: str):
|
|
242
|
+
self.broker = broker
|
|
243
|
+
self.service_name = service_name
|
|
244
|
+
self.name = name
|
|
245
|
+
self.description = description
|
|
246
|
+
|
|
247
|
+
async def run(self, **kwargs) -> Any:
|
|
248
|
+
receipt = await self.broker.submit(self.service_name, kwargs)
|
|
249
|
+
outcome = await self.broker.wait_for_outcome(receipt.receipt_id)
|
|
250
|
+
return outcome.result
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Roviers Native Binary Installer & Runner
|
|
3
|
+
Enables `pip install roviers` to download, manage, and launch native `roviers-server` and `roviers-cli` binaries.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.request
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
GITHUB_REPO = "sunilksundaram/roviers"
|
|
16
|
+
DEFAULT_INSTALL_DIR = Path.home() / ".roviers" / "bin"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_platform_asset_name() -> str:
|
|
20
|
+
system = platform.system().lower()
|
|
21
|
+
machine = platform.machine().lower()
|
|
22
|
+
|
|
23
|
+
if "windows" in system:
|
|
24
|
+
return "roviers-windows-x86_64.zip"
|
|
25
|
+
elif "darwin" in system:
|
|
26
|
+
if "arm" in machine or "aarch64" in machine:
|
|
27
|
+
return "roviers-macos-aarch64.tar.gz"
|
|
28
|
+
return "roviers-macos-x86_64.tar.gz"
|
|
29
|
+
elif "linux" in system:
|
|
30
|
+
if "arm" in machine or "aarch64" in machine:
|
|
31
|
+
return "roviers-linux-aarch64.tar.gz"
|
|
32
|
+
return "roviers-linux-x86_64.tar.gz"
|
|
33
|
+
else:
|
|
34
|
+
raise RuntimeError(f"Unsupported operating system: {system} {machine}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_binary_path(name: str = "roviers-server") -> Path:
|
|
38
|
+
ext = ".exe" if platform.system().lower() == "windows" else ""
|
|
39
|
+
binary_name = f"{name}{ext}"
|
|
40
|
+
|
|
41
|
+
# 1. Check system PATH
|
|
42
|
+
which_path = shutil.which(binary_name)
|
|
43
|
+
if which_path:
|
|
44
|
+
return Path(which_path)
|
|
45
|
+
|
|
46
|
+
# 2. Check ~/.roviers/bin
|
|
47
|
+
local_path = DEFAULT_INSTALL_DIR / binary_name
|
|
48
|
+
if local_path.is_file() and os.access(local_path, os.X_OK):
|
|
49
|
+
return local_path
|
|
50
|
+
|
|
51
|
+
# 3. Check workspace target/release (if local repo build)
|
|
52
|
+
target_path = Path.cwd() / "target" / "release" / binary_name
|
|
53
|
+
if target_path.is_file():
|
|
54
|
+
return target_path
|
|
55
|
+
|
|
56
|
+
return local_path
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def install_roviers(version: str = "latest", dest_dir: Path = DEFAULT_INSTALL_DIR) -> Path:
|
|
60
|
+
"""
|
|
61
|
+
Installs the native roviers-server and roviers-cli binaries into ~/.roviers/bin
|
|
62
|
+
"""
|
|
63
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
asset_name = get_platform_asset_name()
|
|
65
|
+
|
|
66
|
+
if version == "latest":
|
|
67
|
+
url = f"https://github.com/{GITHUB_REPO}/releases/latest/download/{asset_name}"
|
|
68
|
+
else:
|
|
69
|
+
url = f"https://github.com/{GITHUB_REPO}/releases/download/{version}/{asset_name}"
|
|
70
|
+
|
|
71
|
+
archive_path = dest_dir / asset_name
|
|
72
|
+
print(f"📦 Downloading Roviers native binaries from {url}...")
|
|
73
|
+
try:
|
|
74
|
+
urllib.request.urlretrieve(url, archive_path)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"⚠️ Could not download pre-built binary: {e}")
|
|
77
|
+
# Try cargo install as fallback
|
|
78
|
+
if shutil.which("cargo"):
|
|
79
|
+
print("🔨 Found cargo! Building and installing via cargo...")
|
|
80
|
+
subprocess.check_call(["cargo", "install", "--git", f"https://github.com/{GITHUB_REPO}.git", "roviers-server", "roviers-cli"])
|
|
81
|
+
print("✅ Successfully built and installed via cargo!")
|
|
82
|
+
return get_binary_path("roviers-server")
|
|
83
|
+
raise RuntimeError(f"Failed to install native binaries: {e}")
|
|
84
|
+
|
|
85
|
+
# Extract archive
|
|
86
|
+
print(f"📂 Extracting {archive_path.name} to {dest_dir}...")
|
|
87
|
+
shutil.unpack_archive(archive_path, dest_dir)
|
|
88
|
+
archive_path.unlink(missing_ok=True)
|
|
89
|
+
|
|
90
|
+
# Ensure executable permissions on Unix
|
|
91
|
+
for binary_name in ["roviers-server", "roviers-cli"]:
|
|
92
|
+
p = dest_dir / binary_name
|
|
93
|
+
if p.exists():
|
|
94
|
+
p.chmod(0o755)
|
|
95
|
+
|
|
96
|
+
print(f"✅ Roviers successfully installed to {dest_dir}")
|
|
97
|
+
print(f"💡 Add {dest_dir} to your PATH to run `roviers-server` directly.")
|
|
98
|
+
return get_binary_path("roviers-server")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def run_server(args: list[str] | None = None) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Launches the native `roviers-server` binary, installing it first if missing.
|
|
104
|
+
"""
|
|
105
|
+
binary = get_binary_path("roviers-server")
|
|
106
|
+
if not binary.is_file():
|
|
107
|
+
print("🔍 roviers-server binary not found locally.")
|
|
108
|
+
binary = install_roviers()
|
|
109
|
+
|
|
110
|
+
cmd = [str(binary)] + (args or sys.argv[1:])
|
|
111
|
+
try:
|
|
112
|
+
subprocess.run(cmd)
|
|
113
|
+
except KeyboardInterrupt:
|
|
114
|
+
pass
|
|
115
|
+
|