ttsd-colabcli 1.0.0

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.
Files changed (47) hide show
  1. package/cli.js +148 -0
  2. package/core/app/__init__.py +0 -0
  3. package/core/app/colab_cli/__init__.py +0 -0
  4. package/core/app/colab_cli/__pycache__/__init__.cpython-312.pyc +0 -0
  5. package/core/app/colab_cli/__pycache__/auth.cpython-312.pyc +0 -0
  6. package/core/app/colab_cli/__pycache__/auto_update.cpython-312.pyc +0 -0
  7. package/core/app/colab_cli/__pycache__/cli.cpython-312.pyc +0 -0
  8. package/core/app/colab_cli/__pycache__/client.cpython-312.pyc +0 -0
  9. package/core/app/colab_cli/__pycache__/common.cpython-312.pyc +0 -0
  10. package/core/app/colab_cli/__pycache__/console.cpython-312.pyc +0 -0
  11. package/core/app/colab_cli/__pycache__/contents.cpython-312.pyc +0 -0
  12. package/core/app/colab_cli/__pycache__/history.cpython-312.pyc +0 -0
  13. package/core/app/colab_cli/__pycache__/runtime.cpython-312.pyc +0 -0
  14. package/core/app/colab_cli/__pycache__/state.cpython-312.pyc +0 -0
  15. package/core/app/colab_cli/__pycache__/utils.cpython-312.pyc +0 -0
  16. package/core/app/colab_cli/auth.py +278 -0
  17. package/core/app/colab_cli/auto_update.py +248 -0
  18. package/core/app/colab_cli/cli.py +155 -0
  19. package/core/app/colab_cli/client.py +310 -0
  20. package/core/app/colab_cli/commands/__init__.py +14 -0
  21. package/core/app/colab_cli/commands/__pycache__/__init__.cpython-312.pyc +0 -0
  22. package/core/app/colab_cli/commands/__pycache__/automation.cpython-312.pyc +0 -0
  23. package/core/app/colab_cli/commands/__pycache__/execution.cpython-312.pyc +0 -0
  24. package/core/app/colab_cli/commands/__pycache__/files.cpython-312.pyc +0 -0
  25. package/core/app/colab_cli/commands/__pycache__/run.cpython-312.pyc +0 -0
  26. package/core/app/colab_cli/commands/__pycache__/session.cpython-312.pyc +0 -0
  27. package/core/app/colab_cli/commands/__pycache__/utility.cpython-312.pyc +0 -0
  28. package/core/app/colab_cli/commands/automation.py +265 -0
  29. package/core/app/colab_cli/commands/execution.py +362 -0
  30. package/core/app/colab_cli/commands/files.py +204 -0
  31. package/core/app/colab_cli/commands/run.py +477 -0
  32. package/core/app/colab_cli/commands/session.py +519 -0
  33. package/core/app/colab_cli/commands/utility.py +436 -0
  34. package/core/app/colab_cli/common.py +185 -0
  35. package/core/app/colab_cli/console.py +172 -0
  36. package/core/app/colab_cli/contents.py +93 -0
  37. package/core/app/colab_cli/converter.py +184 -0
  38. package/core/app/colab_cli/history.py +65 -0
  39. package/core/app/colab_cli/oauth_config.json +11 -0
  40. package/core/app/colab_cli/repl.py +173 -0
  41. package/core/app/colab_cli/runtime.py +262 -0
  42. package/core/app/colab_cli/state.py +156 -0
  43. package/core/app/colab_cli/utils.py +85 -0
  44. package/core/colab/worker.py +679 -0
  45. package/core/daemon.py +184 -0
  46. package/core/requirements.txt +8 -0
  47. package/package.json +22 -0
