refactor(console): route interactive prompts through a themed helper

Adds prompt_user() and uses it for local, remote, and Proton VPN input. Replaces click.prompt on the remote auth path, which ignored the theme and overlapped active Live displays. Remote auth now exits on EOF instead of POSTing an empty answer and spinning the poll loop.
This commit is contained in:
imSp4rky
2026-08-15 18:13:05 -06:00
parent a6cb0231d5
commit db4f0b39f9
6 changed files with 30 additions and 14 deletions
+1 -1
View File
@@ -242,7 +242,7 @@ use `self.request_input(prompt)`, never a bare `input()`. Under `serve` mode
there is no local terminal, so a bare `input()` would hang the server waiting on
stdin that never arrives; `request_input` instead relays the prompt to the
remote client through the attached `InputBridge`. Locally it routes through the
shared Rich console (`console.input`) so the prompt renders correctly alongside
shared Rich console (`prompt_user`) so the prompt renders correctly alongside
progress and log output.
!!! warning "Key token caches by whatever varies per session"
+2 -1
View File
@@ -398,7 +398,8 @@ session API and a good template for any client.
- **Retries.** The download-side HTTP session mounts an adapter with
`Retry(total=5, backoff_factor=0.2, status_forcelist=[429, 500, 502, 503, 504])`.
- **Flow.** `authenticate()` → `create` (+ poll `prompt` every 2s up to a 600s
deadline, answering `pending_input` prompts) → `get_titles()` → `get_tracks()`
deadline, answering `pending_input` prompts; with stdin closed the client logs an
error and raises `SystemExit(1)` instead of posting an empty answer) → `get_titles()` → `get_tracks()`
(merging returned `session_headers`/`session_cookies`, re-parsing `manifests`) →
license via proxy or `server_cdm` → `close()` (`DELETE`, saving any returned
`cache`).
+17 -1
View File
@@ -427,4 +427,20 @@ console = ComfyConsole(
)
__all__ = ("ComfyLogRenderer", "ComfyRichHandler", "ComfyConsole", "GradientPulseBarColumn", "SyncLive", "console")
def prompt_user(prompt: str) -> str:
"""Ask the user for input on the shared console, themed and indented like the rest of the output."""
indent = " " * 5
body = Text(indent + prompt.rstrip("\n ").replace("\n", "\n" + indent), style="text")
body.append("\n" + indent + "> ", style="rule.text")
return console.input(body)
__all__ = (
"ComfyLogRenderer",
"ComfyRichHandler",
"ComfyConsole",
"GradientPulseBarColumn",
"SyncLive",
"console",
"prompt_user",
)
+2 -4
View File
@@ -11,10 +11,9 @@ from pathlib import Path
from typing import Optional
import requests
from rich.text import Text
from unshackle.core.config import config
from unshackle.core.console import console
from unshackle.core.console import prompt_user
from unshackle.core.proxies.proxy import Proxy
log = logging.getLogger("proxies.proton")
@@ -326,13 +325,12 @@ class ProtonVPN(Proxy):
log.error("Proton: unexpected TV login response")
return False
indent = " " * 5
prompt = (
"Proton: open https://account.proton.me/vpn/tv/code\n"
f"Enter code {user_code}, then press Enter to continue..."
)
try:
console.input(Text(indent + prompt.replace("\n", "\n" + indent), style="text"))
prompt_user(prompt)
except EOFError:
return False
+6 -2
View File
@@ -22,7 +22,7 @@ from rich.padding import Padding
from rich.rule import Rule
from unshackle.core.config import config
from unshackle.core.console import console
from unshackle.core.console import console, prompt_user
from unshackle.core.constants import AnyTrack
from unshackle.core.credential import Credential
from unshackle.core.titles import Title_T, Titles_T, remap_titles
@@ -616,7 +616,11 @@ class RemoteService:
if status == "pending_input":
prompt = resp.get("prompt", "Enter input: ")
user_response = click.prompt(prompt.rstrip("\n "), default="", show_default=False)
try:
user_response = prompt_user(prompt)
except EOFError:
log.error("Remote auth needs interactive input but stdin is closed")
raise SystemExit(1) from None
self.client.post(
f"/api/session/{self._session_id}/prompt",
{"response": user_response},
+2 -5
View File
@@ -16,11 +16,10 @@ import requests
from requests.adapters import HTTPAdapter, Retry
from rich.padding import Padding
from rich.rule import Rule
from rich.text import Text
from unshackle.core.cacher import Cacher
from unshackle.core.config import config
from unshackle.core.console import console
from unshackle.core.console import console, prompt_user
from unshackle.core.constants import AnyTrack
from unshackle.core.credential import Credential
from unshackle.core.drm import DRM_T
@@ -369,9 +368,7 @@ class Service(metaclass=ABCMeta):
"""
if self._input_bridge is not None:
return self._input_bridge.request_input(prompt)
indent = " " * 5
padded = indent + prompt.replace("\n", "\n" + indent)
return console.input(Text(padded, style="text"))
return prompt_user(prompt)
def search(self) -> Generator[SearchResult, None, None]:
"""