mirror of
https://github.com/blackboxprogramming/lucidia.git
synced 2026-03-17 04:57:15 -05:00
137 lines
4.3 KiB
Python
137 lines
4.3 KiB
Python
from pathlib import Path
|
|
|
|
# --- providers/registry.py ---
|
|
registry = r"""
|
|
import os
|
|
from typing import Dict, Any
|
|
|
|
# Feature flags via env; flip to "on" later by setting a token/value
|
|
ENABLED = {
|
|
"slack": bool(os.getenv("SLACK_BOT_TOKEN")),
|
|
"asana": bool(os.getenv("ASANA_ACCESS_TOKEN")),
|
|
"linear": bool(os.getenv("LINEAR_API_KEY")),
|
|
"notion": bool(os.getenv("NOTION_TOKEN")),
|
|
"github": bool(os.getenv("GITHUB_TOKEN")),
|
|
"jira": all(os.getenv(k) for k in ["JIRA_URL","JIRA_EMAIL","JIRA_API_TOKEN"]),
|
|
}
|
|
|
|
def get_enabled():
|
|
return {k: v for k, v in ENABLED.items() if v}
|
|
|
|
def call_tool(tool: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
# PURE PLACEHOLDERS for now; return ok if token is present
|
|
if tool == "slack.say":
|
|
if not ENABLED["slack"]: return {"error":"slack not configured"}
|
|
return {"ok": True, "placeholder": "slack.say", "args": args}
|
|
|
|
if tool == "asana.me":
|
|
if not ENABLED["asana"]: return {"error":"asana not configured"}
|
|
return {"ok": True, "placeholder": "asana.me"}
|
|
|
|
if tool == "linear.me":
|
|
if not ENABLED["linear"]: return {"error":"linear not configured"}
|
|
return {"ok": True, "placeholder": "linear.me"}
|
|
|
|
if tool == "notion.me":
|
|
if not ENABLED["notion"]: return {"error":"notion not configured"}
|
|
return {"ok": True, "placeholder": "notion.me"}
|
|
|
|
if tool == "github.me":
|
|
if not ENABLED["github"]: return {"error":"github not configured"}
|
|
return {"ok": True, "placeholder": "github.me"}
|
|
|
|
if tool == "jira.me":
|
|
if not ENABLED["jira"]: return {"error":"jira not configured"}
|
|
return {"ok": True, "placeholder": "jira.me"}
|
|
|
|
return {"error": f"unknown tool: {tool}"}
|
|
""".lstrip()
|
|
|
|
# --- main.py ---
|
|
main = r"""
|
|
import os, sqlite3
|
|
from typing import Optional, Dict, Any
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
from providers import get_enabled, call_tool
|
|
|
|
# ---- tiny sqlite memory ----
|
|
DB_PATH = "/home/pi/lucidia/lucidia.db"
|
|
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
|
|
conn.execute("CREATE TABLE IF NOT EXISTS memory (k TEXT PRIMARY KEY, v TEXT)")
|
|
|
|
app = FastAPI(title="Lucidia")
|
|
|
|
@app.get("/")
|
|
def root():
|
|
return {"lucidia": "online"}
|
|
|
|
@app.get("/healthz")
|
|
def healthz():
|
|
return {"ok": True}
|
|
|
|
# ---- memory endpoints ----
|
|
class MemoryPut(BaseModel):
|
|
key: str
|
|
value: str
|
|
|
|
@app.post("/memory/put")
|
|
def memory_put(payload: MemoryPut):
|
|
conn.execute("REPLACE INTO memory(k,v) VALUES (?,?)", (payload.key, payload.value))
|
|
conn.commit()
|
|
return {"ok": True}
|
|
|
|
@app.get("/memory/get")
|
|
def memory_get(key: str):
|
|
row = conn.execute("SELECT v FROM memory WHERE k=?", (key,)).fetchone()
|
|
return {"key": key, "value": (row[0] if row else None)}
|
|
|
|
# ---- minimal service endpoints (placeholders; real calls later) ----
|
|
@app.post("/slack/say")
|
|
def slack_say(channel: str = "#general", text: str = "Lucidia says hi"):
|
|
r = call_tool("slack.say", {"channel": channel, "text": text})
|
|
if "error" in r: raise HTTPException(500, r["error"])
|
|
return r
|
|
|
|
@app.get("/asana/me")
|
|
def asana_me():
|
|
r = call_tool("asana.me", {})
|
|
if "error" in r: raise HTTPException(500, r["error"])
|
|
return r
|
|
|
|
@app.get("/linear/me")
|
|
def linear_me():
|
|
r = call_tool("linear.me", {})
|
|
if "error" in r: raise HTTPException(500, r["error"])
|
|
return r
|
|
|
|
# ---- agent skeleton ----
|
|
class AgentMsg(BaseModel):
|
|
message: Optional[str] = None
|
|
tool: Optional[str] = None
|
|
args: Optional[Dict[str, Any]] = None
|
|
|
|
@app.get("/agent/capabilities")
|
|
def agent_caps():
|
|
return {"enabled": list(get_enabled().keys())}
|
|
|
|
@app.post("/agent/chat")
|
|
def agent_chat(payload: AgentMsg):
|
|
# If a tool is provided, call it; message is optional.
|
|
if payload.tool:
|
|
r = call_tool(payload.tool, payload.args or {})
|
|
if "error" in r: raise HTTPException(500, r["error"])
|
|
return {"message": "tool_result", "result": r}
|
|
return {
|
|
"message": (payload.message or "").strip(),
|
|
"you_can_call": list(get_enabled().keys()),
|
|
"hint": "POST {'tool':'slack.say','args':{'channel':'#general','text':'hi'}}"
|
|
}
|
|
""".lstrip()
|
|
|
|
# write files atomically
|
|
Path("providers").mkdir(exist_ok=True)
|
|
Path("providers/registry.py").write_text(registry)
|
|
Path("main.py").write_text(main)
|
|
print("wrote providers/registry.py and main.py")
|