aris-sdk 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.
aris_sdk-0.1.0/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Siddhant Porwal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files...
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: aris-sdk
3
+ Version: 0.1.0
4
+ Summary: Decentralized AI Network SDK and Node Infrastructure
5
+ Author: Sid
6
+ Requires-Python: >=3.8
7
+ License-File: LICENSE
8
+ Requires-Dist: requests
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: python-jose[cryptography]
11
+ Requires-Dist: uvicorn
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pyjwt
15
+ Requires-Dist: python-dotenv
16
+ Dynamic: author
17
+ Dynamic: license-file
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
20
+ Dynamic: summary
@@ -0,0 +1,110 @@
1
+ # Aris SDK šŸš€
2
+ ### The Decentralized AI Network Layer
3
+
4
+ Aris is a lightweight, decentralized infrastructure for local LLM inference. It allows users to contribute compute power as "Worker Nodes" and developers to consume that AI compute via a unified SDK with built-in micro-payments and registry management.
5
+
6
+ ---
7
+
8
+ ## šŸ›  Features
9
+ - **Decentralized Registry**: On-chain style ledger for managing API keys and balances.
10
+ - **Worker Nodes**: Easily turn any machine with a local LLM into a revenue-generating node.
11
+ - **Standardized SDK**: OpenAI-compatible interface for easy integration.
12
+ - **Transparent Billing**: Built-in balance deduction per inference request.
13
+
14
+ ---
15
+
16
+ ## šŸ“¦ Installation
17
+
18
+ Install the core SDK via pip:
19
+
20
+ ```bash
21
+ pip install aris-sdk
22
+
23
+ ```
24
+
25
+ ---
26
+
27
+ ## šŸš€ Quick Start (Client Side)
28
+
29
+ To use the network as a developer, simply initialize the `Aris` client with your API key.
30
+
31
+ ```python
32
+ from aris.client import Aris
33
+
34
+ # Initialize with your minted key
35
+ client = Aris(api_key="sk-aris-your-unique-key")
36
+
37
+ # Generate a response from the decentralized swarm
38
+ response = client.generate("Why is the ocean salty?")
39
+
40
+ print(f"šŸ¤– ARIS: {response}")
41
+
42
+ ```
43
+
44
+ ---
45
+
46
+ ## šŸ— Infrastructure Setup
47
+
48
+ ### 1. Start the Registry
49
+
50
+ The Registry acts as the "Bank" and "DNS" of the network.
51
+
52
+ ```bash
53
+ # Run the registry server (Default port: 8000)
54
+ python3 -m registry.main
55
+
56
+ ```
57
+
58
+ ### 2. Join as a Worker Node
59
+
60
+ Nodes register themselves with the Registry and wait for inference jobs.
61
+
62
+ ```bash
63
+ # Start a node on port 9006
64
+ aris-node --port 9006
65
+
66
+ ```
67
+
68
+ ### 3. Admin: Minting New Keys
69
+
70
+ Manage your network users using the admin CLI.
71
+
72
+ ```bash
73
+ # Create a new user with a 500 credit balance
74
+ aris-admin --email user@example.com --balance 500.0
75
+
76
+ ```
77
+
78
+ ---
79
+
80
+ ## šŸ“‚ Project Structure
81
+
82
+ * `/aris`: The core Client SDK logic.
83
+ * `/agent_node`: Worker node infrastructure and LLM integration (TinyLlama).
84
+ * `/registry`: Centralized ledger for account and node management.
85
+
86
+ ---
87
+
88
+ ## šŸ“œ License
89
+
90
+ Distributed under the MIT License. See `LICENSE` for more information.
91
+
92
+ ---
93
+
94
+ ## šŸ¤ Contributing
95
+
96
+ Contributions are welcome! Please open an issue or submit a pull request.
97
+
98
+ **Built with ā¤ļø for the Decentralized AI Community.**
99
+
100
+ ```
101
+
102
+ ---
103
+
104
+ ### **Final Pro-Tip for the Launch**
105
+ Before you run `python3 -m twine upload dist/*`, make sure your `LICENSE` file exists. You can create a quick one like this:
106
+
107
+ ```bash
108
+ echo "Copyright (c) 2026 Sid - MIT License" > LICENSE
109
+
110
+ ```
File without changes
@@ -0,0 +1,70 @@
1
+ import uvicorn
2
+ import jwt
3
+ import os
4
+ import httpx
5
+ import asyncio
6
+ import argparse
7
+ from fastapi import FastAPI, HTTPException, Header
8
+ from pydantic import BaseModel
9
+
10
+ app = FastAPI(title="Aris Node: LLM Specialist")
11
+
12
+ # --- CONFIG ---
13
+ REGISTRY_URL = os.getenv("ARIS_REGISTRY", "http://localhost:8000/register")
14
+ MY_DID = "did:aris:llm-node-01"
15
+ MY_ENDPOINT = "http://localhost:9006"
16
+ ARIS_PUBLIC_KEY = "s3cret_k3y_for_signing_tokens"
17
+ OLLAMA_URL = "http://localhost:11434/api/generate"
18
+
19
+ # --- AUTO-REGISTER ON STARTUP ---
20
+ @app.on_event("startup")
21
+ async def start_heartbeat():
22
+ async def heartbeat():
23
+ while True:
24
+ async with httpx.AsyncClient() as client:
25
+ try:
26
+ await client.post(REGISTRY_URL, json={
27
+ "did": MY_DID,
28
+ "endpoint": MY_ENDPOINT,
29
+ "capabilities": ["ai.generate"]
30
+ })
31
+ print("šŸ’“ Heartbeat: Registry updated.")
32
+ except Exception:
33
+ print("āš ļø Heartbeat: Registry unreachable...")
34
+ await asyncio.sleep(30) # Check in every 30 seconds
35
+
36
+ asyncio.create_task(heartbeat())
37
+
38
+ # --- MODELS & ENDPOINTS ---
39
+ class PromptRequest(BaseModel):
40
+ model: str = "tinyllama"
41
+ prompt: str
42
+
43
+ @app.post("/generate")
44
+ async def generate_text(job: PromptRequest, x_aris_token: str = Header(...)):
45
+ try:
46
+ # Verify the session token issued by the Registry
47
+ payload = jwt.decode(x_aris_token, ARIS_PUBLIC_KEY, algorithms=["HS256"], audience=MY_DID)
48
+ except jwt.InvalidTokenError:
49
+ raise HTTPException(401, "Invalid or Expired Token")
50
+
51
+ print(f"🧠 [LLM Node] Processing for {payload['sub']}...")
52
+
53
+ async with httpx.AsyncClient() as client:
54
+ resp = await client.post(OLLAMA_URL, json={
55
+ "model": job.model, "prompt": job.prompt, "stream": False
56
+ }, timeout=60.0)
57
+ return {"result": resp.json().get("response", ""), "status": "success"}
58
+
59
+ def main():
60
+ parser = argparse.ArgumentParser(description="Aris Worker Node")
61
+ parser.add_argument("--port", type=int, default=9006, help="Port to run the agent on")
62
+ parser.add_argument("--model", type=str, default="tinyllama", help="Ollama model to use")
63
+ args = parser.parse_args()
64
+
65
+ print(f"šŸš€ Starting Aris Node on port {args.port} using {args.model}...")
66
+ uvicorn.run(app, host="0.0.0.0", port=args.port)
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
@@ -0,0 +1,52 @@
1
+ import sys
2
+ import argparse
3
+ import uvicorn
4
+ import base64
5
+ import hmac
6
+ import hashlib
7
+ import time
8
+ import ollama
9
+ from fastapi import FastAPI, HTTPException
10
+ from pydantic import BaseModel
11
+
12
+ # --- 1. SETUP DYNAMIC ARGS ---
13
+ parser = argparse.ArgumentParser()
14
+ parser.add_argument("--port", type=int, default=9001)
15
+ parser.add_argument("--name", type=str, default="Unknown Agent")
16
+ args, _ = parser.parse_known_args()
17
+
18
+ app = FastAPI(title=f"Aris Node: {args.name}")
19
+
20
+ # --- CONFIG ---
21
+ ARIS_SECRET_KEY = b"super_secret_platform_key_2025"
22
+ MODEL_NAME = "tinyllama"
23
+
24
+ class JobRequest(BaseModel):
25
+ prompt: str
26
+ session_token: str
27
+
28
+ @app.post("/process_job")
29
+ async def process_job(job: JobRequest):
30
+ # AUTH (Same as before)
31
+ try:
32
+ if "." not in job.session_token: raise ValueError("Invalid token")
33
+ payload_b64, signature = job.session_token.split(".")
34
+ expected_sig = hmac.new(ARIS_SECRET_KEY, base64.b64decode(payload_b64), hashlib.sha256).hexdigest()
35
+ if not hmac.compare_digest(signature, expected_sig): raise HTTPException(401, "FAKE TOKEN")
36
+ except Exception as e:
37
+ raise HTTPException(401, str(e))
38
+
39
+ # INTELLIGENCE
40
+ print(f"[{args.name}] Thinking...")
41
+ try:
42
+ response = ollama.chat(model=MODEL_NAME, messages=[{'role': 'user', 'content': job.prompt}])
43
+ return {
44
+ "node": args.name,
45
+ "response": response['message']['content']
46
+ }
47
+ except Exception as e:
48
+ raise HTTPException(500, str(e))
49
+
50
+ if __name__ == "__main__":
51
+ # This allows us to run: python main.py --port 9002
52
+ uvicorn.run(app, host="0.0.0.0", port=args.port)
@@ -0,0 +1,44 @@
1
+ import uvicorn
2
+ import jwt
3
+ from fastapi import FastAPI, HTTPException, Header
4
+ from pydantic import BaseModel
5
+
6
+ app = FastAPI(title="Aris Node: Math Specialist")
7
+
8
+ # The Agent needs the Registry's Public Key (or shared secret for HS256) to verify tokens
9
+ ARIS_PUBLIC_KEY = "s3cret_k3y_for_signing_tokens"
10
+
11
+ class JobRequest(BaseModel):
12
+ a: int
13
+ b: int
14
+ operation: str # "add"
15
+
16
+ @app.post("/execute")
17
+ async def execute_task(job: JobRequest, x_aris_token: str = Header(...)):
18
+ """
19
+ Section 3C: Agent verifies the token before working.
20
+ """
21
+ try:
22
+ # 1. Verify the signature (Did Aris sign this?)
23
+ payload = jwt.decode(x_aris_token, ARIS_PUBLIC_KEY, algorithms=["HS256"], audience="did:aris:math-node-01")
24
+
25
+ # 2. Check Scope
26
+ if "math.add" not in payload.get("scope", ""):
27
+ raise HTTPException(403, "Token not valid for math.add")
28
+
29
+ print(f"šŸ¤– [Agent] Token Verified! Processing job for {payload['sub']}")
30
+
31
+ except jwt.ExpiredSignatureError:
32
+ raise HTTPException(401, "Token Expired")
33
+ except jwt.InvalidTokenError as e:
34
+ raise HTTPException(401, f"Invalid Token: {str(e)}")
35
+
36
+ # 3. Do the Work (The Capability)
37
+ if job.operation == "add":
38
+ return {"result": job.a + job.b, "status": "success"}
39
+
40
+ return {"error": "Unsupported operation"}
41
+
42
+ if __name__ == "__main__":
43
+ # Runs on Port 9005 (Math Node)
44
+ uvicorn.run(app, host="0.0.0.0", port=9005)
@@ -0,0 +1,4 @@
1
+ # aris/__init__.py
2
+ from .client import Aris, generate
3
+
4
+ __version__ = "0.2.0"
@@ -0,0 +1,137 @@
1
+ import os
2
+ import requests
3
+ import logging
4
+ from typing import Optional, Dict, Any
5
+
6
+ # Configure library logging (NullHandler by default so we don't spam unless configured)
7
+ logger = logging.getLogger("aris")
8
+ logger.addHandler(logging.NullHandler())
9
+
10
+ # --- Custom Exceptions (The Professional Way) ---
11
+ class ArisError(Exception):
12
+ """Base exception for all Aris SDK errors."""
13
+ pass
14
+
15
+ class ArisPaymentError(ArisError):
16
+ """Raised when payment fails or balance is insufficient."""
17
+ pass
18
+
19
+ class ArisNodeError(ArisError):
20
+ """Raised when the worker node fails to respond."""
21
+ pass
22
+
23
+ # --- The Main Client ---
24
+ class Aris:
25
+ def __init__(self, api_key: Optional[str] = None, registry_url: Optional[str] = None):
26
+ """
27
+ Initialize the Aris Client.
28
+
29
+ Args:
30
+ api_key: Your Aris API Key (starts with sk-). Defaults to ARIS_API_KEY env var.
31
+ registry_url: URL of the Aris Registry. Defaults to ARIS_REGISTRY_URL env var or localhost.
32
+ """
33
+ self.api_key = api_key or os.getenv("ARIS_API_KEY")
34
+ if not self.api_key:
35
+ raise ArisError("āŒ Missing API Key. Pass it to Aris() or set ARIS_API_KEY.")
36
+
37
+ self.registry_url = registry_url or os.getenv("ARIS_REGISTRY_URL", "http://localhost:8000")
38
+ self.session_token: Optional[str] = None
39
+ self.target_endpoint: Optional[str] = None
40
+
41
+ def generate(self, prompt: str, model: str = "tinyllama") -> str:
42
+ """
43
+ Generate text using the decentralized Aris network.
44
+
45
+ Args:
46
+ prompt: The text prompt to send.
47
+ model: The model to use (default: tinyllama).
48
+
49
+ Returns:
50
+ The generated text string.
51
+ """
52
+ # Auto-connect if not connected
53
+ if not self.session_token:
54
+ self._connect_to_swarm()
55
+
56
+ try:
57
+ return self._execute_request(prompt, model)
58
+ except ArisError as e:
59
+ # If token expired, try one refresh
60
+ logger.warning(f"āš ļø Request failed ({e}). Attempting to refresh session...")
61
+ self.session_token = None
62
+ self._connect_to_swarm()
63
+ return self._execute_request(prompt, model)
64
+
65
+ def _connect_to_swarm(self):
66
+ """Handles Discovery (finding nodes) and Handshake (payment)."""
67
+ logger.info("šŸ”Ž Discovering best available node...")
68
+
69
+ try:
70
+ # 1. Discover
71
+ resp = requests.get(f"{self.registry_url}/discover", params={"capability": "ai.generate"}, timeout=5)
72
+ resp.raise_for_status()
73
+ data = resp.json()
74
+
75
+ if not data.get("agents"):
76
+ raise ArisNodeError("No active worker nodes found in the network.")
77
+
78
+ # Simple routing: Pick the first one (Future: pick based on latency/price)
79
+ target = data["agents"][0]
80
+ self.target_endpoint = target["endpoint"]
81
+ target_did = target["did"]
82
+
83
+ # 2. Handshake (The Transaction)
84
+ logger.info(f"šŸ’³ Negotiating contract with {target_did}...")
85
+
86
+ pay_resp = requests.post(
87
+ f"{self.registry_url}/handshake",
88
+ json={
89
+ "payer_did": "did:aris:customer-sdk", # In future, derive this from key
90
+ "target_did": target_did,
91
+ "capability": "ai.generate"
92
+ },
93
+ headers={"x-api-key": self.api_key},
94
+ timeout=10
95
+ )
96
+
97
+ if pay_resp.status_code == 402:
98
+ raise ArisPaymentError("Insufficient Balance. Please top up your Aris account.")
99
+ elif pay_resp.status_code != 200:
100
+ raise ArisError(f"Handshake failed: {pay_resp.text}")
101
+
102
+ session_data = pay_resp.json()
103
+ self.session_token = session_data["session_token"]
104
+
105
+ logger.info(f"āœ… Session Established. Balance remaining: ₹{session_data.get('remaining_balance', '???')}")
106
+
107
+ except requests.RequestException as e:
108
+ raise ArisError(f"Network error connecting to Registry: {e}")
109
+
110
+ def _execute_request(self, prompt: str, model: str) -> str:
111
+ """Direct Peer-to-Peer execution with the Worker Node."""
112
+ if not self.target_endpoint:
113
+ raise ArisError("No target endpoint configured.")
114
+
115
+ try:
116
+ response = requests.post(
117
+ f"{self.target_endpoint}/generate",
118
+ json={"model": model, "prompt": prompt},
119
+ headers={"x-aris-token": self.session_token},
120
+ timeout=60 # AI can be slow
121
+ )
122
+
123
+ if response.status_code == 200:
124
+ return response.json().get("result", "")
125
+ elif response.status_code in [401, 403]:
126
+ raise ArisError("Session Token Expired or Invalid")
127
+ else:
128
+ raise ArisNodeError(f"Worker Node Error: {response.text}")
129
+
130
+ except requests.RequestException as e:
131
+ raise ArisNodeError(f"Failed to communicate with Worker Node: {e}")
132
+
133
+ # --- Helper for quick scripts ---
134
+ def generate(prompt: str, api_key: Optional[str] = None) -> str:
135
+ """Quick helper function for one-off generations."""
136
+ client = Aris(api_key=api_key)
137
+ return client.generate(prompt)
File without changes
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: aris-sdk
3
+ Version: 0.1.0
4
+ Summary: Decentralized AI Network SDK and Node Infrastructure
5
+ Author: Sid
6
+ Requires-Python: >=3.8
7
+ License-File: LICENSE
8
+ Requires-Dist: requests
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: python-jose[cryptography]
11
+ Requires-Dist: uvicorn
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pyjwt
15
+ Requires-Dist: python-dotenv
16
+ Dynamic: author
17
+ Dynamic: license-file
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
20
+ Dynamic: summary
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ agent_node/__init__.py
5
+ agent_node/llm_agent.py
6
+ agent_node/main.py
7
+ agent_node/math_agent.py
8
+ aris/__init__.py
9
+ aris/client.py
10
+ aris/types.py
11
+ aris_sdk.egg-info/PKG-INFO
12
+ aris_sdk.egg-info/SOURCES.txt
13
+ aris_sdk.egg-info/dependency_links.txt
14
+ aris_sdk.egg-info/entry_points.txt
15
+ aris_sdk.egg-info/requires.txt
16
+ aris_sdk.egg-info/top_level.txt
17
+ registry/__init__.py
18
+ registry/main.py
19
+ registry/mint_key.py
20
+ registry/models.py
21
+ registry/search.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ aris-admin = registry.mint_key:main
3
+ aris-node = agent_node.llm_agent:main
@@ -0,0 +1,8 @@
1
+ requests
2
+ pydantic
3
+ python-jose[cryptography]
4
+ uvicorn
5
+ fastapi
6
+ httpx
7
+ pyjwt
8
+ python-dotenv
@@ -0,0 +1,3 @@
1
+ agent_node
2
+ aris
3
+ registry
File without changes
@@ -0,0 +1,155 @@
1
+ import time
2
+ import json
3
+ import jwt # pip install pyjwt
4
+ from fastapi import FastAPI, HTTPException, Header
5
+ from pydantic import BaseModel
6
+ from typing import List, Dict, Optional
7
+
8
+ # --- CONFIG ---
9
+ ARIS_PRIVATE_KEY = "s3cret_k3y_for_signing_tokens"
10
+ MVP_PRICE_INR = 20.00
11
+ LEDGER_FILE = "registry/accounts.json"
12
+
13
+ app = FastAPI(title="Aris Registry (Paid)", version="0.2")
14
+
15
+ # --- DATA STORES ---
16
+ service_registry: Dict[str, List[dict]] = {}
17
+ ledger: Dict[str, dict] = {}
18
+
19
+ # --- HELPER FUNCTIONS ---
20
+ def load_accounts():
21
+ try:
22
+ with open(LEDGER_FILE, "r") as f:
23
+ return json.load(f)
24
+ except FileNotFoundError:
25
+ return {}
26
+
27
+ def save_accounts(data):
28
+ with open(LEDGER_FILE, "w") as f:
29
+ json.dump(data, f, indent=4)
30
+
31
+ def validate_api_key(api_key: str):
32
+ # Load your ledger
33
+ with open("registry/accounts.json", "r") as f:
34
+ accounts = json.load(f)
35
+
36
+ # Check 1: Does key exist?
37
+ if api_key not in accounts:
38
+ raise HTTPException(status_code=401, detail="Invalid API Key")
39
+
40
+ # Check 2: Is the account enabled?
41
+ user = accounts[api_key]
42
+ if not user.get("active", False):
43
+ raise HTTPException(status_code=403, detail="Account suspended")
44
+
45
+ # Check 3: Does it have money?
46
+ if user.get("balance", 0) <= 0:
47
+ raise HTTPException(status_code=402, detail="Insufficient funds")
48
+
49
+ return user
50
+
51
+ # --- MODELS ---
52
+ class AgentRegistration(BaseModel):
53
+ did: str
54
+ endpoint: str
55
+ capabilities: List[str]
56
+
57
+ class SessionRequest(BaseModel):
58
+ payer_did: str
59
+ target_did: str
60
+ capability: str
61
+
62
+ # --- ENDPOINTS ---
63
+
64
+ @app.post("/register")
65
+ async def register_agent(agent: AgentRegistration):
66
+ for cap in agent.capabilities:
67
+ if cap not in service_registry:
68
+ service_registry[cap] = []
69
+ # Update existing agent or add new
70
+ service_registry[cap] = [a for a in service_registry[cap] if a['did'] != agent.did]
71
+ service_registry[cap].append(agent.dict())
72
+
73
+ print(f"āœ… [Registry] Registered {agent.did}")
74
+ return {"status": "registered"}
75
+
76
+ @app.get("/discover")
77
+ async def discover(capability: str):
78
+ agents = service_registry.get(capability, [])
79
+ if not agents:
80
+ raise HTTPException(404, "No agents found")
81
+ return {"agents": agents}
82
+
83
+ @app.post("/handshake")
84
+ async def handshake(req: SessionRequest, x_api_key: Optional[str] = Header(None)):
85
+ """
86
+ šŸ’° PAYMENT LOGIC:
87
+ 1. Check Key
88
+ 2. Check Balance
89
+ 3. Deduct Fee
90
+ 4. Issue Token
91
+ """
92
+ # 1. AUTH
93
+ if not x_api_key:
94
+ raise HTTPException(401, "Missing API Key")
95
+
96
+ accounts = load_accounts()
97
+ if x_api_key not in accounts:
98
+ raise HTTPException(403, "Invalid API Key")
99
+
100
+ user_account = accounts[x_api_key]
101
+
102
+ if not user_account["active"]:
103
+ raise HTTPException(403, "Account Suspended")
104
+
105
+ # 2. CHECK BALANCE
106
+ if user_account["balance"] < MVP_PRICE_INR:
107
+ raise HTTPException(402, "Insufficient Balance. Please Top Up.")
108
+
109
+ # 3. CHARGE
110
+ user_account["balance"] -= MVP_PRICE_INR
111
+ save_accounts(accounts) # Save to disk
112
+
113
+ print(f"šŸ’° [Billing] Charged {x_api_key[:10]}... | New Balance: ₹{user_account['balance']}")
114
+
115
+ # 4. ISSUE TOKEN
116
+ txn_id = f"txn_{int(time.time())}"
117
+ payload = {
118
+ "iss": "aris-registry",
119
+ "sub": req.payer_did,
120
+ "aud": req.target_did,
121
+ "scope": req.capability,
122
+ "txn_id": txn_id,
123
+ "exp": time.time() + 300 # 5 mins
124
+ }
125
+
126
+ token = jwt.encode(payload, ARIS_PRIVATE_KEY, algorithm="HS256")
127
+
128
+ return {
129
+ "session_token": token,
130
+ "txn_id": txn_id,
131
+ "remaining_balance": user_account["balance"]
132
+ }
133
+
134
+ class TopUpRequest(BaseModel):
135
+ api_key: str
136
+ amount: float
137
+
138
+ @app.post("/admin/topup")
139
+ async def top_up(req: TopUpRequest):
140
+ accounts = load_accounts()
141
+ if req.api_key not in accounts:
142
+ raise HTTPException(404, "Key not found")
143
+
144
+ accounts[req.api_key]["balance"] += req.amount
145
+ save_accounts(accounts)
146
+
147
+ return {
148
+ "status": "success",
149
+ "new_balance": accounts[req.api_key]["balance"]
150
+ }
151
+
152
+ if __name__ == "__main__":
153
+ import uvicorn
154
+ # This actually starts the server
155
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1,53 @@
1
+ import secrets
2
+ import json
3
+ import os
4
+ import argparse
5
+ from datetime import datetime
6
+
7
+ ACCOUNTS_FILE = "registry/accounts.json"
8
+
9
+ def mint_key(email: str, balance: float = 100.0):
10
+ """Business logic to create the key and update the ledger."""
11
+ new_key = f"sk-aris-{secrets.token_hex(16)}"
12
+
13
+ # Ensure directory exists
14
+ os.makedirs(os.path.dirname(ACCOUNTS_FILE), exist_ok=True)
15
+
16
+ if os.path.exists(ACCOUNTS_FILE):
17
+ with open(ACCOUNTS_FILE, "r") as f:
18
+ accounts = json.load(f)
19
+ else:
20
+ accounts = {}
21
+
22
+ accounts[new_key] = {
23
+ "user_email": email,
24
+ "balance": balance,
25
+ "active": True,
26
+ "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
27
+ }
28
+
29
+ with open(ACCOUNTS_FILE, "w") as f:
30
+ json.dump(accounts, f, indent=4)
31
+
32
+ print(f"\n✨ Aris Key Minted Successfully!")
33
+ print(f"šŸ“§ User: {email}")
34
+ print(f"šŸ’° Balance: ₹{balance}")
35
+ print(f"šŸ”‘ Key: {new_key}")
36
+ print(f"--------------------------------------------------")
37
+ return new_key
38
+
39
+ def main():
40
+ """CLI Entrypoint for the aris-admin command."""
41
+ parser = argparse.ArgumentParser(description="Aris Network Admin Tools")
42
+ parser.add_argument("--email", help="Email of the user")
43
+ parser.add_argument("--balance", type=float, default=100.0, help="Starting balance")
44
+ args = parser.parse_args()
45
+
46
+ # If user didn't provide an email via flags, ask interactively
47
+ email = args.email or input("Enter user email: ")
48
+ balance = args.balance if args.email else (input("Enter starting balance (default 100.0): ") or 100.0)
49
+
50
+ mint_key(email, float(balance))
51
+
52
+ if __name__ == "__main__":
53
+ main()
File without changes
@@ -0,0 +1,60 @@
1
+ import redis.asyncio as redis
2
+ from typing import List, Optional
3
+
4
+ # Connect to Redis
5
+ redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
6
+
7
+ class SearchEngine:
8
+ """
9
+ The 'Google' of Aris.
10
+ Uses Inverted Indices in Redis to find agents by capability instantly.
11
+ """
12
+
13
+ @staticmethod
14
+ async def index_agent(did: str, capabilities: List[str]):
15
+ """
16
+ Updates the Inverted Index.
17
+ If Agent A has 'video', we add Agent A to the 'idx:video' set.
18
+ """
19
+ async with redis_client.pipeline() as pipe:
20
+ # 1. Update the 'Last Seen' timestamp
21
+ pipe.hset(f"agent:{did}", mapping={"last_seen": "now"})
22
+
23
+ # 2. Index each capability
24
+ for cap in capabilities:
25
+ # Key: idx:media.video -> Set{did:agent:1, did:agent:2}
26
+ pipe.sadd(f"idx:{cap.lower()}", did)
27
+
28
+ await pipe.execute()
29
+
30
+ @staticmethod
31
+ async def search(capability: str, limit: int = 10) -> List[dict]:
32
+ """
33
+ Finds agents that match the capability.
34
+ Returns full agent details.
35
+ """
36
+ # 1. Get all DIDs from the index (0ms latency)
37
+ dids = await redis_client.smembers(f"idx:{capability.lower()}")
38
+
39
+ if not dids:
40
+ return []
41
+
42
+ # 2. Fetch details for all found agents in parallel
43
+ results = []
44
+ for did in list(dids)[:limit]:
45
+ # In a real app, use mget for even more speed
46
+ data = await redis_client.hgetall(f"agent:{did}")
47
+ if data:
48
+ # Add the DID to the response object since it's the key
49
+ data['did'] = did
50
+ results.append(data)
51
+
52
+ return results
53
+
54
+ @staticmethod
55
+ async def remove_agent(did: str, capabilities: List[str]):
56
+ """
57
+ Removes an agent from the index (e.g., if they go offline).
58
+ """
59
+ for cap in capabilities:
60
+ await redis_client.srem(f"idx:{cap.lower()}", did)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="aris-sdk",
5
+ version="0.1.0",
6
+ description="Decentralized AI Network SDK and Node Infrastructure",
7
+ author="Sid",
8
+ packages=find_packages(include=[
9
+ "aris", "aris.*",
10
+ "agent_node", "agent_node.*",
11
+ "registry", "registry.*"
12
+ ]),
13
+ install_requires=[
14
+ "requests",
15
+ "pydantic",
16
+ "python-jose[cryptography]",
17
+ "uvicorn",
18
+ "fastapi",
19
+ "httpx",
20
+ "pyjwt",
21
+ "python-dotenv"
22
+ ],
23
+ entry_points={
24
+ "console_scripts": [
25
+ "aris-node=agent_node.llm_agent:main",
26
+ "aris-admin=registry.mint_key:main",
27
+ ],
28
+ },
29
+ python_requires=">=3.8",
30
+ )