Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
"""Pre-import startup fast paths — THE canonical lightweight helpers.
|
||||
|
||||
This module is imported by ``hermes_cli/main.py`` BEFORE its heavy import
|
||||
wall (config, argparse tree, logging, providers). Everything here must stay
|
||||
**stdlib-only and cheap** (os/sys file probes; no yaml, no hermes_cli.config,
|
||||
no argparse). A guard test (``test_startup_fast_import_weight``) subprocess-
|
||||
imports this module and fails if any heavy module sneaks into sys.modules.
|
||||
|
||||
Why this module exists (the bug class it kills): version-printing kept being
|
||||
reimplemented as ``*_fast()`` copies at the top of main.py (Termux first,
|
||||
then globally), each duplicating canonical logic — project-root resolution,
|
||||
container detection, profile detection. The copies drifted: eb4040242
|
||||
changed the canonical output and referenced ``PROJECT_ROOT`` inside the fast
|
||||
function, which doesn't exist yet on the fast path → the Termux fast path
|
||||
NameError'd on --version and nobody noticed. One implementation, imported
|
||||
by both the fast path and the module constants, makes that drift
|
||||
structurally impossible; the parity guard test would have caught eb4040242
|
||||
the day it landed.
|
||||
|
||||
``hermes_cli/config.py``'s ``get_container_exec_info()`` reads the same
|
||||
``.container-mode`` file; keep the file-format assumptions here and there in
|
||||
sync (this module deliberately only PROBES existence/typos cheaply and errs
|
||||
toward the slow path, which then does the authoritative parse).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = [
|
||||
"project_root_str",
|
||||
"ensure_project_root_on_path",
|
||||
"is_termux_env",
|
||||
"is_termux_fast_version_argv",
|
||||
"is_global_fast_version_argv",
|
||||
"is_container_startup_environment",
|
||||
"active_profile_may_override_home",
|
||||
"container_mode_may_be_active",
|
||||
"read_openai_version",
|
||||
"read_install_method",
|
||||
"print_fast_version_info",
|
||||
"try_fast_version",
|
||||
]
|
||||
|
||||
|
||||
def project_root_str() -> str:
|
||||
"""Repo root as a str — the single source for main.py's PROJECT_ROOT."""
|
||||
return os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
|
||||
def ensure_project_root_on_path() -> None:
|
||||
"""Put the project root at sys.path[0], deduping realpath-equivalents."""
|
||||
project_root = project_root_str()
|
||||
normalized_root = os.path.normcase(os.path.realpath(project_root))
|
||||
sys.path[:] = [
|
||||
entry
|
||||
for entry in sys.path
|
||||
if not entry
|
||||
or os.path.normcase(os.path.realpath(entry)) != normalized_root
|
||||
]
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
|
||||
def is_termux_env() -> bool:
|
||||
"""Tiny Termux check for pre-import startup shortcuts."""
|
||||
prefix = os.environ.get("PREFIX", "")
|
||||
return bool(
|
||||
os.environ.get("TERMUX_VERSION")
|
||||
or "com.termux/files/usr" in prefix
|
||||
or prefix.startswith("/data/data/com.termux/")
|
||||
)
|
||||
|
||||
|
||||
def is_termux_fast_version_argv(argv: list[str]) -> bool:
|
||||
return argv in (["--version"], ["-V"])
|
||||
|
||||
|
||||
def is_global_fast_version_argv(argv: list[str]) -> bool:
|
||||
return argv in (["--version"], ["-V"])
|
||||
|
||||
|
||||
def is_container_startup_environment() -> bool:
|
||||
"""True when we're already INSIDE a container (fast path is then safe)."""
|
||||
if os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv"):
|
||||
return True
|
||||
try:
|
||||
with open("/proc/1/cgroup", encoding="utf-8") as handle:
|
||||
cgroup = handle.read()
|
||||
except OSError:
|
||||
return False
|
||||
return "docker" in cgroup or "podman" in cgroup or "/lxc/" in cgroup
|
||||
|
||||
|
||||
def active_profile_may_override_home(hermes_root: str) -> bool:
|
||||
"""Cheap probe: does an active non-default profile redirect HERMES_HOME?"""
|
||||
active_profile = os.path.join(hermes_root, "active_profile")
|
||||
try:
|
||||
if os.path.exists(active_profile):
|
||||
with open(active_profile, encoding="utf-8") as handle:
|
||||
active = handle.read().strip()
|
||||
return bool(active and active != "default")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def _resolved_home() -> str:
|
||||
hermes_home = os.environ.get("HERMES_HOME", "").strip()
|
||||
if hermes_home:
|
||||
return hermes_home
|
||||
return os.path.join(os.path.expanduser("~"), ".hermes")
|
||||
|
||||
|
||||
def container_mode_may_be_active() -> bool:
|
||||
"""Conservative probe for NixOS container-mode routing.
|
||||
|
||||
False positives are fine (we fall through to the slow path, whose
|
||||
``get_container_exec_info()`` does the authoritative check and routes
|
||||
into the container). False negatives are NOT fine — they'd print the
|
||||
host's version instead of the container's. Hence: any profile
|
||||
ambiguity → assume container mode may be active.
|
||||
"""
|
||||
if os.environ.get("HERMES_DEV") == "1":
|
||||
return False
|
||||
if is_container_startup_environment():
|
||||
return False
|
||||
|
||||
hermes_home = os.environ.get("HERMES_HOME", "").strip()
|
||||
if hermes_home:
|
||||
if os.path.exists(os.path.join(hermes_home, ".container-mode")):
|
||||
return True
|
||||
parent_name = os.path.basename(os.path.dirname(os.path.normpath(hermes_home)))
|
||||
return (
|
||||
parent_name != "profiles"
|
||||
and active_profile_may_override_home(hermes_home)
|
||||
)
|
||||
|
||||
default_home = os.path.join(os.path.expanduser("~"), ".hermes")
|
||||
if active_profile_may_override_home(default_home):
|
||||
return True
|
||||
return os.path.exists(os.path.join(default_home, ".container-mode"))
|
||||
|
||||
|
||||
def read_openai_version() -> str | None:
|
||||
"""Read OpenAI SDK version without importing ``importlib.metadata``."""
|
||||
for base in sys.path:
|
||||
if not base:
|
||||
base = os.getcwd()
|
||||
version_file = os.path.join(base, "openai", "_version.py")
|
||||
try:
|
||||
with open(version_file, encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith("__version__"):
|
||||
continue
|
||||
_key, _sep, value = stripped.partition("=")
|
||||
value = value.split("#", 1)[0].strip().strip("\"'")
|
||||
return value or None
|
||||
except OSError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def read_install_method() -> str | None:
|
||||
"""Read the installer's ``.install_method`` stamp, if present.
|
||||
|
||||
Only the stamp (step 1 of ``config.detect_install_method``'s resolution
|
||||
order) — the managed/git/pip fallbacks need heavier imports and stay on
|
||||
the slow path. On the fast path home ambiguity is already excluded:
|
||||
``container_mode_may_be_active()`` bails to the slow path whenever a
|
||||
non-default profile might redirect HERMES_HOME.
|
||||
"""
|
||||
stamp = os.path.join(_resolved_home(), ".install_method")
|
||||
try:
|
||||
with open(stamp, encoding="utf-8") as handle:
|
||||
method = handle.read().strip().lower()
|
||||
return method or None
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def print_fast_version_info(*, check_updates: bool = True) -> None:
|
||||
"""THE canonical ``hermes --version`` output (also used by /version).
|
||||
|
||||
The static lines print instantly from stdlib-only probes; everything
|
||||
heavier (upstream SHA in the version line, authoritative install-method
|
||||
detection, the update-status check) is lazy-imported AFTER the first
|
||||
line is already on screen, so perceived latency stays instant while the
|
||||
output carries the full information that used to require the (removed)
|
||||
``hermes version`` subcommand. Every lazy block degrades gracefully —
|
||||
a broken/heavy import can never take the basic version output down.
|
||||
"""
|
||||
# Line 1: registry-owned banner label (includes "· upstream <sha>" for
|
||||
# git installs). banner.py keeps rich/prompt_toolkit lazy, so this
|
||||
# import is light; fall back to the plain label if anything fails.
|
||||
try:
|
||||
from hermes_cli.banner import format_banner_version_label
|
||||
|
||||
print(format_banner_version_label())
|
||||
except Exception:
|
||||
from hermes_cli import __release_date__, __version__
|
||||
|
||||
print(f"Hermes Agent v{__version__} ({__release_date__})")
|
||||
|
||||
print(f"Install directory: {project_root_str()}")
|
||||
|
||||
# Install method: authoritative resolver first (code-scoped stamp →
|
||||
# managed → nix → git → pip; also self-heals poisoned shared-home
|
||||
# 'docker' stamps). Fall back to the cheap stdlib stamp probe only if
|
||||
# the resolver import/run fails.
|
||||
try:
|
||||
from pathlib import Path
|
||||
|
||||
from hermes_cli.config import detect_install_method
|
||||
|
||||
install_method = detect_install_method(Path(project_root_str()))
|
||||
except Exception:
|
||||
install_method = read_install_method()
|
||||
if install_method:
|
||||
print(f"Install method: {install_method}")
|
||||
|
||||
print(f"Python: {sys.version.split()[0]}")
|
||||
|
||||
openai_version = read_openai_version()
|
||||
print(f"OpenAI SDK: {openai_version}" if openai_version else "OpenAI SDK: Not installed")
|
||||
|
||||
if not check_updates:
|
||||
return
|
||||
|
||||
# Update status (synchronous — acceptable since the user asked for
|
||||
# version info). Bounded by check_for_updates' own subprocess/network
|
||||
# timeouts and its 6-hour cache; any failure prints nothing.
|
||||
try:
|
||||
from hermes_cli.banner import UPDATE_AVAILABLE_NO_COUNT, check_for_updates
|
||||
from hermes_cli.config import recommended_update_command
|
||||
|
||||
behind = check_for_updates()
|
||||
if behind == UPDATE_AVAILABLE_NO_COUNT:
|
||||
print(f"Update available — run '{recommended_update_command()}'")
|
||||
elif behind and behind > 0:
|
||||
commits_word = "commit" if behind == 1 else "commits"
|
||||
print(
|
||||
f"Update available: {behind} {commits_word} behind — "
|
||||
f"run '{recommended_update_command()}'"
|
||||
)
|
||||
elif behind == 0:
|
||||
print("Up to date")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def try_fast_version(argv: list[str] | None = None) -> bool:
|
||||
"""Handle ``hermes --version`` before the heavy import wall.
|
||||
|
||||
Only ``--version``/``-V`` (the ``version`` subcommand was removed —
|
||||
``--version`` now carries the full output incl. update status), and
|
||||
never when container mode may need to route the command into the
|
||||
container. Termux keeps the HERMES_TERMUX_DISABLE_FAST_CLI escape hatch.
|
||||
"""
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
is_termux = is_termux_env()
|
||||
if is_termux and os.environ.get("HERMES_TERMUX_DISABLE_FAST_CLI") == "1":
|
||||
return False
|
||||
if is_termux:
|
||||
if not is_termux_fast_version_argv(argv):
|
||||
return False
|
||||
elif not is_global_fast_version_argv(argv):
|
||||
return False
|
||||
elif container_mode_may_be_active():
|
||||
return False
|
||||
|
||||
print_fast_version_info()
|
||||
return True
|
||||
Reference in New Issue
Block a user