mirror of
https://github.com/open-webui/open-terminal.git
synced 2026-11-04 08:10:53 +00:00
init
This commit is contained in:
commit
3f7c544835
9 files changed
+171
No files matched your search
+23
@@ -0,0 +1,23 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# venv
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# uv
|
||||||
|
uv.lock
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
git \
|
||||||
|
jq \
|
||||||
|
vim-tiny \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
openssh-client \
|
||||||
|
unzip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["open-terminal"]
|
||||||
|
CMD ["run"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
uv run uvicorn open_terminal.main:app --reload
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""open-terminal — a barebones terminal interaction API."""
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import click
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def main():
|
||||||
|
"""open-terminal — terminal interaction API"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.option("--host", default="0.0.0.0", help="Bind host")
|
||||||
|
@click.option("--port", default=8000, type=int, help="Bind port")
|
||||||
|
def run(host: str, port: int):
|
||||||
|
"""Start the sandbox API server."""
|
||||||
|
uvicorn.run("open_terminal.main:app", host=host, port=port)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI(title="open-terminal")
|
||||||
|
|
||||||
|
|
||||||
|
class ExecRequest(BaseModel):
|
||||||
|
command: str
|
||||||
|
timeout: Optional[float] = 30.0
|
||||||
|
|
||||||
|
|
||||||
|
class ExecResponse(BaseModel):
|
||||||
|
exit_code: int
|
||||||
|
stdout: str
|
||||||
|
stderr: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/execute")
|
||||||
|
async def execute(req: ExecRequest, stream: bool = False):
|
||||||
|
if stream:
|
||||||
|
return _stream_response(req)
|
||||||
|
|
||||||
|
try:
|
||||||
|
proc = await asyncio.create_subprocess_shell(
|
||||||
|
req.command,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
)
|
||||||
|
stdout, stderr = await asyncio.wait_for(
|
||||||
|
proc.communicate(), timeout=req.timeout
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
proc.kill()
|
||||||
|
await proc.communicate()
|
||||||
|
return ExecResponse(
|
||||||
|
exit_code=-1,
|
||||||
|
stdout="",
|
||||||
|
stderr=f"Command timed out after {req.timeout}s",
|
||||||
|
)
|
||||||
|
|
||||||
|
return ExecResponse(
|
||||||
|
exit_code=proc.returncode or 0,
|
||||||
|
stdout=stdout.decode(errors="replace"),
|
||||||
|
stderr=stderr.decode(errors="replace"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_response(req: ExecRequest):
|
||||||
|
async def generate():
|
||||||
|
proc = await asyncio.create_subprocess_shell(
|
||||||
|
req.command,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def read_stream(s, label):
|
||||||
|
async for line in s:
|
||||||
|
yield json.dumps({"type": label, "data": line.decode(errors="replace")}) + "\n"
|
||||||
|
|
||||||
|
async for chunk in read_stream(proc.stdout, "stdout"):
|
||||||
|
yield chunk
|
||||||
|
async for chunk in read_stream(proc.stderr, "stderr"):
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
await proc.wait()
|
||||||
|
yield json.dumps({"type": "exit", "data": proc.returncode}) + "\n"
|
||||||
|
|
||||||
|
return StreamingResponse(generate(), media_type="application/x-ndjson")
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[project]
|
||||||
|
name = "open-terminal"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A barebones terminal interaction API"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "Timothy Jaeryang Baek", email = "tim@openwebui.com" }
|
||||||
|
]
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.115.0",
|
||||||
|
"uvicorn[standard]>=0.34.0",
|
||||||
|
"click>=8.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
open-terminal = "open_terminal.cli:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["open_terminal"]
|
||||||
Reference in new issue
Block a user