This commit is contained in:
Timothy Jaeryang Baek committed 2026-02-12 13:10:52 -06:00
commit 3f7c544835
9 files changed
+171

No files matched your search

+23
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
3.11
+22
View File
@@ -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"]
View File
Whitespace-only changes.
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
uv run uvicorn open_terminal.main:app --reload
+1
View File
@@ -0,0 +1 @@
"""open-terminal — a barebones terminal interaction API."""
+20
View File
@@ -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()
+78
View File
@@ -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")
+24
View File
@@ -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"]