Files
tai/tai_pkg/clirun.py
T
retoor 8df5cf7a45 Split monolithic tai.py into tai_pkg package, keep tai-orig.py as original
tai.py (8643 lines) is now a thin facade over tai_pkg/ (33 feature modules, one class or feature per file). tai-orig.py preserves the pre-split single-file version byte for byte (sha256 verified against snapshot); import tai and ./tai.py behavior unchanged.

Equivalence proof, all run on this tree immediately pre-commit: differential battery tai-orig.py vs facade 3718 checks 0 failures (surface, pure fuzz, tool dispatch, store/audit, release ops, fake-HOME install roundtrip, CLI runs); test_tai.py 261 OK; test_seal.py 16 OK; --version/--help byte-identical; dir(tai) zero removals.

Fix included: opencode wire-protocol schema echo leaked into replies; opencode_parse now scans with JSONDecoder.raw_decode capped at 200 scans (13 OpencodeParseTests). Test count 261 = 237 HEAD + 8 pre-existing uncommitted ToolArgumentsTests (now committed with this change, disclosed) + 13 OpencodeParseTests + 3 EmbeddedStringsTests. Patch-target-only test migration (24 hunks), zero assertion changes. tai_pkg clean-regenerated after mid-verification tamper was found and reverted.
2026-10-10 19:25:02 +02:00

54 lines
2.5 KiB
Python

"""Shared subprocess runners for CLI backends."""
import re
import subprocess
from tai_pkg.errors import BackendError
from tai_pkg.text import Text
__all__ = ["Cli"]
class Cli:
"""Cli."""
@staticmethod
def _cli_unknown_flag(stderr):
return bool(re.search(r"unknown (option|flag|command)|unrecognised|unrecognized|invalid flag|not found|no such option", stderr or "", re.IGNORECASE))
@staticmethod
def _cli_auth_failure(text):
return bool(re.search(r"not (logged in|authenticated)|auth|api key|unauthorized|401|login required|please (login|log in|authenticate)", text or "", re.IGNORECASE))
@staticmethod
def _cli_run(argv, prompt_input, timeout, label):
try:
done = subprocess.run(argv, input=prompt_input, stdin=subprocess.DEVNULL if prompt_input is None else None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=timeout)
except subprocess.TimeoutExpired:
raise BackendError("%s run timed out after %ds" % (label, timeout))
except OSError as exc:
raise BackendError("%s run failed: %s" % (label, Text.short_error(exc)))
return done
@staticmethod
def _cli_version_detail(done, fallback):
return (done.stdout.strip() or done.stderr.strip()).splitlines()[0][:60] if (done.stdout.strip() or done.stderr.strip()) else fallback
@staticmethod
def _cli_version_only_health(binary, fallback_name):
"""Shared by the CLIs with no cheap, read-only way to check real login
state (muse, grok): `--version` only proves the binary runs, never that
it's actually authenticated, so this deliberately never claims "ok" or
"subscription" -- a claim this check cannot back up (see codex_health/
claude_health for the two CLIs that DO expose a real status command,
used instead of this). Caught a real bug: this used to print "ok,
subscription" unconditionally on a successful --version, which is false
whenever the CLI is installed but never logged in."""
try:
done = subprocess.run([binary, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=15)
except (OSError, subprocess.SubprocessError) as exc:
return "fail: %s" % Text.short_error(exc)[:60]
if done.returncode != 0:
return "fail: exit %d" % done.returncode
return "cli found (%s), login unverified: no status command" % Cli._cli_version_detail(done, fallback_name)