#!/usr/bin/env bash # Ormas client installer 0.0.4-r2. macOS / Linux / WSL2; no sudo or login. # Reviewed releases keep this script and requirements.txt immutable. set -Eeuo pipefail umask 077 RELEASE="0.0.4-r2" WHEEL="tensorbox_spec-0.0.4-py3-none-any.whl" WHEEL_SHA="226d56cdf1992d29eae893959f4a223c5c8846739cc7460260c11f78c55beb1f" LOCK_SHA="b217652df314c42683b0e670dd16c7bebd65a1985998694b77c2d7c5a16532c1" STAGE="prerequisites" LOG="" WORK="" LOCK_DIR="" LOCK_HELD=0 PYTHON="" ASSETS="" MANAGED_PYTHON=0 MODIFY_PATH=1 AGENT="none" GIT_READY=1 cleanup() { if [ "$LOCK_HELD" = 1 ]; then rmdir "$LOCK_DIR" 2>/dev/null || true; fi } failed() { local code="${1:-1}" trap - ERR printf '\nOrmas installation failed at: %s (exit %s).\n' "$STAGE" "$code" >&2 case "$STAGE" in prerequisites) printf 'A working Bash, curl and home directory are needed. Python is installed privately when needed. Windows users: use WSL2 Ubuntu.\n' >&2 ;; "Python setup") printf 'The private Python download or runtime check failed. Check access to github.com; no system Python was changed. Retry, or report the error below.\n' >&2 ;; "agent setup") printf 'The client is installed, but the agent connection did not finish. Existing agent settings were preserved; see the specific error below.\n' >&2 ;; download*) printf 'Check network access to https://ormas.ai/downloads/ and retry.\n' >&2 ;; integrity*) printf 'The release files do not match their checksums. Do not bypass this check; download again or report it.\n' >&2 ;; dependencies) printf 'Check access to https://pypi.org and https://files.pythonhosted.org. A missing compatible wheel or dependency conflict is shown below; source builds are disabled.\n' >&2 ;; environment) printf 'Check Python 3.12 venv support and available disk space. On Ubuntu install python3.12-venv if venv is missing.\n' >&2 ;; activation) printf 'Check directory permissions or the existing ~/.ormas-client path. Existing client data is preserved.\n' >&2 ;; *) printf 'The checked client could not be activated. See the diagnostic below; retry or report the failed stage.\n' >&2 ;; esac if [ -n "$LOG" ] && [ -f "$LOG" ]; then printf 'Diagnostic log: %s\nLast diagnostic lines:\n' "$LOG" >&2 tail -n 20 "$LOG" >&2 || true fi printf 'Release: %s. Help: https://ormas.ai/docs/first-task#installation-troubleshooting\n' "$RELEASE" >&2 exit "$code" } run() { "$@" >> "$LOG" 2>&1; } show_run() { local code=0 "$@" > "$WORK/step-output.txt" 2>&1 || code=$? cat "$WORK/step-output.txt" >> "$LOG" if [ "$code" != 0 ]; then return "$code"; fi cat "$WORK/step-output.txt" } fetch() { run curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 \ --retry 2 --connect-timeout 15 --max-time 180 --output "$2" "$1" } # Keep pip independent of user indexes, credentials, PYTHONPATH and pip config. pip_run() { run env -i HOME="$HOME" PATH="$PATH" PIP_CONFIG_FILE=/dev/null \ "$VENV/bin/python" -I -m pip --disable-pip-version-check --no-input "$@" } check_client() { run "$VENV/bin/python" -I - "$WORK/requirements.txt" "$WHEEL_SHA" <<'CHECK' import importlib.metadata as m, json, pathlib, sys from packaging.requirements import Requirement lock = pathlib.Path(sys.argv[1]).read_text().replace("\\\n", " ") for line in lock.splitlines(): line = line.split(" --hash=")[0].strip() if not line or line.startswith("#"): continue req = Requirement(line) if req.marker and not req.marker.evaluate(): continue version = m.version(req.name) if req.specifier and not req.specifier.contains(version): raise RuntimeError(f"{req.name}: expected {req.specifier}, installed {version}") assert m.version("tensorbox-spec") == "0.0.4", "Unexpected Ormas client version" info = json.loads(m.distribution("tensorbox-spec").read_text("direct_url.json") or "{}") assert info.get("archive_info", {}).get("hashes", {}).get("sha256") == sys.argv[2], "Installed client is not the pinned wheel" # Import the exact module used by coding-agent integrations without starting a server. import os, subprocess env = {k: v for k, v in os.environ.items() if k in ("HOME", "PATH", "TMPDIR", "LANG", "SYSTEMROOT")} subprocess.run([sys.executable, "-I", "-c", "import tensorbox_spec.mcp_server.client_server"], check=True, timeout=30, env=env) import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def handshake(): params = StdioServerParameters(command=sys.executable, args=["-I", "-m", "tensorbox_spec.mcp_server.client_server"], env=env) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = {tool.name for tool in (await session.list_tools()).tools} assert {"ormas_prepare_task", "ormas_outcomes_job"} <= tools, "Required agent tools are missing" asyncio.run(asyncio.wait_for(handshake(), timeout=30)) print("Pinned dependencies and MCP handshake verified.") CHECK local check_status=$? [ "$check_status" = 0 ] || return "$check_status" pip_run check || return $? run "$VENV/bin/python" -I - "$VENV/bin/ormas" <<'HELP' import subprocess, sys import os env = {k: v for k, v in os.environ.items() if k in ("HOME", "PATH", "TMPDIR", "LANG", "SYSTEMROOT")} result = subprocess.run([sys.argv[1], "-h"], capture_output=True, text=True, timeout=30, env=env) print(result.stdout) print(result.stderr) if result.returncode: raise SystemExit(result.returncode) for command in ("login", "doctor", "grokbuild", "project"): if command not in result.stdout: raise RuntimeError(f"Client help is missing {command}") HELP } ensure_python() { if [ "$MANAGED_PYTHON" = 0 ] && [ -z "$PYTHON" ]; then PYTHON="$(command -v python3.12 || true)"; fi if [ "$MANAGED_PYTHON" = 0 ] && [ -n "$PYTHON" ]; then if "$PYTHON" -I -c 'import sys, platform, venv, ensurepip; assert sys.version_info[:2] == (3,12) and platform.python_implementation() == "CPython"' >> "$LOG" 2>&1; then return; fi if [ "$EXPLICIT_PYTHON" = 1 ]; then printf 'The selected Python cannot create a CPython 3.12 environment. Retry without --python to install a private runtime automatically.\n' >&2 failed 1 fi fi STAGE="Python setup" printf 'Setting up a private Python runtime; your system Python stays unchanged…\n' local platform archive_sha archive_name case "$(uname -s):$(uname -m)" in Darwin:arm64) platform="aarch64-apple-darwin"; archive_sha="81a359f1cfadd4da11766534c5913791cea55f26e1bb902cacd2a531bb1e4b2b" ;; Darwin:x86_64) platform="x86_64-apple-darwin"; archive_sha="65b195c9cedc1fef6767f044f9822069adbd1bd9204d424ece4628776fdc04bb" ;; Linux:x86_64) platform="x86_64-unknown-linux-gnu"; archive_sha="72748da13197c1fb161e3afeef20a6a385ff24f2165e6e2758e47008e7faba4c" ;; esac local candidate for candidate in "$store"/python/3.12.14."${archive_sha:0:12}".*/python/bin/python3.12; do if [ -x "$candidate" ] && "$candidate" -I -c 'import sys, venv, ensurepip; assert sys.version_info[:3] == (3,12,14)' >> "$LOG" 2>&1; then PYTHON="$candidate"; return; fi done if [ "$(uname -s)" = Linux ] && ! command -v gzip >/dev/null; then printf 'Private Python setup needs gzip. On Ubuntu run: sudo apt-get install -y gzip, then retry.\n' >&2 failed 1 fi archive_name="cpython-3.12.14%2B20260901-$platform-install_only_stripped.tar.gz" fetch "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/$archive_name" "$WORK/python.tar.gz" local actual if command -v sha256sum >/dev/null; then actual="$(sha256sum "$WORK/python.tar.gz")"; actual="${actual%% *}"; elif command -v shasum >/dev/null; then actual="$(shasum -a 256 "$WORK/python.tar.gz")"; actual="${actual%% *}"; elif command -v openssl >/dev/null; then actual="$(openssl dgst -sha256 "$WORK/python.tar.gz")"; actual="${actual##* }"; else printf 'A checksum tool is missing (sha256sum, shasum or openssl). No unverified runtime will run.\n' >&2; failed 1; fi if [ "$actual" != "$archive_sha" ]; then printf 'Python archive checksum mismatch: expected %s, got %s\n' "$archive_sha" "$actual" >> "$LOG"; failed 1; fi mkdir -p "$store/python" local python_directory python_directory="$(mktemp -d "$store/python/3.12.14.${archive_sha:0:12}.XXXXXXXX")" run tar -xzf "$WORK/python.tar.gz" -C "$python_directory" PYTHON="$python_directory/python/bin/python3.12" run "$PYTHON" -I -c 'import sys, platform, venv, ensurepip; assert sys.version_info[:3] == (3,12,14) and platform.python_implementation() == "CPython"' } main() { trap 'failed "$?"' ERR trap cleanup EXIT trap 'failed 130' INT trap 'failed 143' TERM EXPLICIT_PYTHON=0 while [ "$#" -gt 0 ]; do case "$1" in --python) [ "$#" -ge 2 ] || { printf 'Missing --python value\n' >&2; exit 2; }; PYTHON="$2"; EXPLICIT_PYTHON=1; shift 2 ;; --assets-dir) [ "$#" -ge 2 ] || { printf 'Missing --assets-dir value\n' >&2; exit 2; }; ASSETS="$2"; shift 2 ;; --managed-python) MANAGED_PYTHON=1; shift ;; --no-modify-path) MODIFY_PATH=0; shift ;; --agent) [ "$#" -ge 2 ] || exit 2; AGENT="$2"; case "$AGENT" in codex|claude|none) ;; *) printf 'Choose --agent codex, claude or none.\n' >&2; exit 2 ;; esac; shift 2 ;; -h|--help) printf 'Usage: bash install-client.sh [--python /path/to/python3.12] [--assets-dir /path/to/site-public] [--managed-python] [--agent codex|claude|none] [--no-modify-path]\nInstalls the pinned Ormas client without sudo or login. Sets up supported shells unless --no-modify-path is supplied. --agent also configures the selected agent.\n'; exit 0 ;; *) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;; esac done case "$(uname -s):$(uname -m)" in Darwin:arm64|Darwin:x86_64|Linux:x86_64) ;; *) printf 'Supported: macOS Apple Silicon/Intel and Linux x86_64 (including WSL2 Ubuntu). Native Windows and other architectures are unsupported.\n' >&2; failed 1 ;; esac if [ "$(uname -s)" = Darwin ]; then local os_version os_version="$(/usr/bin/sw_vers -productVersion)" if [ "${os_version%%.*}" -lt 14 ]; then printf 'This client requires macOS 14 or later; this computer runs %s. Upgrade macOS or use a supported Linux computer.\n' "$os_version" >&2; failed 1; fi else local libc_version libc_major libc_minor libc_version="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)" if [ -z "$libc_version" ]; then printf 'This installer requires glibc Linux (for example Ubuntu 22.04 or 24.04). Alpine/musl is unsupported.\n' >&2; failed 1; fi libc_version="${libc_version#glibc }"; libc_major="${libc_version%%.*}"; libc_minor="${libc_version#*.}"; libc_minor="${libc_minor%%.*}" if [ "$libc_major" -lt 2 ] || { [ "$libc_major" -eq 2 ] && [ "$libc_minor" -lt 28 ]; }; then printf 'This client requires glibc 2.28 or later. Use a supported Linux release such as Ubuntu 22.04 or 24.04.\n' >&2; failed 1; fi fi if [ "$(id -u)" = 0 ]; then printf 'Run as your normal user, without sudo.\n' >&2; exit 1; fi if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then printf 'A writable home directory is required.\n' >&2; exit 1; fi for tool in curl tar; do command -v "$tool" >/dev/null || { printf 'Missing required command: %s. On Ubuntu run: sudo apt-get update && sudo apt-get install -y curl tar\n' "$tool" >&2 failed 1 }; done local state="$HOME/.local/state/ormas-client" store="$HOME/.local/share/ormas-client" mkdir -p "$state" "$store/venvs" WORK="$(mktemp -d "$state/install-${RELEASE}.XXXXXXXX")" LOG="$WORK/install.log" : > "$LOG" printf 'Ormas client %s\nDiagnostic log: %s\n' "$RELEASE" "$LOG" LOCK_DIR="$store/install.lock" if ! mkdir "$LOCK_DIR" 2>/dev/null; then printf 'Another install is running, or was interrupted. If no installer is running, remove the empty lock directory: %s\n' "$LOCK_DIR" >&2 exit 1 fi LOCK_HELD=1 printf 'Platform: %s %s\n' "$(uname -s)" "$(uname -m)" >> "$LOG" ensure_python STAGE="prerequisites" run "$PYTHON" -I -V if ! command -v git >/dev/null; then GIT_READY=0; elif [ "$(uname -s)" = Darwin ] && [ "$(command -v git)" = /usr/bin/git ] && ! xcode-select -p >> "$LOG" 2>&1; then GIT_READY=0; elif ! git --version >> "$LOG" 2>&1; then GIT_READY=0; fi STAGE="download requirements" printf '[1/5] Downloading the pinned release files…\n' if [ -n "$ASSETS" ]; then run cp "$ASSETS/downloads/client-$RELEASE/requirements.txt" "$WORK/requirements.txt" else fetch "https://ormas.ai/downloads/client-$RELEASE/requirements.txt" "$WORK/requirements.txt" fi STAGE="integrity requirements" run "$PYTHON" -I - "$WORK/requirements.txt" "$LOCK_SHA" <<'HASH' import hashlib, pathlib, sys actual = hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest() if actual != sys.argv[2]: raise RuntimeError(f"Checksum mismatch: expected {sys.argv[2]}, got {actual}") print("Requirements checksum verified.") HASH # Reuse only a complete, checked environment from this exact installer release. VENV="$("$PYTHON" -I - "$HOME/.ormas-client" "$store/venvs" "$RELEASE" <<'REUSE' import pathlib, sys link, store = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]).resolve() if link.is_symlink(): target = link.resolve() if target.parent == store and (target / ".ormas-release").is_file() and (target / ".ormas-release").read_text().strip() == sys.argv[3]: print(target) REUSE )" if [ -n "$VENV" ] && (trap - ERR; check_client); then printf '[2/5] Existing pinned client passed checks; no reinstall needed.\n' else STAGE="download wheel" if [ -n "$ASSETS" ]; then run cp "$ASSETS/downloads/$WHEEL" "$WORK/$WHEEL"; else fetch "https://ormas.ai/downloads/$WHEEL" "$WORK/$WHEEL"; fi STAGE="integrity wheel" run "$PYTHON" -I - "$WORK/$WHEEL" "$WHEEL_SHA" "$WORK/requirements.txt" <<'WHEELCHECK' import hashlib, pathlib, sys wheel = pathlib.Path(sys.argv[1]) actual = hashlib.sha256(wheel.read_bytes()).hexdigest() if actual != sys.argv[2]: raise RuntimeError(f"Wheel checksum mismatch: expected {sys.argv[2]}, got {actual}") lock = pathlib.Path(sys.argv[3]) original = "https://ormas.ai/downloads/" + wheel.name + "#sha256=" + sys.argv[2] assert original in lock.read_text(), "Lock does not refer to the pinned wheel" lock.with_name("install-requirements.txt").write_text(lock.read_text().replace(original, wheel.as_uri())) print("Wheel checksum verified.") WHEELCHECK STAGE="environment" printf '[2/5] Creating a separate Python 3.12 environment…\n' VENV="$(mktemp -d "$store/venvs/$RELEASE.XXXXXXXX")" run "$PYTHON" -I -m venv "$VENV" STAGE="dependencies" printf '[3/5] Installing locked dependencies (wheels only)…\n' pip_run install --require-hashes --only-binary=:all: --index-url https://pypi.org/simple \ --timeout 30 --retries 2 -r "$WORK/install-requirements.txt" STAGE="client checks" printf '[4/5] Checking dependencies, client commands and MCP imports…\n' check_client printf '%s\n' "$RELEASE" > "$VENV/.ormas-release" fi STAGE="activation" printf '[5/5] Activating the checked client and user launcher…\n' run "$PYTHON" -I - "$VENV" "$HOME/.ormas-client" "$HOME/.local/bin/ormas" "$WORK" <<'ACTIVATE' import os, pathlib, sys, time, signal def interrupted(signum, frame): raise InterruptedError("Activation interrupted") signal.signal(signal.SIGTERM, interrupted) signal.signal(signal.SIGINT, interrupted) venv, link, launcher, work = map(pathlib.Path, sys.argv[1:]) launcher.parent.mkdir(parents=True, exist_ok=True) backup = None old_link = link.readlink() if link.is_symlink() else None if link.exists() and not link.is_symlink(): if not link.is_dir() or not (link / "pyvenv.cfg").is_file() or not (link / "bin/ormas").exists(): raise RuntimeError(f"{link} is not an Ormas virtual environment; preserve it and resolve this path conflict before retrying") backup = link.with_name(link.name + ".previous-" + str(time.time_ns())) elif link.is_symlink(): old = link.resolve() owned = old.parent == venv.parent if not owned and not ((old / "pyvenv.cfg").is_file() and (old / "bin/ormas").exists()): raise RuntimeError(f"{link} points outside an Ormas environment; preserve it and resolve this path conflict") pending = link.with_name(link.name + ".install-" + work.name) switched = False try: pending.symlink_to(venv) if backup: link.rename(backup) os.replace(pending, link) switched = True except BaseException: # Complete restoration even if a second interrupt arrives during rollback. signal.signal(signal.SIGTERM, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN) pending.unlink(missing_ok=True) if backup and backup.exists(): if link.is_symlink(): link.unlink() backup.rename(link) elif switched: link.unlink(missing_ok=True) if old_link is not None: link.symlink_to(old_link) raise if backup: print(f"Previous client preserved at: {backup}") if os.path.lexists(launcher): if not launcher.is_symlink() or launcher.readlink() != link / "bin/ormas": print(f"WARNING: existing launcher preserved: {launcher}. Use {link}/bin/ormas until you resolve the launcher conflict.") else: try: launcher.symlink_to(link / "bin/ormas") except OSError as error: print(f"WARNING: could not create launcher {launcher}: {error}. Use {link}/bin/ormas until you resolve its permissions.") print(f"Active client: {link} -> {venv}") ACTIVATE tail -n 4 "$LOG" if [ "$MODIFY_PATH" = 1 ]; then STAGE="shell setup" show_run "$VENV/bin/python" -I - "$WORK" <<'PROFILE' import os, pathlib, shutil, tempfile home = pathlib.Path.home() work = pathlib.Path(__import__("sys").argv[1]) shell = pathlib.Path(os.environ.get("SHELL", "/bin/zsh" if __import__("sys").platform == "darwin" else "/bin/bash")).name line = 'export PATH="$HOME/.local/bin:$PATH"' if shell == "zsh": profiles = [pathlib.Path(os.environ.get("ZDOTDIR", str(home))).expanduser() / ".zshrc"] elif shell == "bash": profiles = [home / ".bashrc"] login = next((home / name for name in (".bash_profile", ".bash_login", ".profile") if (home / name).is_file() and os.access(home / name, os.R_OK)), home / ".profile") profiles.append(login) elif shell == "fish": profiles = [pathlib.Path(os.environ.get("XDG_CONFIG_HOME", str(home / ".config"))) / "fish/config.fish"] line = 'fish_add_path --path "$HOME/.local/bin"' else: print(f"WARNING: automatic shell setup does not support {shell}. The agent connection works by absolute path; use {home}/.ormas-client/bin/ormas for terminal commands.") profiles = [] block = "\n# Ormas client command\n" + (line + "\n" if shell == "fish" else 'case ":$PATH:" in\n *":$HOME/.local/bin:"*) ;;\n *) ' + line + ' ;;\nesac\n') for profile in dict.fromkeys(profiles): try: if profile.is_symlink(): profile = profile.resolve(strict=True) original = profile.read_text() if profile.exists() else "" if block in original or line in original.splitlines(): print(f"Shell command already configured: {profile}") continue profile.parent.mkdir(parents=True, exist_ok=True) if profile.exists(): backup = work / ("shell-backup-" + profile.name) shutil.copyfile(profile, backup) backup.chmod(0o600) # Append preserves the original content and file permissions, including linked profiles. with profile.open("a") as stream: stream.write(block) print(f"Shell command configured: {profile}; existing content preserved.") except (OSError, UnicodeError) as error: print(f"WARNING: could not configure {profile}: {error}. Your agent can still use its absolute connection path; terminal commands are at {home}/.ormas-client/bin/ormas.") PROFILE fi if [ "$AGENT" != none ]; then STAGE="agent setup" printf 'Configuring Ormas for %s…\n' "$AGENT" show_run "$VENV/bin/python" -I - "$AGENT" "$HOME/.ormas-client/bin/python" "$WORK" <<'AGENTSETUP' import json, os, pathlib, shutil, subprocess, sys, tempfile import tomlkit agent, command, work = sys.argv[1], sys.argv[2], pathlib.Path(sys.argv[3]) home = pathlib.Path.home() args = ["-I", "-m", "tensorbox_spec.mcp_server.client_server"] if agent == "codex": config = pathlib.Path(os.environ.get("CODEX_HOME", str(home / ".codex"))).expanduser() / "config.toml" original = config.read_bytes() if config.exists() else b"" data = tomlkit.parse(original.decode()) servers = data.get("mcp_servers", {}) else: config = pathlib.Path(os.environ["CLAUDE_CONFIG_DIR"]).expanduser() / ".claude.json" if os.environ.get("CLAUDE_CONFIG_DIR") else home / ".claude.json" original = config.read_bytes() if config.exists() else b"" data = json.loads(original) if original else {} servers = data.get("mcpServers", {}) existing = servers.get("ormas") if existing: if existing.get("command") == command and list(existing.get("args", [])) == args: if existing.get("enabled") is False or existing.get("disabled") is True: raise RuntimeError("Ormas is disabled in your agent settings. Enable that connection and restart your agent; existing settings were preserved.") print(f"Ormas is already configured for {agent}; existing settings preserved.") raise SystemExit(0) raise RuntimeError("An existing agent connection named 'ormas' points elsewhere. It was preserved. Resolve that connection in your agent's MCP settings, then rerun this installer; the client is already installed.") config.parent.mkdir(parents=True, exist_ok=True) if original: backup = work / (agent + "-config-backup") backup.write_bytes(original) backup.chmod(0o600) if agent == "claude": executable = shutil.which("claude") if not executable and (home / ".local/bin/claude").is_file(): executable = str(home / ".local/bin/claude") if not executable: raise RuntimeError("Claude Code was not found. Install or open Claude Code first, then rerun the same install command. No Claude settings were changed.") # Claude owns this file, which also contains its session; use its supported CLI. subprocess.run([executable, "mcp", "add", "--scope", "user", "--transport", "stdio", "ormas", "--", command, *args], check=True, timeout=30) else: # This supported config route also works for desktop users without the Codex CLI. if "mcp_servers" not in data: data["mcp_servers"] = tomlkit.table() entry = tomlkit.table() entry["command"], entry["args"] = command, args data["mcp_servers"]["ormas"] = entry rendered = tomlkit.dumps(data).encode() if (config.read_bytes() if config.exists() else b"") != original: raise RuntimeError("Codex settings changed during installation; no settings were overwritten. Rerun the installer.") fd, pending = tempfile.mkstemp(prefix=".ormas-config-", dir=config.parent) try: with os.fdopen(fd, "wb") as stream: stream.write(rendered) os.replace(pending, config) finally: pathlib.Path(pending).unlink(missing_ok=True) print(f"Configured Ormas for {agent} using a fixed executable path; no environment activation is needed. Restart your agent to load the connection.") AGENTSETUP fi printf '\nInstalled and checked Ormas client %s.\n' "$RELEASE" if [ "$AGENT" != none ]; then printf 'Ormas is configured for %s. Restart that agent after connecting your account.\n' "$AGENT"; fi printf 'Connect your account: open https://ormas.ai/app/keys, create an Ormas key, then run:\n "%s/.ormas-client/bin/ormas" login\nPaste the key only when that command prompts for it, never into agent chat.\n' "$HOME" printf 'Open a new terminal to use the short ormas command after supported shell setup. The full-path login command above works immediately.\n' if [ "$GIT_READY" = 0 ]; then printf '\nGit is missing or unavailable. The client is installed, but repository work needs Git.\n' if [ "$(uname -s)" = Darwin ]; then printf 'On macOS run: xcode-select --install\nComplete the window that opens, then retry your connection check.\n'; else printf 'On Ubuntu run: sudo apt-get update && sudo apt-get install -y git\n'; fi fi printf 'Help: https://ormas.ai/docs/first-task#installation-troubleshooting\n' } # Wrapping execution ensures Bash has parsed the full function before changing files. main "$@"