openring 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openring/__init__.py +3 -0
- openring/__main__.py +118 -0
- openring/api.py +598 -0
- openring/auth.py +116 -0
- openring/bot.py +310 -0
- openring/config.py +42 -0
- openring/importer.py +151 -0
- openring/keys.py +209 -0
- openring/providers.py +458 -0
- openring/server_messages.py +142 -0
- openring/static/index.html +1138 -0
- openring/stats.py +100 -0
- openring/store.py +172 -0
- openring/tools.py +278 -0
- openring/twilio.py +80 -0
- openring-0.1.0.dist-info/METADATA +214 -0
- openring-0.1.0.dist-info/RECORD +20 -0
- openring-0.1.0.dist-info/WHEEL +4 -0
- openring-0.1.0.dist-info/entry_points.txt +2 -0
- openring-0.1.0.dist-info/licenses/LICENSE +21 -0
openring/__init__.py
ADDED
openring/__main__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""`openring` command: first run creates ~/.openring with a generated API key, then starts the server."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import secrets
|
|
6
|
+
import socket
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
HOME = Path(os.environ.get("OPENRING_HOME", Path.home() / ".openring"))
|
|
10
|
+
ENV_FILE = HOME / ".env"
|
|
11
|
+
|
|
12
|
+
ENV_TEMPLATE = """# OpenRing settings. Edit and restart `openring` to apply.
|
|
13
|
+
OPENRING_API_KEY={key}
|
|
14
|
+
# Dashboard password. Leave empty to choose it in the browser on first visit (from this computer).
|
|
15
|
+
# Set it for servers you reach over the network (Docker, VPS). Forgot it? Run: openring --reset-password
|
|
16
|
+
OPENRING_PASSWORD=
|
|
17
|
+
# Public https URL Twilio can reach (e.g. from `ngrok http 8000`)
|
|
18
|
+
PUBLIC_URL=
|
|
19
|
+
TWILIO_ACCOUNT_SID=
|
|
20
|
+
# Required to answer calls: it proves each webhook really comes from Twilio
|
|
21
|
+
TWILIO_AUTH_TOKEN=
|
|
22
|
+
# Speech-to-text: Deepgram gives US$200 free credit to new accounts
|
|
23
|
+
DEEPGRAM_API_KEY=
|
|
24
|
+
# LLM: any of these (or run OmniRoute locally for free models)
|
|
25
|
+
OPENAI_API_KEY=
|
|
26
|
+
GROQ_API_KEY=
|
|
27
|
+
OPENROUTER_API_KEY=
|
|
28
|
+
OMNIROUTE_BASE_URL=http://localhost:20128/v1
|
|
29
|
+
# Text-to-speech: leave empty to use Kokoro (free, local)
|
|
30
|
+
ELEVENLABS_API_KEY=
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_env_file(path: Path) -> None:
|
|
35
|
+
"""Load KEY=VALUE lines; real environment variables win."""
|
|
36
|
+
if not path.exists():
|
|
37
|
+
return
|
|
38
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
39
|
+
line = line.strip()
|
|
40
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
41
|
+
continue
|
|
42
|
+
key, value = line.split("=", 1)
|
|
43
|
+
if value.strip():
|
|
44
|
+
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def first_run_setup() -> bool:
|
|
48
|
+
if ENV_FILE.exists():
|
|
49
|
+
return False
|
|
50
|
+
HOME.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
ENV_FILE.write_text(ENV_TEMPLATE.format(key=secrets.token_urlsafe(24)), encoding="utf-8")
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def port_in_use(host: str, port: int) -> bool:
|
|
56
|
+
with socket.socket(socket.AF_INET6 if ":" in host else socket.AF_INET) as s:
|
|
57
|
+
if os.name != "nt": # like uvicorn, ignore connections still closing from a previous run
|
|
58
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
59
|
+
try:
|
|
60
|
+
s.bind((host, port))
|
|
61
|
+
except OSError:
|
|
62
|
+
return True
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ensure_port_free(host: str, port: int) -> None:
|
|
67
|
+
if port_in_use(host, port):
|
|
68
|
+
print(f"\n Port {port} is already in use, maybe by another OpenRing.")
|
|
69
|
+
print(f" Stop that program, or start on another port: openring --port {port + 1}\n")
|
|
70
|
+
raise SystemExit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def reset_password(db_path: str) -> None:
|
|
74
|
+
from .auth import Sessions
|
|
75
|
+
from .store import Store
|
|
76
|
+
|
|
77
|
+
Sessions(Store(db_path)).reset_password()
|
|
78
|
+
print("\n Dashboard password removed. Start openring and open http://localhost to choose a new one.")
|
|
79
|
+
if os.environ.get("OPENRING_PASSWORD"):
|
|
80
|
+
print(" OPENRING_PASSWORD is set, so it becomes the password again on the next start.")
|
|
81
|
+
print()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main(argv: list[str] | None = None) -> None:
|
|
85
|
+
parser = argparse.ArgumentParser(prog="openring", description="Self-hosted voice AI agents, Vapi-compatible.")
|
|
86
|
+
parser.add_argument("--port", type=int, help="port to serve on (default: $PORT, or 8000)")
|
|
87
|
+
parser.add_argument("--reset-password", action="store_true",
|
|
88
|
+
help="forget the dashboard password; choose a new one at http://localhost on the next visit")
|
|
89
|
+
args = parser.parse_args(argv)
|
|
90
|
+
|
|
91
|
+
created = first_run_setup()
|
|
92
|
+
load_env_file(Path.cwd() / ".env") # a project-local .env takes precedence
|
|
93
|
+
load_env_file(ENV_FILE)
|
|
94
|
+
os.environ.setdefault("OPENRING_DB_PATH", str(HOME / "openring.db"))
|
|
95
|
+
if args.reset_password:
|
|
96
|
+
reset_password(os.environ["OPENRING_DB_PATH"])
|
|
97
|
+
return
|
|
98
|
+
host = os.environ.get("HOST", "127.0.0.1")
|
|
99
|
+
port = args.port or int(os.environ.get("PORT", "8000"))
|
|
100
|
+
ensure_port_free(host, port)
|
|
101
|
+
|
|
102
|
+
import uvicorn
|
|
103
|
+
|
|
104
|
+
from .api import create_app
|
|
105
|
+
from .providers import kokoro_installed, kokoro_ready, warm_up
|
|
106
|
+
|
|
107
|
+
os.environ["OPENRING_ENV_FILE"] = str(ENV_FILE) # error messages point people to it
|
|
108
|
+
print(f"\n OpenRing is running: http://localhost:{port}")
|
|
109
|
+
print(f" Settings: {ENV_FILE}" + (" (created now)" if created else ""))
|
|
110
|
+
if kokoro_installed() and not kokoro_ready():
|
|
111
|
+
print(" Downloading the free Kokoro voice in the background (one time, about 120 MB).")
|
|
112
|
+
warm_up()
|
|
113
|
+
print(" Press Ctrl+C to stop.\n")
|
|
114
|
+
uvicorn.run(create_app(), host=host, port=port, log_level="warning")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|