Restore Giant GPT Terminal Kernel v3.1.1 — GPT relay
Browse files- .gitignore +5 -0
- Dockerfile +1 -1
- README.md +4 -4
- app.py +444 -129
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.db
|
| 5 |
+
data/
|
Dockerfile
CHANGED
|
@@ -2,7 +2,7 @@ FROM python:3.11-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
RUN pip install --no-cache-dir fastapi uvicorn[standard]
|
| 6 |
|
| 7 |
COPY . /app
|
| 8 |
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
RUN pip install --no-cache-dir fastapi uvicorn[standard] pydantic
|
| 6 |
|
| 7 |
COPY . /app
|
| 8 |
|
README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
|
|
|
| 1 |
---
|
| 2 |
+
title: GPT Relay Endpoint
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
app.py
CHANGED
|
@@ -1,170 +1,485 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
"""
|
| 6 |
import os
|
| 7 |
-
import sys
|
| 8 |
import json
|
|
|
|
|
|
|
|
|
|
| 9 |
import sqlite3
|
|
|
|
|
|
|
| 10 |
from pathlib import Path
|
| 11 |
-
from
|
| 12 |
-
from
|
|
|
|
|
|
|
|
|
|
| 13 |
from fastapi.staticfiles import StaticFiles
|
| 14 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
# ───
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
DB_PATH = Path(os.environ.get("
|
| 20 |
-
if not DB_PATH.exists():
|
| 21 |
-
DB_PATH = Path(__file__).parent / "data" / "masseurs.db"
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
STATIC_DIR = BASE_DIR / "static"
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
| 31 |
-
if STATIC_DIR.exists():
|
| 32 |
-
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
| 33 |
|
| 34 |
-
|
| 35 |
|
| 36 |
|
| 37 |
-
def
|
| 38 |
-
conn = sqlite3.connect(str(DB_PATH))
|
| 39 |
conn.row_factory = sqlite3.Row
|
| 40 |
return conn
|
| 41 |
|
| 42 |
|
| 43 |
-
# ───
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
async def health():
|
| 46 |
-
return {"status": "ok", "
|
| 47 |
|
| 48 |
|
| 49 |
-
#
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
"gold_members": gold,
|
| 65 |
-
"avg_views_per_day": round(avg_vpd or 0, 1),
|
| 66 |
-
}
|
| 67 |
|
| 68 |
|
| 69 |
-
#
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
)
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
where = "WHERE views_per_day >= ?"
|
| 84 |
-
params = [min_vpd]
|
| 85 |
-
if city:
|
| 86 |
-
where += " AND city = ?"
|
| 87 |
-
params.append(city)
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
-
total = c.execute(f"SELECT COUNT(*) FROM masseurs {where}", params).fetchone()[0]
|
| 93 |
-
rows = c.execute(
|
| 94 |
-
f"SELECT username, city, views_per_day, visits, member_since, rating, reviews_count, is_gold, photo_count FROM masseurs {where} ORDER BY {sort} DESC LIMIT ? OFFSET ?",
|
| 95 |
-
params + [limit, offset],
|
| 96 |
-
).fetchall()
|
| 97 |
-
conn.close()
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
"gold": bool(r[7]),
|
| 111 |
-
"photos": r[8],
|
| 112 |
-
"profile_url": f"{RENTMASSEUR_BASE}/{r[0]}",
|
| 113 |
-
"internal_url": f"/profile/{r[0]}",
|
| 114 |
-
}
|
| 115 |
-
for r in rows
|
| 116 |
-
],
|
| 117 |
-
}
|
| 118 |
|
| 119 |
|
| 120 |
-
#
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
if not row:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
conn.close()
|
| 130 |
-
|
| 131 |
|
| 132 |
-
profile = dict(row)
|
| 133 |
-
total = c.execute("SELECT COUNT(*) FROM masseurs").fetchone()[0]
|
| 134 |
-
rank = c.execute("SELECT COUNT(*) FROM masseurs WHERE views_per_day > ?", (profile["views_per_day"],)).fetchone()[0] + 1
|
| 135 |
-
percentile = round((1 - rank / total) * 100, 1)
|
| 136 |
-
conn.close()
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
"
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
"
|
| 146 |
-
|
| 147 |
-
"reviews_count": profile["reviews_count"],
|
| 148 |
-
"is_gold": bool(profile["is_gold"]),
|
| 149 |
-
"photo_count": profile["photo_count"],
|
| 150 |
-
"bio_length": len(profile.get("bio", "") or ""),
|
| 151 |
-
"profile_url": f"{RENTMASSEUR_BASE}/{username}",
|
| 152 |
-
}
|
| 153 |
|
| 154 |
|
| 155 |
-
#
|
|
|
|
|
|
|
| 156 |
@app.get("/")
|
| 157 |
async def root():
|
| 158 |
return {
|
| 159 |
-
"service": "
|
| 160 |
-
"
|
| 161 |
-
"https://josephrw-endpoint.hf.space",
|
| 162 |
-
"https://josephrw-csc-engine.hf.space",
|
| 163 |
-
],
|
| 164 |
"endpoints": [
|
| 165 |
-
"/
|
| 166 |
-
"/
|
| 167 |
-
"/
|
| 168 |
-
"/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
],
|
| 170 |
}
|
|
|
|
| 1 |
+
"""Giant GPT Terminal Kernel — josephrw-endpoint HF Space.
|
| 2 |
|
| 3 |
+
GPT Actions compatible external terminal, Python, file, artifact URL,
|
| 4 |
+
dynamic tool, memory, and receipt kernel. Hardened to avoid post-execution 500s.
|
| 5 |
"""
|
| 6 |
import os
|
|
|
|
| 7 |
import json
|
| 8 |
+
import time
|
| 9 |
+
import uuid
|
| 10 |
+
import hashlib
|
| 11 |
import sqlite3
|
| 12 |
+
import subprocess
|
| 13 |
+
import threading
|
| 14 |
from pathlib import Path
|
| 15 |
+
from datetime import datetime, timezone
|
| 16 |
+
from typing import Optional
|
| 17 |
+
|
| 18 |
+
from fastapi import FastAPI, Request, Query, HTTPException
|
| 19 |
+
from fastapi.responses import JSONResponse, PlainTextResponse
|
| 20 |
from fastapi.staticfiles import StaticFiles
|
| 21 |
+
from pydantic import BaseModel, Field
|
| 22 |
+
|
| 23 |
+
app = FastAPI(
|
| 24 |
+
title="Giant GPT Terminal Kernel",
|
| 25 |
+
version="3.1.1",
|
| 26 |
+
docs_url="/docs",
|
| 27 |
+
openapi_url="/openapi.json",
|
| 28 |
+
)
|
| 29 |
|
| 30 |
+
# ─── Paths ────────────────────────────────────────────────────
|
| 31 |
+
WORKSPACE_ROOT = Path(os.environ.get("WORKSPACE_ROOT", str(Path(__file__).parent / "data" / "workspaces")))
|
| 32 |
+
ARTIFACTS_DIR = Path(os.environ.get("ARTIFACTS_DIR", str(Path(__file__).parent / "data" / "artifacts")))
|
| 33 |
+
DB_PATH = Path(os.environ.get("KERNEL_DB", str(Path(__file__).parent / "data" / "kernel.db")))
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
WORKSPACE_ROOT.mkdir(parents=True, exist_ok=True)
|
| 36 |
+
ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
| 37 |
|
| 38 |
+
# ─── SQLite for receipts, memory, tools, sessions ─────────────
|
| 39 |
+
def _init_db():
|
| 40 |
+
conn = sqlite3.connect(str(DB_PATH))
|
| 41 |
+
c = conn.cursor()
|
| 42 |
+
c.execute("""CREATE TABLE IF NOT EXISTS receipts (
|
| 43 |
+
id TEXT PRIMARY KEY, kind TEXT, workspace TEXT, summary TEXT,
|
| 44 |
+
sha256 TEXT, created_at TEXT, data_json TEXT
|
| 45 |
+
)""")
|
| 46 |
+
c.execute("""CREATE TABLE IF NOT EXISTS memory (
|
| 47 |
+
id TEXT PRIMARY KEY, topic TEXT, content TEXT, utility REAL,
|
| 48 |
+
tags TEXT, created_at TEXT
|
| 49 |
+
)""")
|
| 50 |
+
c.execute("""CREATE TABLE IF NOT EXISTS tools (
|
| 51 |
+
name TEXT PRIMARY KEY, description TEXT, mode TEXT,
|
| 52 |
+
command_template TEXT, schema_data TEXT, enabled INTEGER,
|
| 53 |
+
created_at TEXT
|
| 54 |
+
)""")
|
| 55 |
+
c.execute("""CREATE TABLE IF NOT EXISTS sessions (
|
| 56 |
+
session_id TEXT PRIMARY KEY, workspace TEXT, cwd TEXT,
|
| 57 |
+
created_at TEXT, active INTEGER
|
| 58 |
+
)""")
|
| 59 |
+
conn.commit()
|
| 60 |
+
conn.close()
|
| 61 |
|
| 62 |
+
_init_db()
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
DB_LOCK = threading.Lock()
|
| 65 |
|
| 66 |
|
| 67 |
+
def _db():
|
| 68 |
+
conn = sqlite3.connect(str(DB_PATH), timeout=10)
|
| 69 |
conn.row_factory = sqlite3.Row
|
| 70 |
return conn
|
| 71 |
|
| 72 |
|
| 73 |
+
# ─── Helpers ──────────────────────────────────────────────────
|
| 74 |
+
def _ws_path(workspace: str, path: str = ".") -> Path:
|
| 75 |
+
base = WORKSPACE_ROOT / workspace
|
| 76 |
+
base.mkdir(parents=True, exist_ok=True)
|
| 77 |
+
resolved = (base / path).resolve()
|
| 78 |
+
if not str(resolved).startswith(str(base.resolve())):
|
| 79 |
+
raise HTTPException(status_code=400, detail="Path traversal denied")
|
| 80 |
+
return resolved
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _truncate(text: str, limit: int = 50000) -> tuple:
|
| 84 |
+
if len(text) > limit:
|
| 85 |
+
return text[:limit], True
|
| 86 |
+
return text, False
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _receipt(kind: str, summary: str, data: dict, workspace: Optional[str] = None) -> dict:
|
| 90 |
+
rid = uuid.uuid4().hex[:16]
|
| 91 |
+
sha = hashlib.sha256(json.dumps(data, sort_keys=True, default=str).encode()).hexdigest()
|
| 92 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 93 |
+
with DB_LOCK:
|
| 94 |
+
conn = _db()
|
| 95 |
+
conn.execute(
|
| 96 |
+
"INSERT INTO receipts VALUES (?,?,?,?,?,?,?)",
|
| 97 |
+
(rid, kind, workspace, summary, sha, now, json.dumps(data, default=str)),
|
| 98 |
+
)
|
| 99 |
+
conn.commit()
|
| 100 |
+
conn.close()
|
| 101 |
+
return {"id": rid, "kind": kind, "workspace": workspace, "summary": summary, "sha256": sha, "created_at": now}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _safe_run(cmd: list, cwd: Path, timeout: int) -> dict:
|
| 105 |
+
try:
|
| 106 |
+
result = subprocess.run(
|
| 107 |
+
cmd, cwd=str(cwd), capture_output=True, text=True, timeout=timeout,
|
| 108 |
+
)
|
| 109 |
+
stdout, truncated = _truncate(result.stdout)
|
| 110 |
+
stderr, _ = _truncate(result.stderr)
|
| 111 |
+
return {"returncode": result.returncode, "stdout": stdout, "stderr": stderr, "truncated": truncated}
|
| 112 |
+
except subprocess.TimeoutExpired:
|
| 113 |
+
return {"returncode": -1, "stdout": "", "stderr": f"Timed out after {timeout}s", "truncated": False}
|
| 114 |
+
except Exception as e:
|
| 115 |
+
return {"returncode": -1, "stdout": "", "stderr": str(e), "truncated": False}
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# ═══════════════════════════════════════════════════════════════
|
| 119 |
+
# MODELS
|
| 120 |
+
# ═══════════════════════════════════════════════════════════════
|
| 121 |
+
class TerminalRunRequest(BaseModel):
|
| 122 |
+
workspace: str = "default"
|
| 123 |
+
command: str
|
| 124 |
+
timeout_seconds: int = Field(default=10, ge=1, le=30)
|
| 125 |
+
cwd: str = "."
|
| 126 |
+
create_receipt: bool = True
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class PythonRunRequest(BaseModel):
|
| 130 |
+
workspace: str = "default"
|
| 131 |
+
code: str
|
| 132 |
+
timeout_seconds: int = Field(default=10, ge=1, le=30)
|
| 133 |
+
create_receipt: bool = True
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class SessionCreateRequest(BaseModel):
|
| 137 |
+
workspace: str = "default"
|
| 138 |
+
cwd: str = "."
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class SessionRunRequest(BaseModel):
|
| 142 |
+
session_id: str
|
| 143 |
+
command: str
|
| 144 |
+
timeout_seconds: int = Field(default=10, ge=1, le=30)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
class FileWriteRequest(BaseModel):
|
| 148 |
+
workspace: str = "default"
|
| 149 |
+
path: str
|
| 150 |
+
content: str
|
| 151 |
+
encoding: str = "utf-8"
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class FileReadRequest(BaseModel):
|
| 155 |
+
workspace: str = "default"
|
| 156 |
+
path: str
|
| 157 |
+
max_bytes: int = Field(default=120000, ge=1, le=20000000)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class ListRequest(BaseModel):
|
| 161 |
+
workspace: str = "default"
|
| 162 |
+
path: str = "."
|
| 163 |
+
max_items: int = Field(default=250, ge=1, le=2000)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
class ArtifactRequest(BaseModel):
|
| 167 |
+
workspace: str = "default"
|
| 168 |
+
filename: str
|
| 169 |
+
content: str
|
| 170 |
+
content_type: str = "text/plain"
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class LearnRequest(BaseModel):
|
| 174 |
+
topic: str = "general"
|
| 175 |
+
content: str
|
| 176 |
+
utility: float = Field(default=0.5, ge=0, le=1)
|
| 177 |
+
tags: list = []
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
class RecallRequest(BaseModel):
|
| 181 |
+
query: str
|
| 182 |
+
limit: int = Field(default=8, ge=1, le=50)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
class ToolRegisterRequest(BaseModel):
|
| 186 |
+
name: str
|
| 187 |
+
description: str = ""
|
| 188 |
+
mode: str = "command"
|
| 189 |
+
command_template: str = ""
|
| 190 |
+
schema_data: dict = {}
|
| 191 |
+
enabled: bool = True
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class ToolInvokeRequest(BaseModel):
|
| 195 |
+
workspace: str = "default"
|
| 196 |
+
args: dict = {}
|
| 197 |
+
timeout_seconds: int = Field(default=10, ge=1, le=30)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
# ═══════════════════════════════════════════════════════════════
|
| 201 |
+
# HEALTH
|
| 202 |
+
# ═══════════════════════════════════════════════════════════════
|
| 203 |
+
@app.get("/health")
|
| 204 |
async def health():
|
| 205 |
+
return {"status": "ok", "version": "3.1.1", "workspaces": len(list(WORKSPACE_ROOT.iterdir()))}
|
| 206 |
|
| 207 |
|
| 208 |
+
# ═══════════════════════════════════════════════════════════════
|
| 209 |
+
# TERMINAL RUN
|
| 210 |
+
# ═══════════════════════════════════════════════════════════════
|
| 211 |
+
@app.post("/terminal/run")
|
| 212 |
+
async def run_terminal(req: TerminalRunRequest):
|
| 213 |
+
cwd = _ws_path(req.workspace, req.cwd)
|
| 214 |
+
result = _safe_run(["sh", "-c", req.command], cwd, req.timeout_seconds)
|
| 215 |
+
receipt = None
|
| 216 |
+
receipt_error = None
|
| 217 |
+
if req.create_receipt:
|
| 218 |
+
try:
|
| 219 |
+
receipt = _receipt("terminal_run", req.command[:80], result, req.workspace)
|
| 220 |
+
except Exception as e:
|
| 221 |
+
receipt_error = str(e)
|
| 222 |
+
return {"workspace": req.workspace, "cwd": req.cwd, "command": req.command, **result, "receipt": receipt, "receipt_error": receipt_error}
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
|
| 225 |
+
# ═══════════════════════════════════════════════════════════════
|
| 226 |
+
# PYTHON RUN
|
| 227 |
+
# ═══════════════════════════════════════════════════════════════
|
| 228 |
+
@app.post("/python/run")
|
| 229 |
+
async def run_python(req: PythonRunRequest):
|
| 230 |
+
ws_base = _ws_path(req.workspace)
|
| 231 |
+
script_path = ws_base / f"_run_{uuid.uuid4().hex[:8]}.py"
|
| 232 |
+
script_path.write_text(req.code)
|
| 233 |
+
result = _safe_run(["python3", str(script_path)], ws_base, req.timeout_seconds)
|
| 234 |
+
try:
|
| 235 |
+
script_path.unlink(missing_ok=True)
|
| 236 |
+
except Exception:
|
| 237 |
+
pass
|
| 238 |
+
receipt = None
|
| 239 |
+
receipt_error = None
|
| 240 |
+
if req.create_receipt:
|
| 241 |
+
try:
|
| 242 |
+
receipt = _receipt("python_run", req.code[:80], result, req.workspace)
|
| 243 |
+
except Exception as e:
|
| 244 |
+
receipt_error = str(e)
|
| 245 |
+
return {"workspace": req.workspace, "script": script_path.name, **result, "receipt": receipt, "receipt_error": receipt_error}
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
+
# ═══════════════════════════════════════════════════════════════
|
| 249 |
+
# SESSIONS
|
| 250 |
+
# ═══════════════════════════════════════════════════════════════
|
| 251 |
+
@app.post("/session/create")
|
| 252 |
+
async def create_session(req: SessionCreateRequest):
|
| 253 |
+
sid = uuid.uuid4().hex[:12]
|
| 254 |
+
_ws_path(req.workspace, req.cwd)
|
| 255 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 256 |
+
with DB_LOCK:
|
| 257 |
+
conn = _db()
|
| 258 |
+
conn.execute("INSERT INTO sessions VALUES (?,?,?,?,1)", (sid, req.workspace, req.cwd, now))
|
| 259 |
+
conn.commit()
|
| 260 |
+
conn.close()
|
| 261 |
+
return {"session_id": sid, "workspace": req.workspace, "cwd": req.cwd, "created_at": now}
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
+
@app.post("/session/run")
|
| 265 |
+
async def run_session_command(req: SessionRunRequest):
|
| 266 |
+
with DB_LOCK:
|
| 267 |
+
conn = _db()
|
| 268 |
+
row = conn.execute("SELECT * FROM sessions WHERE session_id=? AND active=1", (req.session_id,)).fetchone()
|
| 269 |
+
conn.close()
|
| 270 |
+
if not row:
|
| 271 |
+
raise HTTPException(status_code=404, detail="Session not found or inactive")
|
| 272 |
+
cwd = _ws_path(row["workspace"], row["cwd"])
|
| 273 |
+
result = _safe_run(["sh", "-c", req.command], cwd, req.timeout_seconds)
|
| 274 |
+
return {"workspace": row["workspace"], "cwd": row["cwd"], "command": req.command, **result, "receipt": _receipt("session_run", req.command[:80], result, row["workspace"]), "receipt_error": None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
|
| 277 |
+
# ═══════════════════════════════════════════════════════════════
|
| 278 |
+
# FILES
|
| 279 |
+
# ═══════════════════════════════════════════════════════════════
|
| 280 |
+
@app.post("/files/write")
|
| 281 |
+
async def write_file(req: FileWriteRequest):
|
| 282 |
+
target = _ws_path(req.workspace, req.path)
|
| 283 |
+
target.parent.mkdir(parents=True, exist_ok=True)
|
| 284 |
+
target.write_text(req.content, encoding=req.encoding)
|
| 285 |
+
receipt = _receipt("file_write", req.path, {"bytes": len(req.content)}, req.workspace)
|
| 286 |
+
return {"status": "written", "path": req.path, "bytes": len(req.content), "receipt": receipt}
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
@app.post("/files/read")
|
| 290 |
+
async def read_file(req: FileReadRequest):
|
| 291 |
+
target = _ws_path(req.workspace, req.path)
|
| 292 |
+
if not target.exists():
|
| 293 |
+
raise HTTPException(status_code=404, detail="File not found")
|
| 294 |
+
if target.is_dir():
|
| 295 |
+
raise HTTPException(status_code=400, detail="Path is a directory")
|
| 296 |
+
data = target.read_bytes()[:req.max_bytes]
|
| 297 |
+
try:
|
| 298 |
+
text = data.decode("utf-8")
|
| 299 |
+
return {"path": req.path, "content": text, "bytes": len(data), "truncated": target.stat().st_size > req.max_bytes}
|
| 300 |
+
except UnicodeDecodeError:
|
| 301 |
+
import base64
|
| 302 |
+
return {"path": req.path, "content_base64": base64.b64encode(data).decode(), "bytes": len(data), "truncated": target.stat().st_size > req.max_bytes}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@app.post("/files/list")
|
| 306 |
+
async def list_files(req: ListRequest):
|
| 307 |
+
target = _ws_path(req.workspace, req.path)
|
| 308 |
+
if not target.exists():
|
| 309 |
+
return {"path": req.path, "entries": []}
|
| 310 |
+
entries = []
|
| 311 |
+
if target.is_dir():
|
| 312 |
+
for item in sorted(target.iterdir())[:req.max_items]:
|
| 313 |
+
entries.append({"name": item.name, "type": "dir" if item.is_dir() else "file", "size": item.stat().st_size if item.is_file() else None})
|
| 314 |
+
return {"path": req.path, "entries": entries}
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
@app.get("/workspace/tree")
|
| 318 |
+
async def get_workspace_tree(workspace: str = "default", max_items: int = 500):
|
| 319 |
+
base = _ws_path(workspace)
|
| 320 |
+
lines = []
|
| 321 |
+
count = 0
|
| 322 |
+
for p in sorted(base.rglob("*")):
|
| 323 |
+
if count >= max_items:
|
| 324 |
+
lines.append("... (truncated)")
|
| 325 |
+
break
|
| 326 |
+
rel = p.relative_to(base)
|
| 327 |
+
indent = " " * (len(rel.parts) - 1)
|
| 328 |
+
marker = "/" if p.is_dir() else ""
|
| 329 |
+
lines.append(f"{indent}{p.name}{marker}")
|
| 330 |
+
count += 1
|
| 331 |
+
return PlainTextResponse("\n".join(lines) if lines else "(empty)")
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
# ═══════════════════════════════════════════════════════════════
|
| 335 |
+
# ARTIFACTS
|
| 336 |
+
# ═══════════════════════════════════════════════════════════════
|
| 337 |
+
app.mount("/artifact", StaticFiles(directory=str(ARTIFACTS_DIR)), name="artifacts")
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
@app.post("/artifact/compile")
|
| 341 |
+
async def compile_artifact(req: ArtifactRequest):
|
| 342 |
+
artifact_hash = hashlib.sha256(req.content.encode()).hexdigest()[:16]
|
| 343 |
+
artifact_dir = ARTIFACTS_DIR / artifact_hash
|
| 344 |
+
artifact_dir.mkdir(parents=True, exist_ok=True)
|
| 345 |
+
(artifact_dir / req.filename).write_text(req.content)
|
| 346 |
+
(artifact_dir / "meta.json").write_text(json.dumps({"filename": req.filename, "content_type": req.content_type, "workspace": req.workspace, "sha256": artifact_hash, "created_at": datetime.now(timezone.utc).isoformat()}, indent=2))
|
| 347 |
+
receipt = _receipt("artifact", req.filename, {"hash": artifact_hash}, req.workspace)
|
| 348 |
+
return {"hash": artifact_hash, "filename": req.filename, "url": f"/artifact/{artifact_hash}/{req.filename}", "receipt": receipt}
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
@app.post("/url/issue")
|
| 352 |
+
async def issue_url(req: ArtifactRequest):
|
| 353 |
+
artifact_hash = hashlib.sha256(req.content.encode()).hexdigest()[:16]
|
| 354 |
+
artifact_dir = ARTIFACTS_DIR / artifact_hash
|
| 355 |
+
artifact_dir.mkdir(parents=True, exist_ok=True)
|
| 356 |
+
(artifact_dir / req.filename).write_text(req.content)
|
| 357 |
+
url = f"https://josephrw-endpoint.hf.space/artifact/{artifact_hash}/{req.filename}"
|
| 358 |
+
receipt = _receipt("url_issue", req.filename, {"url": url, "hash": artifact_hash}, req.workspace)
|
| 359 |
+
return {"url": url, "hash": artifact_hash, "filename": req.filename, "receipt": receipt}
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
# ═══════════════════════════════════════════════════════════════
|
| 363 |
+
# MEMORY
|
| 364 |
+
# ════════════════════════════════════════════════════════════��══
|
| 365 |
+
@app.post("/learn")
|
| 366 |
+
async def learn_memory(req: LearnRequest):
|
| 367 |
+
mid = uuid.uuid4().hex[:16]
|
| 368 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 369 |
+
with DB_LOCK:
|
| 370 |
+
conn = _db()
|
| 371 |
+
conn.execute("INSERT INTO memory VALUES (?,?,?,?,?,?)", (mid, req.topic, req.content, req.utility, json.dumps(req.tags), now))
|
| 372 |
+
conn.commit()
|
| 373 |
+
conn.close()
|
| 374 |
+
return {"status": "learned", "id": mid, "topic": req.topic}
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
@app.post("/recall")
|
| 378 |
+
async def recall_memory(req: RecallRequest):
|
| 379 |
+
with DB_LOCK:
|
| 380 |
+
conn = _db()
|
| 381 |
+
rows = conn.execute("SELECT * FROM memory WHERE content LIKE ? OR topic LIKE ? ORDER BY utility DESC LIMIT ?", (f"%{req.query}%", f"%{req.query}%", req.limit)).fetchall()
|
| 382 |
+
conn.close()
|
| 383 |
+
return {"results": [{"id": r["id"], "topic": r["topic"], "content": r["content"], "utility": r["utility"], "tags": json.loads(r["tags"]), "created_at": r["created_at"]} for r in rows]}
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
# ═══════════════════════════════════════════════════════════════
|
| 387 |
+
# DYNAMIC TOOLS
|
| 388 |
+
# ═══════════════════════════════════════════════════════════════
|
| 389 |
+
@app.post("/tool/register")
|
| 390 |
+
async def register_tool(req: ToolRegisterRequest):
|
| 391 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 392 |
+
with DB_LOCK:
|
| 393 |
+
conn = _db()
|
| 394 |
+
conn.execute("INSERT OR REPLACE INTO tools VALUES (?,?,?,?,?,?,?)", (req.name, req.description, req.mode, req.command_template, json.dumps(req.schema_data), int(req.enabled), now))
|
| 395 |
+
conn.commit()
|
| 396 |
+
conn.close()
|
| 397 |
+
return {"status": "registered", "name": req.name, "mode": req.mode}
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
@app.get("/tools")
|
| 401 |
+
async def list_tools():
|
| 402 |
+
with DB_LOCK:
|
| 403 |
+
conn = _db()
|
| 404 |
+
rows = conn.execute("SELECT * FROM tools WHERE enabled=1").fetchall()
|
| 405 |
+
conn.close()
|
| 406 |
+
return {"tools": [{"name": r["name"], "description": r["description"], "mode": r["mode"], "command_template": r["command_template"], "schema": json.loads(r["schema_data"])} for r in rows]}
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
@app.post("/tool/{name}")
|
| 410 |
+
async def invoke_tool(name: str, req: ToolInvokeRequest):
|
| 411 |
+
with DB_LOCK:
|
| 412 |
+
conn = _db()
|
| 413 |
+
row = conn.execute("SELECT * FROM tools WHERE name=? AND enabled=1", (name,)).fetchone()
|
| 414 |
+
conn.close()
|
| 415 |
if not row:
|
| 416 |
+
raise HTTPException(status_code=404, detail=f"Tool '{name}' not found")
|
| 417 |
+
template = row["command_template"]
|
| 418 |
+
cmd = template
|
| 419 |
+
for k, v in req.args.items():
|
| 420 |
+
cmd = cmd.replace(f"{{{{{k}}}}}", str(v))
|
| 421 |
+
if row["mode"] == "python":
|
| 422 |
+
ws_base = _ws_path(req.workspace)
|
| 423 |
+
script = ws_base / f"_tool_{uuid.uuid4().hex[:8]}.py"
|
| 424 |
+
script.write_text(cmd)
|
| 425 |
+
result = _safe_run(["python3", str(script)], ws_base, req.timeout_seconds)
|
| 426 |
+
script.unlink(missing_ok=True)
|
| 427 |
+
else:
|
| 428 |
+
cwd = _ws_path(req.workspace)
|
| 429 |
+
result = _safe_run(["sh", "-c", cmd], cwd, req.timeout_seconds)
|
| 430 |
+
receipt = _receipt("tool_invoke", name, result, req.workspace)
|
| 431 |
+
return {"tool": name, **result, "receipt": receipt}
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
# ═══════════════════════════════════════════════════════════════
|
| 435 |
+
# RECEIPTS / LEDGER
|
| 436 |
+
# ═══════════════════════════════════════════════════════════════
|
| 437 |
+
@app.get("/ledger/recent")
|
| 438 |
+
async def get_recent_ledger(limit: int = 50):
|
| 439 |
+
with DB_LOCK:
|
| 440 |
+
conn = _db()
|
| 441 |
+
rows = conn.execute("SELECT id, kind, workspace, summary, sha256, created_at FROM receipts ORDER BY created_at DESC LIMIT ?", (limit,)).fetchall()
|
| 442 |
conn.close()
|
| 443 |
+
return {"receipts": [{"id": r["id"], "kind": r["kind"], "workspace": r["workspace"], "summary": r["summary"], "sha256": r["sha256"], "created_at": r["created_at"]} for r in rows]}
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
|
| 446 |
+
@app.get("/receipt/{receipt_id}")
|
| 447 |
+
async def get_receipt(receipt_id: str):
|
| 448 |
+
with DB_LOCK:
|
| 449 |
+
conn = _db()
|
| 450 |
+
row = conn.execute("SELECT * FROM receipts WHERE id=?", (receipt_id,)).fetchone()
|
| 451 |
+
conn.close()
|
| 452 |
+
if not row:
|
| 453 |
+
raise HTTPException(status_code=404, detail="Receipt not found")
|
| 454 |
+
return {"id": row["id"], "kind": row["kind"], "workspace": row["workspace"], "summary": row["summary"], "sha256": row["sha256"], "created_at": row["created_at"], "data": json.loads(row["data_json"])}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
|
| 457 |
+
# ═══════════════════════════════════════════════════════════════
|
| 458 |
+
# ROOT
|
| 459 |
+
# ═══════════════════════════════════════════════════════════════
|
| 460 |
@app.get("/")
|
| 461 |
async def root():
|
| 462 |
return {
|
| 463 |
+
"service": "Giant GPT Terminal Kernel",
|
| 464 |
+
"version": "3.1.1",
|
|
|
|
|
|
|
|
|
|
| 465 |
"endpoints": [
|
| 466 |
+
"GET /health",
|
| 467 |
+
"POST /terminal/run",
|
| 468 |
+
"POST /python/run",
|
| 469 |
+
"POST /session/create",
|
| 470 |
+
"POST /session/run",
|
| 471 |
+
"POST /files/write",
|
| 472 |
+
"POST /files/read",
|
| 473 |
+
"POST /files/list",
|
| 474 |
+
"GET /workspace/tree",
|
| 475 |
+
"POST /artifact/compile",
|
| 476 |
+
"POST /url/issue",
|
| 477 |
+
"POST /learn",
|
| 478 |
+
"POST /recall",
|
| 479 |
+
"POST /tool/register",
|
| 480 |
+
"GET /tools",
|
| 481 |
+
"POST /tool/{name}",
|
| 482 |
+
"GET /ledger/recent",
|
| 483 |
+
"GET /receipt/{receipt_id}",
|
| 484 |
],
|
| 485 |
}
|