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.
54 lines
2.5 KiB
Python
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)
|