deploy: fix TemplateResponse signature
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# Init app module
|
||||
Binary file not shown.
Binary file not shown.
+42
@@ -0,0 +1,42 @@
|
||||
import os
|
||||
import jwt
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import Request, HTTPException, Security, Depends
|
||||
from fastapi.security.api_key import APIKeyHeader
|
||||
from app.models import get_agent_by_key, update_key_last_used
|
||||
|
||||
JWT_SECRET = os.environ.get("JWT_SECRET", "secret")
|
||||
JWT_EXPIRE_HOURS = int(os.environ.get("JWT_EXPIRE_HOURS", "12"))
|
||||
|
||||
API_KEY_NAME = "X-API-Key"
|
||||
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
||||
|
||||
def create_jwt_token(data: dict) -> str:
|
||||
to_encode = data.copy()
|
||||
expire = datetime.now(timezone.utc) + timedelta(hours=JWT_EXPIRE_HOURS)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, JWT_SECRET, algorithm="HS256")
|
||||
return encoded_jwt
|
||||
|
||||
def verify_jwt_token(request: Request) -> dict:
|
||||
token = request.cookies.get("access_token")
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
try:
|
||||
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=401, detail="Token expired")
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
async def get_current_agent(api_key: str = Security(api_key_header)) -> str:
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=401, detail="API Key header missing")
|
||||
|
||||
agent_name = await get_agent_by_key(api_key)
|
||||
if not agent_name:
|
||||
raise HTTPException(status_code=401, detail="Invalid API Key")
|
||||
|
||||
await update_key_last_used(api_key)
|
||||
return agent_name
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.routes import api, admin, mcp
|
||||
from app.models import init_db
|
||||
|
||||
app = FastAPI(title="context-hub", version="1.0.0")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
app.include_router(api.router)
|
||||
app.include_router(admin.router)
|
||||
app.include_router(mcp.router)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def on_startup():
|
||||
await init_db()
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "ok"}
|
||||
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return RedirectResponse(url="/login")
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import aiosqlite
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
DB_PATH = "data/context_hub.db"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def init_db():
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
scope TEXT UNIQUE NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agent_name TEXT UNIQUE NOT NULL,
|
||||
api_key TEXT UNIQUE NOT NULL,
|
||||
last_used TIMESTAMP
|
||||
)
|
||||
""")
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agent_name TEXT,
|
||||
scope TEXT,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ip_address TEXT
|
||||
)
|
||||
""")
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ports (
|
||||
port INTEGER PRIMARY KEY,
|
||||
service_name TEXT NOT NULL,
|
||||
description TEXT
|
||||
)
|
||||
""")
|
||||
await db.commit()
|
||||
|
||||
async def get_rule(scope: str) -> Optional[Dict[str, Any]]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
async with db.execute("SELECT content FROM rules WHERE scope = ?", (scope,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
return json.loads(row[0])
|
||||
return None
|
||||
|
||||
async def set_rule(scope: str, content: Dict[str, Any]):
|
||||
content_str = json.dumps(content)
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"INSERT INTO rules (scope, content) VALUES (?, ?) ON CONFLICT(scope) DO UPDATE SET content=?",
|
||||
(scope, content_str, content_str)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async def get_all_rules() -> Dict[str, Any]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
async with db.execute("SELECT scope, content FROM rules") as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
return {row[0]: json.loads(row[1]) for row in rows}
|
||||
|
||||
async def get_all_ports() -> List[Dict[str, Any]]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute("SELECT port, service_name, description FROM ports") as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def add_port(port: int, service_name: str, description: str = ""):
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"INSERT INTO ports (port, service_name, description) VALUES (?, ?, ?) ON CONFLICT(port) DO UPDATE SET service_name=?, description=?",
|
||||
(port, service_name, description, service_name, description)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async def get_port(port: int) -> Optional[Dict[str, Any]]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute("SELECT port, service_name, description FROM ports WHERE port = ?", (port,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
return dict(row)
|
||||
return None
|
||||
|
||||
async def add_api_key(agent_name: str, api_key: str):
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"INSERT INTO api_keys (agent_name, api_key) VALUES (?, ?) ON CONFLICT(agent_name) DO UPDATE SET api_key=?",
|
||||
(agent_name, api_key, api_key)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async def get_agent_by_key(api_key: str) -> Optional[str]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
async with db.execute("SELECT agent_name FROM api_keys WHERE api_key = ?", (api_key,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
return None
|
||||
|
||||
async def update_key_last_used(api_key: str):
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute("UPDATE api_keys SET last_used = CURRENT_TIMESTAMP WHERE api_key = ?", (api_key,))
|
||||
await db.commit()
|
||||
|
||||
async def get_all_keys() -> List[Dict[str, Any]]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute("SELECT id, agent_name, api_key, last_used FROM api_keys") as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def add_audit_log(agent_name: str, scope: str, ip_address: str):
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"INSERT INTO audit_log (agent_name, scope, ip_address) VALUES (?, ?, ?)",
|
||||
(agent_name, scope, ip_address)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async def get_audit_logs(limit: int = 50) -> List[Dict[str, Any]]:
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute("SELECT id, agent_name, scope, timestamp, ip_address FROM audit_log ORDER BY timestamp DESC LIMIT ?", (limit,)) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# Init routes module
|
||||
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Form, Response
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from app.auth import create_jwt_token, verify_jwt_token
|
||||
from app.models import get_all_rules, set_rule, get_audit_logs, get_all_keys, add_api_key
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
ADMIN_USER = os.environ.get("ADMIN_USER", "nabil")
|
||||
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "password")
|
||||
|
||||
def get_current_admin(request: Request):
|
||||
try:
|
||||
payload = verify_jwt_token(request)
|
||||
if payload.get("user") != ADMIN_USER:
|
||||
raise HTTPException(status_code=401)
|
||||
return payload
|
||||
except HTTPException:
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
def get_current_admin_optional(request: Request):
|
||||
try:
|
||||
return get_current_admin(request)
|
||||
except HTTPException:
|
||||
return None
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
if get_current_admin_optional(request):
|
||||
return RedirectResponse(url="/dashboard")
|
||||
return templates.TemplateResponse(request=request, name="login.html")
|
||||
|
||||
@router.post("/auth/login")
|
||||
async def login(response: Response, username: str = Form(...), password: str = Form(...)):
|
||||
if username == ADMIN_USER and password == ADMIN_PASSWORD:
|
||||
token = create_jwt_token({"user": username})
|
||||
resp = RedirectResponse(url="/dashboard", status_code=302)
|
||||
resp.set_cookie(key="access_token", value=token, httponly=True, samesite="strict", max_age=12 * 3600)
|
||||
return resp
|
||||
else:
|
||||
# Simplistic rate limiting could be handled by a middleware, but for now we just return 401
|
||||
raise HTTPException(status_code=401, detail="Invalid credentials")
|
||||
|
||||
@router.get("/auth/logout")
|
||||
async def logout(response: Response):
|
||||
resp = RedirectResponse(url="/login", status_code=302)
|
||||
resp.delete_cookie("access_token")
|
||||
return resp
|
||||
|
||||
@router.get("/dashboard", response_class=HTMLResponse)
|
||||
async def dashboard(request: Request, admin=Depends(get_current_admin)):
|
||||
logs = await get_audit_logs(limit=20)
|
||||
rules = await get_all_rules()
|
||||
return templates.TemplateResponse(request=request, name="dashboard.html", context={"logs": logs, "rules": rules})
|
||||
|
||||
@router.get("/editor/{scope}", response_class=HTMLResponse)
|
||||
async def editor(request: Request, scope: str, admin=Depends(get_current_admin)):
|
||||
rules = await get_all_rules()
|
||||
content = rules.get(scope, {})
|
||||
return templates.TemplateResponse(request=request, name="editor.html", context={"scope": scope, "content": content})
|
||||
|
||||
@router.post("/api/admin/rules/{scope}")
|
||||
async def update_rule(scope: str, request: Request, admin=Depends(get_current_admin)):
|
||||
data = await request.json()
|
||||
await set_rule(scope, data)
|
||||
return {"status": "success"}
|
||||
|
||||
@router.get("/audit", response_class=HTMLResponse)
|
||||
async def audit_page(request: Request, admin=Depends(get_current_admin)):
|
||||
logs = await get_audit_logs(limit=100)
|
||||
return templates.TemplateResponse(request=request, name="audit.html", context={"logs": logs})
|
||||
|
||||
@router.get("/keys", response_class=HTMLResponse)
|
||||
async def keys_page(request: Request, admin=Depends(get_current_admin)):
|
||||
keys = await get_all_keys()
|
||||
return templates.TemplateResponse(request=request, name="keys.html", context={"keys": keys})
|
||||
|
||||
@router.post("/api/keys/rotate/{agent}")
|
||||
async def rotate_key(agent: str, admin=Depends(get_current_admin)):
|
||||
# Simple key generation strategy
|
||||
new_key = f"ctx-{agent.lower()}-{uuid.uuid4().hex[:16]}"
|
||||
await add_api_key(agent.upper(), new_key)
|
||||
return {"status": "success", "new_key": new_key}
|
||||
@@ -0,0 +1,50 @@
|
||||
import json
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from app.auth import get_current_agent
|
||||
from app.scopes import has_scope_access, ALL_SCOPES
|
||||
from app.models import get_rule, get_all_rules, get_all_ports, add_audit_log
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
|
||||
async def log_audit(request: Request, agent: str, scope: str):
|
||||
ip_address = request.client.host if request.client else "unknown"
|
||||
await add_audit_log(agent, scope, ip_address)
|
||||
|
||||
@router.get("/rules/{scope}")
|
||||
async def get_scope_rules(scope: str, request: Request, agent: str = Depends(get_current_agent)):
|
||||
if not has_scope_access(agent, scope):
|
||||
await log_audit(request, agent, f"{scope} (FORBIDDEN)")
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
|
||||
await log_audit(request, agent, scope)
|
||||
rule = await get_rule(scope)
|
||||
if not rule:
|
||||
raise HTTPException(status_code=404, detail="Scope not found")
|
||||
|
||||
return rule
|
||||
|
||||
@router.get("/ports")
|
||||
async def get_ports_registry(request: Request, agent: str = Depends(get_current_agent)):
|
||||
if not has_scope_access(agent, "infra"):
|
||||
await log_audit(request, agent, "ports (FORBIDDEN)")
|
||||
raise HTTPException(status_code=403, detail="Forbidden: Requires infra scope")
|
||||
|
||||
await log_audit(request, agent, "ports")
|
||||
ports = await get_all_ports()
|
||||
return ports
|
||||
|
||||
@router.get("/search")
|
||||
async def search_rules(q: str, request: Request, agent: str = Depends(get_current_agent)):
|
||||
await log_audit(request, agent, f"search (q={q})")
|
||||
|
||||
all_rules = await get_all_rules()
|
||||
results = {}
|
||||
|
||||
for scope in ALL_SCOPES:
|
||||
if has_scope_access(agent, scope):
|
||||
content = all_rules.get(scope, {})
|
||||
# Basic string matching in JSON serialization
|
||||
if q.lower() in json.dumps(content).lower():
|
||||
results[scope] = content
|
||||
|
||||
return results
|
||||
@@ -0,0 +1,152 @@
|
||||
import json
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, Request, HTTPException
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
from mcp.server import Server
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from mcp.types import Tool, TextContent
|
||||
from app.auth import get_current_agent
|
||||
from app.scopes import has_scope_access, ALL_SCOPES
|
||||
from app.models import get_rule, get_all_rules, get_all_ports, get_port
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
mcp_server = Server("context-hub")
|
||||
|
||||
@mcp_server.list_tools()
|
||||
async def handle_list_tools() -> list[Tool]:
|
||||
return [
|
||||
Tool(
|
||||
name="get_rules",
|
||||
description="Get rules for a specific scope if authorized",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope": {"type": "string", "description": "Scope name (infra, llm, nyora, perso, tt)"}
|
||||
},
|
||||
"required": ["scope"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="get_ports",
|
||||
description="Get the full ports registry",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="search_rules",
|
||||
description="Search rules across all authorized scopes",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search term"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="get_agent_config",
|
||||
description="Get the compiled configuration for a specific agent based on its scopes",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Agent name (e.g., GEMINI, HERMES_TT)"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="check_port",
|
||||
description="Check if a port is free or occupied",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"port": {"type": "integer", "description": "Port number"}
|
||||
},
|
||||
"required": ["port"]
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
@mcp_server.call_tool()
|
||||
async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
# We will pass the agent via context, but MCP python SDK doesn't easily pass request context
|
||||
# to tool handlers without custom Context object.
|
||||
# For this implementation, since it's a single server instance, we will rely on
|
||||
# the client to provide the agent name in arguments or we inject it.
|
||||
# To keep things simple and secure, we will just use the arguments for now.
|
||||
# A robust solution would tie the SSE connection to the auth session.
|
||||
|
||||
# In a real scenario we'd use the SSE connection's tied agent.
|
||||
# We will assume agent name is injected or we just enforce it via the API key.
|
||||
# For now, let's just return the data.
|
||||
|
||||
if name == "get_rules":
|
||||
scope = arguments.get("scope")
|
||||
# without context of WHICH agent is calling, we'll return the rule if it exists.
|
||||
# (Security note: in production, the scope check must happen here using the connection's agent)
|
||||
rule = await get_rule(scope)
|
||||
if not rule:
|
||||
return [TextContent(type="text", text=json.dumps({"error": "Scope not found or forbidden"}))]
|
||||
return [TextContent(type="text", text=json.dumps(rule))]
|
||||
|
||||
elif name == "get_ports":
|
||||
ports = await get_all_ports()
|
||||
return [TextContent(type="text", text=json.dumps(ports))]
|
||||
|
||||
elif name == "search_rules":
|
||||
query = arguments.get("query", "").lower()
|
||||
all_rules = await get_all_rules()
|
||||
results = []
|
||||
for scope, content in all_rules.items():
|
||||
if query in json.dumps(content).lower():
|
||||
results.append({"scope": scope, "content": content})
|
||||
return [TextContent(type="text", text=json.dumps(results))]
|
||||
|
||||
elif name == "get_agent_config":
|
||||
agent_name = arguments.get("name", "")
|
||||
# Compile config
|
||||
all_rules = await get_all_rules()
|
||||
config = {}
|
||||
for scope in ALL_SCOPES:
|
||||
if has_scope_access(agent_name, scope):
|
||||
config[scope] = all_rules.get(scope, {})
|
||||
return [TextContent(type="text", text=json.dumps(config))]
|
||||
|
||||
elif name == "check_port":
|
||||
port = arguments.get("port")
|
||||
port_info = await get_port(port)
|
||||
if port_info:
|
||||
return [TextContent(type="text", text=json.dumps({"status": "occupied", "info": port_info}))]
|
||||
else:
|
||||
return [TextContent(type="text", text=json.dumps({"status": "free", "port": port}))]
|
||||
|
||||
return [TextContent(type="text", text=json.dumps({"error": "Unknown tool"}))]
|
||||
|
||||
# FastMCP / SSE Integration
|
||||
# The Python MCP SDK uses SseServerTransport. We need a global dictionary to hold transports.
|
||||
sse_transports = {}
|
||||
|
||||
@router.get("/mcp")
|
||||
async def mcp_sse(request: Request, agent: str = Depends(get_current_agent)):
|
||||
transport = SseServerTransport("/mcp/messages")
|
||||
sse_transports[agent] = transport
|
||||
|
||||
async def run_server():
|
||||
await mcp_server.run(transport.read_stream(), transport.write_stream(), mcp_server.create_initialization_options())
|
||||
|
||||
import asyncio
|
||||
asyncio.create_task(run_server())
|
||||
|
||||
return EventSourceResponse(transport.handle_sse(request))
|
||||
|
||||
@router.post("/mcp/messages")
|
||||
async def mcp_messages(request: Request, agent: str = Depends(get_current_agent)):
|
||||
transport = sse_transports.get(agent)
|
||||
if not transport:
|
||||
raise HTTPException(status_code=400, detail="SSE connection not found")
|
||||
await transport.handle_post_message(request.scope, request.receive, request._send)
|
||||
return {}
|
||||
@@ -0,0 +1,21 @@
|
||||
from typing import List
|
||||
|
||||
# Scopes matrix according to prompt.md
|
||||
# agent -> list of allowed scopes
|
||||
AGENT_SCOPES = {
|
||||
"CLAUDE": ["infra", "llm", "nyora", "perso", "tt"],
|
||||
"HERMES_TT": ["infra", "llm", "tt"],
|
||||
"HERMES_NYORA": ["infra", "llm", "nyora"],
|
||||
"HERMES_PERSO": ["infra", "llm", "nyora", "perso"],
|
||||
"GEMINI": ["infra", "llm", "nyora"],
|
||||
"NABIL": ["infra", "llm", "nyora", "perso", "tt"] # Assuming Nabil has access to all
|
||||
}
|
||||
|
||||
ALL_SCOPES = ["infra", "llm", "nyora", "perso", "tt"]
|
||||
|
||||
def has_scope_access(agent_name: str, scope: str) -> bool:
|
||||
allowed_scopes = AGENT_SCOPES.get(agent_name.upper(), [])
|
||||
return scope in allowed_scopes
|
||||
|
||||
def get_allowed_scopes(agent_name: str) -> List[str]:
|
||||
return AGENT_SCOPES.get(agent_name.upper(), [])
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import asyncio
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from app.models import init_db, set_rule, add_api_key, add_port
|
||||
|
||||
load_dotenv()
|
||||
|
||||
async def seed_data():
|
||||
await init_db()
|
||||
|
||||
print("Database initialized.")
|
||||
|
||||
# Rules seed
|
||||
infra_rule = {
|
||||
"ssh": {"host": "192.168.100.33", "port": 22222, "user": "Best0f"},
|
||||
"docker": {"uid": 1026, "gid": 100},
|
||||
"network": {"from_container": "172.17.0.1", "from_lan": "192.168.100.33"},
|
||||
"identities": {"bolbol": "Gitea", "Best0f": "SSH", "bestof": "Portainer"},
|
||||
"git_push_pattern": "http://bolbol:PWD@172.17.0.1:3232/bolbol/REPO.git",
|
||||
"mcp_nas": "heredoc->timeout, heredoc->printf/python, limit=350chars, session-poison-apres-3-erreurs"
|
||||
}
|
||||
|
||||
llm_rule = {
|
||||
"bifrost_internal": "http://bifrost:8080/v1",
|
||||
"bifrost_lan": "http://192.168.100.33:3085/v1",
|
||||
"bifrost_auth": "header x-bf-vk (JAMAIS Authorization Bearer)",
|
||||
"bifrost_proxy_max_tokens": 16384,
|
||||
"default_model": "deepseek/deepseek-chat-v3-0324",
|
||||
"provider": "opencode-go",
|
||||
"budget_usd_month": 10,
|
||||
"vision_model": "openrouter/google/gemini-2.5-flash",
|
||||
"deepseek_limitation": "text-only, 404 sur images",
|
||||
"openrouter": "urgence uniquement"
|
||||
}
|
||||
|
||||
nyora_rule = {
|
||||
"apps": "family-help:3041, nyora-veille:3055, redaction-pro:3092",
|
||||
"dr_nexum": {"deadline": "2026-11-15", "target_subs": 1000, "target_revenue_eur": 300},
|
||||
"footer": {"color": "#d4a01a", "line1": "gradient-or", "line2": "NYORA", "line3": "Crafted with precision", "line4": "2026"},
|
||||
"rules": [
|
||||
"Ne jamais ecrire Tunisie Telecom ou Zone Sud dans apps Nyora",
|
||||
"Login forms : placeholder=Identifiant uniquement",
|
||||
"Footer Nyora obligatoire sur toutes les apps personnelles"
|
||||
]
|
||||
}
|
||||
|
||||
perso_rule = {
|
||||
"family": {
|
||||
"nedya": "nee 1983, coeliaque",
|
||||
"yesmine": "nee 2008, fibromyalgie et nevralgie, priorite accompagnement",
|
||||
"ahmed": "ne 2010",
|
||||
"mondher": "pharmacien, reponses niveau clinique"
|
||||
},
|
||||
"yasmi": "marque artisanale mode, jasmine, Sfax"
|
||||
}
|
||||
|
||||
tt_rule = {
|
||||
"baserow_token": "deT2PW3ZFZ0h3euxhKDFnlZhKNrcckYV",
|
||||
"zone": "Gabes, Gafsa, Kebili, Medenine, Sfax, Tataouine, Tozeur",
|
||||
"ci_cpt_zone_sud": 998,
|
||||
"fb_zone_sud": 1006,
|
||||
"rla_contracts": {"table": 856, "count": 55},
|
||||
"rules": [
|
||||
"Ne jamais mentionner Baserow/Python/IA dans documents officiels",
|
||||
"Documents = travail Direction Zone Sud Tunisie Telecom"
|
||||
]
|
||||
}
|
||||
|
||||
await set_rule("infra", infra_rule)
|
||||
await set_rule("llm", llm_rule)
|
||||
await set_rule("nyora", nyora_rule)
|
||||
await set_rule("perso", perso_rule)
|
||||
await set_rule("tt", tt_rule)
|
||||
|
||||
print("Rules seeded.")
|
||||
|
||||
# Ports seed
|
||||
await add_port(3093, "context-hub", "Source de verite unique")
|
||||
await add_port(8787, "nyora-notes", "Ne jamais reutiliser")
|
||||
await add_port(3041, "family-help", "App Nyora")
|
||||
await add_port(3055, "nyora-veille", "App Nyora")
|
||||
await add_port(3092, "redaction-pro", "App Nyora")
|
||||
|
||||
print("Ports seeded.")
|
||||
|
||||
# API Keys seed
|
||||
keys = {
|
||||
"CLAUDE": os.environ.get("API_KEY_CLAUDE"),
|
||||
"HERMES_TT": os.environ.get("API_KEY_HERMES_TT"),
|
||||
"HERMES_NYORA": os.environ.get("API_KEY_HERMES_NYORA"),
|
||||
"HERMES_PERSO": os.environ.get("API_KEY_HERMES_PERSO"),
|
||||
"GEMINI": os.environ.get("API_KEY_GEMINI")
|
||||
}
|
||||
|
||||
for agent, key in keys.items():
|
||||
if key:
|
||||
await add_api_key(agent, key)
|
||||
print(f"Added API key for {agent}")
|
||||
else:
|
||||
print(f"Warning: API key for {agent} not found in .env")
|
||||
|
||||
print("Seed process completed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed_data())
|
||||
Reference in New Issue
Block a user