package/core/daemon.py ADDED
@@ -0,0 +1,184 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ import uuid
5
+ import time
6
+ import httpx
7
+ import argparse
8
+ import base64
9
+ import logging
10
+ import signal
11
+ from dotenv import load_dotenv
12
+
13
+ load_dotenv()
14
+
15
+ # Add project root to python path so we can import app.colab_cli
16
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
17
+
18
+ from app.colab_cli.auth import AuthProvider, get_credentials
19
+ from app.colab_cli.client import Client, Prod, Variant, Accelerator, ColabRequestError, TooManyAssignmentsError
20
+ from app.colab_cli.common import State
21
+ from app.colab_cli.commands.session import spawn_keep_alive
22
+ from app.colab_cli.state import SessionState
23
+
24
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
25
+ logger = logging.getLogger("satellite")
26
+
27
+ MASTER_URL = os.getenv("MASTER_URL", "http://localhost:8090")
28
+ NODE_API_KEY = os.getenv("NODE_API_KEY", "satellite-secret-key-123")
29
+ NODE_ID = os.getenv("NODE_ID", f"node-{uuid.uuid4().hex[:6]}")
30
+
31
+ HEADERS = {"x-node-key": NODE_API_KEY}
32
+
33
+ async def fetch_token(email: str):
34
+ token_dir = os.path.expanduser("~/.config/colab-cli")
35
+ os.makedirs(token_dir, exist_ok=True)
36
+ safe = email.replace("@", "_at_").replace(".", "_")
37
+ token_path = os.path.join(token_dir, f"token_{safe}.json")
38
+
39
+ async with httpx.AsyncClient() as client:
40
+ res = await client.get(f"{MASTER_URL}/api/node/token/{email}", headers=HEADERS)
41
+ if res.status_code == 200:
42
+ with open(token_path, "w") as f:
43
+ f.write(res.text)
44
+ return token_path
45
+ raise Exception(f"Failed to fetch token: {res.text}")
46
+
47
+ async def report_status(email: str, status: str, colab_pid: int = None, worker_session_id: str = None, error: str = None):
48
+ async with httpx.AsyncClient() as client:
49
+ payload = {
50
+ "email": email,
51
+ "status": status,
52
+ "colab_pid": colab_pid,
53
+ "worker_session_id": worker_session_id,
54
+ "error": error
55
+ }
56
+ await client.post(f"{MASTER_URL}/api/node/status", json=payload, headers=HEADERS)
57
+
58
+ async def launch_worker(email: str, public_url: str):
59
+ logger.info("Launching worker for %s", email)
60
+
61
+ try:
62
+ token_path = await fetch_token(email)
63
+ except Exception as e:
64
+ logger.error("Failed to fetch token: %s", e)
65
+ await report_status(email, "FAILED", error=str(e))
66
+ return
67
+
68
+ _creds = get_credentials(provider=AuthProvider.OAUTH2, token_path=token_path)
69
+ _client = Client(Prod(), _creds)
70
+
71
+ _state = State()
72
+ _state.config_path = os.path.expanduser(f"~/.config/colab-cli/store_{email.replace('@', '_at_').replace('.', '_')}.json")
73
+ _state.auth_provider = AuthProvider.OAUTH2
74
+ _state._client = _client
75
+
76
+ name = f"worker-{NODE_ID}-{uuid.uuid4().hex[:4]}"
77
+ variant = Variant.GPU
78
+ accelerator = Accelerator.T4
79
+
80
+ loop = asyncio.get_running_loop()
81
+
82
+ try:
83
+ res = await loop.run_in_executor(None, _client.assign, uuid.uuid4(), variant, accelerator)
84
+ except Exception as e:
85
+ logger.error("Assign failed for %s: %s", email, e)
86
+ await report_status(email, "FAILED", error=str(e))
87
+ return
88
+
89
+ token = res.runtime_proxy_info.token if hasattr(res, "runtime_proxy_info") else getattr(res, "runtime_proxy_token", "")
90
+ url = res.runtime_proxy_info.url if hasattr(res, "runtime_proxy_info") else ""
91
+ endpoint = res.endpoint
92
+
93
+ s = SessionState(name=name, token=token, url=url, endpoint=endpoint, variant=variant.value, accelerator=accelerator.value)
94
+
95
+ try:
96
+ await loop.run_in_executor(None, _client.keep_alive_assignment, endpoint)
97
+ except Exception as e:
98
+ logger.warning("Keep-alive failed for %s: %s", email, e)
99
+
100
+ _state.store.add(s)
101
+ pid = spawn_keep_alive(endpoint, name, auth_provider=_state.auth_provider, config_path=_state.config_path, token_path=token_path)
102
+ s.keep_alive_pid = pid
103
+ _state.store.add(s)
104
+
105
+ deploy_wsid = str(uuid.uuid4())
106
+
107
+ try:
108
+ from app.colab_cli.runtime import ColabRuntime
109
+ _worker_bytes = open(os.path.join(os.path.dirname(__file__), "../colab/worker.py"), "rb").read()
110
+ _worker_b64 = base64.b64encode(_worker_bytes).decode()
111
+
112
+ _rt = ColabRuntime(url, token, session_name=name)
113
+ _deploy_code = f"""
114
+ import subprocess, sys, os, json, base64
115
+ subprocess.run([sys.executable, "-m", "pip", "install", "-q", "websockets", "httpx", "soundfile", "pydub", "omnivoice", "--no-deps", "transformers>=4.44.0"], capture_output=True, timeout=120)
116
+ print("DEPS_OK", flush=True)
117
+ worker_path = "/content/worker.py"
118
+ with open(worker_path, "wb") as f:
119
+ f.write(base64.b64decode("{_worker_b64}"))
120
+ env = os.environ.copy()
121
+ env["OMNIVOICE_NUM_STEP"] = "16"
122
+ env["OMNIVOICE_GUIDANCE_SCALE"] = "4.0"
123
+ proc = subprocess.Popen([sys.executable, worker_path,
124
+ "--server-url", "{public_url}",
125
+ "--email", "{email}",
126
+ "--worker-session-id", "{deploy_wsid}"], env=env, start_new_session=True,
127
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
128
+ import time
129
+ time.sleep(10)
130
+ ret = proc.poll()
131
+ print("DEPLOYED PID=" + str(proc.pid) + " ALIVE=" + str(ret is None), flush=True)
132
+ """
133
+ await loop.run_in_executor(None, lambda: _rt.execute_code(_deploy_code, timeout=300))
134
+ logger.info("Worker deployed on Colab runtime for %s", email)
135
+ except Exception as e:
136
+ logger.error("Worker deploy failed: %s", e)
137
+ await report_status(email, "FAILED", error=str(e))
138
+ return
139
+
140
+ await report_status(email, "READY", colab_pid=pid, worker_session_id=deploy_wsid)
141
+ running_workers[email] = {"pid": pid, "start_time": time.time()}
142
+
143
+ running_workers = {}
144
+
145
+ async def monitor_workers():
146
+ while True:
147
+ now = time.time()
148
+ for email, info in list(running_workers.items()):
149
+ if now - info["start_time"] > 9000: # 2.5 hours
150
+ logger.info("Auto-rotating worker for %s (reached 2.5h limit)", email)
151
+ try:
152
+ os.kill(info["pid"], signal.SIGTERM)
153
+ except Exception as e:
154
+ logger.debug("Failed to kill PID %s: %s", info["pid"], e)
155
+
156
+ await report_status(email, "FAILED", error="Auto-rotation limit reached")
157
+ del running_workers[email]
158
+
159
+ await asyncio.sleep(60)
160
+
161
+ async def main():
162
+ logger.info("Starting Satellite Daemon %s", NODE_ID)
163
+ logger.info("Master URL: %s", MASTER_URL)
164
+
165
+ asyncio.create_task(monitor_workers())
166
+
167
+ async with httpx.AsyncClient() as client:
168
+ while True:
169
+ try:
170
+ res = await client.get(f"{MASTER_URL}/api/node/jobs", headers=HEADERS)
171
+ if res.status_code == 200:
172
+ job = res.json().get("job")
173
+ if job:
174
+ email = job["email"]
175
+ public_url = job.get("public_url") or MASTER_URL
176
+ # Launch in background so we don't block polling
177
+ asyncio.create_task(launch_worker(email, public_url))
178
+ except Exception as e:
179
+ logger.debug("Polling error: %s", e)
180
+
181
+ await asyncio.sleep(5)
182
+
183
+ if __name__ == "__main__":
184
+ asyncio.run(main())
@@ -0,0 +1,8 @@
1
+ httpx==0.27.2
2
+ websockets==12.0
3
+ google-auth-oauthlib==1.2.0
4
+ typer>=0.12.0
5
+ nbformat>=5.9.0
6
+ websocket-client>=1.7.0
7
+ filelock>=3.13.0
8
+ python-dotenv>=1.0.0
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ttsd-colabcli",
3
+ "version": "1.0.0",
4
+ "description": "Satellite Node CLI for TTS Dubbing Distributed Architecture",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "colabcli": "./cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node cli.js"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "tts",
15
+ "worker"
16
+ ],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "inquirer": "^8.2.5"
21
+ }
22
+ }