fix(platforms): QR fallback tip names Hermes' own uv when it is not on PATH

The installer drops uv in $HERMES_HOME/bin without exporting it, so the
bare `uv pip install --python ...` tip failed with `uv: command not found`
for installer-only users. The four copies of the tip (QQ Bot, Feishu, WeCom,
managed Telegram bot) now render through one helper, managed_uv.pip_install_hint,
which names the managed binary when present and falls back to `uv` otherwise.
This commit is contained in:
teknium1
2026-09-15 15:15:11 -07:00
committed by Teknium
parent c2b93ae5ac
commit 6062ad5aee
6 changed files with 32 additions and 7 deletions
+2 -2
View File
@@ -4,7 +4,6 @@
from __future__ import annotations
import logging
import sys
import time
from enum import IntEnum
from typing import Optional, Tuple
@@ -98,7 +97,8 @@ def qr_register(timeout_seconds: int = 600) -> Optional[dict]:
print(f" Scan the QR code above, or open this URL directly:\n {url}")
else:
print(f" Open this URL in QQ on your phone:\n {url}")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here")
from hermes_cli.managed_uv import pip_install_hint
print(f" Tip: {pip_install_hint('qrcode')} to display a scannable QR code here")
print()
while time.monotonic() < deadline:
try:
+9
View File
@@ -52,6 +52,15 @@ def resolve_uv() -> Optional[str]:
return str(p) if p.is_file() and os.access(p, os.X_OK) else None
def pip_install_hint(package: str) -> str:
"""Copy-pasteable command that installs *package* into the running interpreter.
Names Hermes' own uv when it exists: the installer drops it in ``$HERMES_HOME/bin``
without putting that on PATH, so a bare ``uv`` would fail for installer-only users.
"""
return f"{resolve_uv() or 'uv'} pip install --python {sys.executable} {package}"
def managed_python_install_dir(project_root: Path | None = None) -> Path:
"""Return the checkout-scoped Python store shared by all profiles."""
root = Path(project_root) if project_root is not None else _PROJECT_ROOT
+2 -1
View File
@@ -76,7 +76,8 @@ def render_qr_terminal(url: str) -> str:
def print_qr_code(url: str, *, include_link: bool = True) -> None:
"""Print a QR code to stdout, with URL fallback if qrcode is missing."""
print(render_qr_terminal(url) or f" (Install 'qrcode' for a scannable QR code: uv pip install --python {sys.executable} qrcode)")
from hermes_cli.managed_uv import pip_install_hint
print(render_qr_terminal(url) or f" (Install 'qrcode' for a scannable QR code: {pip_install_hint('qrcode')})")
if include_link:
print(f" Link: {url}")
+2 -2
View File
@@ -30,7 +30,6 @@ import logging
import mimetypes
import os
import re
import sys
import threading
import time
import uuid
@@ -4104,7 +4103,8 @@ def _qr_register_inner(*, initial_domain: str, timeout_seconds: int) -> Optional
print(f"\n Scan the QR code above, or open this URL directly:\n {qr_url}")
else:
print(f" Open this URL in Feishu / Lark on your phone:\n\n {qr_url}\n")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here next time")
from hermes_cli.managed_uv import pip_install_hint
print(f" Tip: {pip_install_hint('qrcode')} to display a scannable QR code here next time")
print()
result = _poll_registration(
device_code=begin["device_code"], interval=begin["interval"],
+2 -2
View File
@@ -10,7 +10,6 @@ import asyncio
import json
import logging
import re
import sys
import time
import uuid
from datetime import datetime, timezone
@@ -675,7 +674,8 @@ def qr_scan_for_bot_info(*, timeout_seconds: int = _QR_POLL_TIMEOUT) -> Optional
print(f"\n Scan the QR code above, or open this URL directly:\n {page_url}")
except Exception:
print(f" Open this URL in WeCom on your phone:\n\n {page_url}\n")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here next time")
from hermes_cli.managed_uv import pip_install_hint
print(f" Tip: {pip_install_hint('qrcode')} to display a scannable QR code here next time")
print("\n Fetching configuration results...", end="", flush=True)
deadline = time.monotonic() + timeout_seconds
query_url = f"{_QR_QUERY_URL}?scode={urllib.parse.quote(scode)}"
+15
View File
@@ -184,6 +184,21 @@ class TestResolveUv:
assert resolve_uv() is None
class TestPipInstallHint:
def test_names_the_managed_uv_when_present(self, tmp_path):
uv = tmp_path / "bin" / _UV_BINARY_NAME
_make_executable(uv)
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path):
from hermes_cli.managed_uv import pip_install_hint
assert pip_install_hint("qrcode") == f"{uv} pip install --python {sys.executable} qrcode"
def test_falls_back_to_bare_uv_when_absent(self, tmp_path):
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path):
from hermes_cli.managed_uv import pip_install_hint
assert pip_install_hint("qrcode") == f"uv pip install --python {sys.executable} qrcode"
# ---------------------------------------------------------------------------
# ensure_uv
# ---------------------------------------------------------------------------