refactor(hermes_cli): hug lone closing brackets (AST-neutral) in desktop/dashboard/web_build/tui_launch
This commit is contained in:
@@ -70,8 +70,7 @@ _SYSTEMCTL_ERRORS = (FileNotFoundError, subprocess.TimeoutExpired, OSError)
|
||||
def _run_probe(cmd: list[str], *, timeout: int) -> subprocess.CompletedProcess:
|
||||
"""Captured, text-decoded ``subprocess.run`` for short local probes (systemctl, ps)."""
|
||||
return subprocess.run(
|
||||
cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout
|
||||
)
|
||||
cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
|
||||
|
||||
|
||||
def _restart_managed_dashboard_service(reason: str, unit: str = _DASHBOARD_SYSTEMD_UNIT) -> bool:
|
||||
@@ -263,8 +262,7 @@ def _respawn_dashboard_processes(commands: list[list[str]]) -> list[list[str]]:
|
||||
with open(log_path, "ab") as log_f:
|
||||
subprocess.Popen(
|
||||
command, stdin=subprocess.DEVNULL, stdout=log_f, stderr=subprocess.STDOUT,
|
||||
start_new_session=True, close_fds=True,
|
||||
)
|
||||
start_new_session=True, close_fds=True)
|
||||
respawned.append(command)
|
||||
except (OSError, ValueError) as exc:
|
||||
failed.append((command, str(exc)))
|
||||
@@ -335,8 +333,7 @@ def _install_hangup_protection(gateway_mode: bool = False):
|
||||
(stdio wrapped in ``_UpdateOutputStream``). SIGINT/SIGTERM are left alone — legitimate cancels.
|
||||
No-op in gateway mode (already detached). Returns state for ``_finalize_update_output``."""
|
||||
state = {
|
||||
"prev_stdout": sys.stdout, "prev_stderr": sys.stderr, "log_file": None, "installed": False
|
||||
}
|
||||
"prev_stdout": sys.stdout, "prev_stderr": sys.stderr, "log_file": None, "installed": False}
|
||||
|
||||
if gateway_mode:
|
||||
return state
|
||||
@@ -700,8 +697,7 @@ def _route_named_profile_dashboard(args, _headless_backend: bool, _ssh_owner_non
|
||||
"serve" if _headless_backend else "dashboard",
|
||||
"--port", str(args.port),
|
||||
"--host", args.host,
|
||||
"--open-profile", _launch_profile,
|
||||
]
|
||||
"--open-profile", _launch_profile]
|
||||
if _ssh_owner_nonce:
|
||||
reexec_argv.extend(["--ssh-owner-nonce", _ssh_owner_nonce])
|
||||
if _token_file:
|
||||
|
||||
@@ -122,8 +122,7 @@ def _write_desktop_build_stamp(project_root: Path, *, source_mode: bool) -> None
|
||||
from hermes_cli.main import _desktop_stamp_path
|
||||
_write_build_stamp(
|
||||
_desktop_stamp_path(), "desktop",
|
||||
lambda: _compute_desktop_content_hash(project_root), sourceMode=source_mode,
|
||||
)
|
||||
lambda: _compute_desktop_content_hash(project_root), sourceMode=source_mode)
|
||||
|
||||
|
||||
def _desktop_packaged_executable(desktop_dir: Path) -> Optional[Path]:
|
||||
@@ -242,15 +241,13 @@ def _windows_native_machine_from_iswow64() -> Optional[str]:
|
||||
kernel32.GetCurrentProcess.restype = wintypes.HANDLE
|
||||
kernel32.GetCurrentProcess.argtypes = []
|
||||
kernel32.IsWow64Process2.argtypes = [
|
||||
wintypes.HANDLE, ctypes.POINTER(wintypes.USHORT), ctypes.POINTER(wintypes.USHORT),
|
||||
]
|
||||
wintypes.HANDLE, ctypes.POINTER(wintypes.USHORT), ctypes.POINTER(wintypes.USHORT)]
|
||||
kernel32.IsWow64Process2.restype = wintypes.BOOL
|
||||
|
||||
process_machine = wintypes.USHORT(0)
|
||||
native_machine = wintypes.USHORT(0)
|
||||
if not kernel32.IsWow64Process2(
|
||||
kernel32.GetCurrentProcess(), ctypes.byref(process_machine), ctypes.byref(native_machine)
|
||||
):
|
||||
kernel32.GetCurrentProcess(), ctypes.byref(process_machine), ctypes.byref(native_machine)):
|
||||
return None
|
||||
return _PE_MACHINE_TO_NAME.get(native_machine.value)
|
||||
|
||||
@@ -547,8 +544,7 @@ def _electron_pkg_staged_missing_dist(project_root: Path) -> bool:
|
||||
return (
|
||||
(electron_dir / "package.json").is_file()
|
||||
and (electron_dir / "install.js").is_file()
|
||||
and not _electron_dist_ok(project_root)
|
||||
)
|
||||
and not _electron_dist_ok(project_root))
|
||||
|
||||
|
||||
def _redownload_electron_dist(project_root: Path, env: dict, *, mirror: Optional[str] = None) -> bool:
|
||||
@@ -652,8 +648,7 @@ def _desktop_macos_bundle_id(bundle: Path) -> Optional[str]:
|
||||
info = bundle / "Contents" / "Info.plist"
|
||||
if not info.exists() and bundle.suffix == ".framework":
|
||||
candidates = list(bundle.glob("Versions/*/Resources/Info.plist")) + list(
|
||||
bundle.glob("Resources/Info.plist")
|
||||
)
|
||||
bundle.glob("Resources/Info.plist"))
|
||||
if candidates:
|
||||
info = candidates[0]
|
||||
if not info.exists():
|
||||
@@ -690,8 +685,7 @@ def _desktop_macos_local_signing_identity() -> Optional[str]:
|
||||
|
||||
def _codesign_verify(codesign: str, app: Path, **kwargs) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
[codesign, "--verify", "--deep", "--strict", str(app)], capture_output=True, **kwargs
|
||||
)
|
||||
[codesign, "--verify", "--deep", "--strict", str(app)], capture_output=True, **kwargs)
|
||||
|
||||
|
||||
def _desktop_macos_has_valid_real_signature(app: Path) -> bool:
|
||||
@@ -702,8 +696,7 @@ def _desktop_macos_has_valid_real_signature(app: Path) -> bool:
|
||||
return False
|
||||
try:
|
||||
info = subprocess.run(
|
||||
[codesign, "-dv", str(app)], check=False, capture_output=True, text=True
|
||||
)
|
||||
[codesign, "-dv", str(app)], check=False, capture_output=True, text=True)
|
||||
output = f"{info.stdout}\n{info.stderr}"
|
||||
if info.returncode != 0 or "TeamIdentifier=" not in output or "TeamIdentifier=not set" in output:
|
||||
return False
|
||||
@@ -731,8 +724,7 @@ def _desktop_macos_local_codesign(app: Path, *, desktop_dir: Path, identity: str
|
||||
|
||||
def sign_path(
|
||||
path: Path, *, entitlements: Optional[Path] = None, identifier: Optional[str] = None,
|
||||
runtime: bool = True,
|
||||
) -> None:
|
||||
runtime: bool = True) -> None:
|
||||
args = [codesign, "--force", "--sign", identity, "--timestamp=none"]
|
||||
if runtime:
|
||||
args += ["--options", "runtime"]
|
||||
@@ -808,8 +800,7 @@ def _macos_legacy_adhoc_resign(codesign: str, app: Path) -> bool:
|
||||
|
||||
def _desktop_macos_relaunchable_fixup(
|
||||
desktop_dir: Path, *, publisher_signing_configured: Optional[bool] = None,
|
||||
release_dir: Optional[Path] = None,
|
||||
) -> bool:
|
||||
release_dir: Optional[Path] = None) -> bool:
|
||||
"""Re-sign a locally-built macOS app so in-place self-update doesn't reset TCC grants.
|
||||
|
||||
A rebuilt ad-hoc bundle (new cdhash, no stable Designated Requirement) reports
|
||||
@@ -825,8 +816,7 @@ def _desktop_macos_relaunchable_fixup(
|
||||
return True
|
||||
if publisher_signing_configured is None:
|
||||
publisher_signing_configured = bool(
|
||||
os.environ.get("CSC_LINK") or os.environ.get("APPLE_SIGNING_IDENTITY")
|
||||
)
|
||||
os.environ.get("CSC_LINK") or os.environ.get("APPLE_SIGNING_IDENTITY"))
|
||||
if publisher_signing_configured:
|
||||
return True
|
||||
exe = _desktop_packaged_executable_in(release_dir or (desktop_dir / "release"))
|
||||
@@ -872,8 +862,7 @@ def _macos_codesigning_identity_valid(security: str, identity: str) -> bool:
|
||||
|
||||
|
||||
def _macos_create_signing_identity(
|
||||
openssl: str, security: str, codesign: str, keychain: str, identity: str
|
||||
) -> bool:
|
||||
openssl: str, security: str, codesign: str, keychain: str, identity: str) -> bool:
|
||||
"""Create a self-signed code-signing cert (10 years), import it with codesign access, trust it for codeSign."""
|
||||
tmp_dir = Path(tempfile.mkdtemp(prefix="hermes-tcc-"))
|
||||
try:
|
||||
@@ -890,8 +879,7 @@ def _macos_create_signing_identity(
|
||||
"-addext", "keyUsage=critical,digitalSignature,keyCertSign",
|
||||
"-addext", "extendedKeyUsage=codeSigning",
|
||||
],
|
||||
capture_output=True, check=True,
|
||||
)
|
||||
capture_output=True, check=True)
|
||||
|
||||
# OpenSSL 3 defaults to AES/SHA-2 PKCS#12 that `security import` rejects
|
||||
# with "MAC verification failed". `-legacy` restores the accepted
|
||||
@@ -904,8 +892,7 @@ def _macos_create_signing_identity(
|
||||
"-inkey", str(key), "-in", str(crt),
|
||||
"-out", str(p12), "-passout", "pass:hermeslocal",
|
||||
],
|
||||
capture_output=True, check=True,
|
||||
)
|
||||
capture_output=True, check=True)
|
||||
|
||||
def _import_p12():
|
||||
return subprocess.run(
|
||||
@@ -914,8 +901,7 @@ def _macos_create_signing_identity(
|
||||
"-P", "hermeslocal",
|
||||
"-T", codesign, "-T", "/usr/bin/codesign_allocate",
|
||||
],
|
||||
capture_output=True, text=True, check=False,
|
||||
)
|
||||
capture_output=True, text=True, check=False)
|
||||
|
||||
_export_p12([])
|
||||
imported = _import_p12()
|
||||
@@ -935,8 +921,7 @@ def _macos_create_signing_identity(
|
||||
# command exists to front-load.
|
||||
trusted = subprocess.run(
|
||||
[security, "add-trusted-cert", "-r", "trustRoot", "-p", "codeSign", "-k", keychain, str(crt)],
|
||||
capture_output=True, text=True, check=False,
|
||||
)
|
||||
capture_output=True, text=True, check=False)
|
||||
if trusted.returncode != 0:
|
||||
print(
|
||||
" (could not trust the certificate for code signing: "
|
||||
@@ -1062,8 +1047,7 @@ def _desktop_linux_userns_sandbox_available() -> bool:
|
||||
[unshare, "--user", "--map-root-user", "true"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5, check=False,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
== 0)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return False
|
||||
|
||||
@@ -1159,8 +1143,7 @@ def _detect_linux_password_store() -> str | None:
|
||||
"org.freedesktop.DBus.Peer.Ping",
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=5,
|
||||
)
|
||||
timeout=5)
|
||||
if result.returncode == 0:
|
||||
return "gnome-libsecret"
|
||||
except Exception:
|
||||
@@ -1285,8 +1268,7 @@ def _run_desktop_pack_with_recovery(
|
||||
build_result.returncode != 0
|
||||
and staging_dir is not None
|
||||
and not env.get("ELECTRON_MIRROR")
|
||||
and _staged_exe() is None
|
||||
):
|
||||
and _staged_exe() is None):
|
||||
print(" ⚠ Desktop build still failing; the Electron download from "
|
||||
"GitHub looks blocked. Re-downloading via a public mirror "
|
||||
"(npmmirror.com)... (set ELECTRON_MIRROR to use another mirror)")
|
||||
@@ -1396,8 +1378,7 @@ def _desktop_launch_env(args: argparse.Namespace) -> tuple[dict, list[str]]:
|
||||
env["HERMES_DESKTOP_CWD"] = os.getcwd()
|
||||
|
||||
config_electron_flags, config_disable_gpu, config_password_store, config_ozone_hint = (
|
||||
_desktop_launch_options()
|
||||
)
|
||||
_desktop_launch_options())
|
||||
if config_disable_gpu != "auto" and "HERMES_DESKTOP_DISABLE_GPU" not in os.environ:
|
||||
env["HERMES_DESKTOP_DISABLE_GPU"] = config_disable_gpu
|
||||
if config_ozone_hint != "auto" and "ELECTRON_OZONE_PLATFORM_HINT" not in os.environ:
|
||||
|
||||
@@ -54,8 +54,7 @@ def _print_tui_exit_summary(session_id: Optional[str], active_session_file: Opti
|
||||
return # No real conversation — don't show resume info
|
||||
tokens = {
|
||||
k: int(session.get(f"{k}_tokens") or 0)
|
||||
for k in ("input", "output", "cache_read", "cache_write", "reasoning")
|
||||
}
|
||||
for k in ("input", "output", "cache_read", "cache_write", "reasoning")}
|
||||
except Exception:
|
||||
return
|
||||
finally:
|
||||
@@ -99,8 +98,7 @@ def _workspace_root(dir: Path) -> Path:
|
||||
if (
|
||||
(dir / "package.json").is_file()
|
||||
and not (dir / "package-lock.json").is_file()
|
||||
and (dir.parent / "package-lock.json").is_file()
|
||||
):
|
||||
and (dir.parent / "package-lock.json").is_file()):
|
||||
return dir.parent
|
||||
return dir
|
||||
|
||||
@@ -340,8 +338,7 @@ def _ensure_tui_node() -> None:
|
||||
result = subprocess.run(
|
||||
["bash", "-c", f'source "{helper}" >&2 && ensure_node >&2 && command -v node'],
|
||||
env={**os.environ, "HERMES_HOME": hermes_home},
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace", check=False,
|
||||
)
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace", check=False)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return
|
||||
|
||||
@@ -378,8 +375,7 @@ def _restore_tui_workspace(tui_dir: Path) -> bool:
|
||||
try:
|
||||
subprocess.run(
|
||||
[git, "restore", "--", tui_dir.name], cwd=str(tui_dir.parent), capture_output=True,
|
||||
text=True, encoding="utf-8", errors="replace", check=False,
|
||||
)
|
||||
text=True, encoding="utf-8", errors="replace", check=False)
|
||||
except OSError:
|
||||
return False
|
||||
return tui_dir.is_dir()
|
||||
@@ -405,8 +401,7 @@ def _ensure_tui_workspace(tui_dir: Path) -> None:
|
||||
" 2. Run `npm install --silent --no-fund --no-audit --progress=false`\n"
|
||||
" 3. Retry `hermes --tui`\n"
|
||||
"If the checkout is still inconsistent, run `hermes update --force`.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -457,8 +452,7 @@ def _run_tui_npm_build(npm: str, cwd: Path, failure_message: str) -> None:
|
||||
"""``npm run build`` in *cwd*; exit with *failure_message* + output tail on failure."""
|
||||
result = subprocess.run(
|
||||
[npm, "run", "build"], cwd=str(cwd), capture_output=True, text=True, encoding="utf-8",
|
||||
errors="replace", env=_npm_lifecycle_env(),
|
||||
)
|
||||
errors="replace", env=_npm_lifecycle_env())
|
||||
_exit_on_npm_failure(result, failure_message, sep="")
|
||||
|
||||
|
||||
@@ -489,8 +483,7 @@ def _install_tui_dependencies(tui_dir: Path, *, termux_startup: bool) -> None:
|
||||
return subprocess.run(
|
||||
npm_install_cmd, cwd=str(npm_cwd), stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, encoding="utf-8", errors="replace",
|
||||
env=_npm_lifecycle_env(with_hermes_node_path()),
|
||||
)
|
||||
env=_npm_lifecycle_env(with_hermes_node_path()))
|
||||
|
||||
result = _run_tui_install()
|
||||
if result.returncode != 0:
|
||||
@@ -517,8 +510,7 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
|
||||
f"Error: --dev is incompatible with HERMES_TUI_DIR={ext_dir}\n"
|
||||
f"The prebuilt TUI has no source code to hot-reload.\n"
|
||||
f"Unset HERMES_TUI_DIR (e.g. `unset HERMES_TUI_DIR`) to use --dev from a checkout.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Prebuilt bundle (nix / packaged release / Docker image): just run it.
|
||||
@@ -700,8 +692,7 @@ def _launch_tui(
|
||||
provider: Optional[str] = None, toolsets: object = None, skills: object = None,
|
||||
verbose: Optional[bool] = None, quiet: bool = False, query: Optional[str] = None,
|
||||
image: Optional[str] = None, worktree: bool = False, checkpoints: bool = False,
|
||||
pass_session_id: bool = False, max_turns: Optional[int] = None, accept_hooks: bool = False,
|
||||
):
|
||||
pass_session_id: bool = False, max_turns: Optional[int] = None, accept_hooks: bool = False):
|
||||
"""Replace current process with the TUI."""
|
||||
from hermes_cli.main import PROJECT_ROOT, _apply_tui_python_env, _make_tui_argv, _resolve_tui_heap_mb
|
||||
tui_dir = PROJECT_ROOT / "ui-tui"
|
||||
@@ -748,8 +739,7 @@ def _launch_tui(
|
||||
("HERMES_TUI_PASS_SESSION_ID", "1" if pass_session_id else None),
|
||||
("HERMES_TUI_MAX_TURNS", str(max_turns) if max_turns is not None else None),
|
||||
("HERMES_TUI_TOOL_PROGRESS", "verbose" if verbose else "off" if quiet else None),
|
||||
("HERMES_ACCEPT_HOOKS", "1" if accept_hooks else None),
|
||||
):
|
||||
("HERMES_ACCEPT_HOOKS", "1" if accept_hooks else None)):
|
||||
if value:
|
||||
env[key] = value
|
||||
# Generous V8 heap (8GB target; default cap can fatal-OOM on long sessions),
|
||||
|
||||
@@ -20,8 +20,7 @@ import time as _time
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
from hermes_cli.main_tui_launch import (
|
||||
_npm_lifecycle_env, _termux_workspace_install_context, _workspace_root
|
||||
)
|
||||
_npm_lifecycle_env, _termux_workspace_install_context, _workspace_root)
|
||||
|
||||
# Log-record parity with the origin module.
|
||||
logger = logging.getLogger("hermes_cli.main")
|
||||
@@ -178,8 +177,7 @@ def _web_ui_build_needed(web_dir: Path) -> bool:
|
||||
if not sentinel.exists():
|
||||
return True
|
||||
return not _stamp_is_current(
|
||||
_web_ui_stamp_path(), lambda: _compute_web_ui_content_hash(project_root, web_dir)
|
||||
)
|
||||
_web_ui_stamp_path(), lambda: _compute_web_ui_content_hash(project_root, web_dir))
|
||||
|
||||
|
||||
def _compute_web_ui_content_hash(project_root: Path, web_dir: Path) -> str:
|
||||
@@ -196,8 +194,7 @@ def _web_ui_stamp_path() -> Path:
|
||||
def _write_web_ui_build_stamp(project_root: Path, web_dir: Path) -> None:
|
||||
"""Write the web UI build stamp after a successful build."""
|
||||
_write_build_stamp(
|
||||
_web_ui_stamp_path(), "web UI", lambda: _compute_web_ui_content_hash(project_root, web_dir)
|
||||
)
|
||||
_web_ui_stamp_path(), "web UI", lambda: _compute_web_ui_content_hash(project_root, web_dir))
|
||||
|
||||
|
||||
def _console_print(text: str) -> None:
|
||||
@@ -211,8 +208,7 @@ def _console_print(text: str) -> None:
|
||||
|
||||
def _run_with_idle_timeout(
|
||||
cmd: list[str], cwd: Path, *, idle_timeout_seconds: int = 180, indent: str = " ",
|
||||
env: dict[str, str] | None = None,
|
||||
) -> subprocess.CompletedProcess:
|
||||
env: dict[str, str] | None = None) -> subprocess.CompletedProcess:
|
||||
"""Stream a subprocess, killing it after *idle_timeout_seconds* of silence (a silent captured
|
||||
Vite build on a low-memory host looks like a hang and users reboot mid-install). Returns merged
|
||||
stdout, empty stderr, rc 124 if terminate raced a clean exit; never raises on idle timeout."""
|
||||
@@ -223,8 +219,7 @@ def _run_with_idle_timeout(
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, encoding="utf-8", errors="replace", bufsize=1, env=env,
|
||||
)
|
||||
text=True, encoding="utf-8", errors="replace", bufsize=1, env=env)
|
||||
except OSError as exc:
|
||||
# E.g. npm not on PATH between the which() check and now.
|
||||
return subprocess.CompletedProcess(cmd, 127, stdout="", stderr=str(exc))
|
||||
@@ -309,8 +304,7 @@ def _nixos_build_env() -> dict[str, str] | None:
|
||||
|
||||
def _run_npm_install_deterministic(
|
||||
npm: str, cwd: Path, *, extra_args: tuple[str, ...] = (), capture_output: bool = True,
|
||||
env: dict[str, str] | None = None,
|
||||
) -> subprocess.CompletedProcess:
|
||||
env: dict[str, str] | None = None) -> subprocess.CompletedProcess:
|
||||
"""Deterministic npm install that never mutates ``package-lock.json``.
|
||||
|
||||
``npm ci`` when a lockfile exists, else/on failure ``npm install --no-save``
|
||||
|
||||
Reference in New Issue
Block a user