forgexa-cli 1.55.0__tar.gz → 1.55.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/PKG-INFO +1 -1
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/agent_core.py +33 -2
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/daemon.py +1 -1
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/main.py +55 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/pyproject.toml +1 -1
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/README.md +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/setup.cfg +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.55.0 → forgexa_cli-1.55.1}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.55.
|
|
2
|
+
__version__ = "1.55.1"
|
|
@@ -2311,15 +2311,46 @@ def zcode_binary_fingerprint(binary: str | Path) -> str:
|
|
|
2311
2311
|
return digest.hexdigest()
|
|
2312
2312
|
|
|
2313
2313
|
|
|
2314
|
-
|
|
2315
|
-
|
|
2314
|
+
ZCODE_ALLOWED_SHA256_FILE = Path.home() / ".forgexa" / "zcode-allowed-sha256"
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
def zcode_allowed_fingerprints(env: dict[str, str] | None = None) -> set[str]:
|
|
2318
|
+
"""Union of the env allowlist and the persistent approval file.
|
|
2319
|
+
|
|
2320
|
+
GUI-launched daemons (macOS Dock/Finder) never inherit shell exports, so
|
|
2321
|
+
an explicit `forgexa zcode approve-build` writes the approval here where
|
|
2322
|
+
every runtime can read it regardless of how it was launched.
|
|
2323
|
+
"""
|
|
2316
2324
|
source_env = os.environ if env is None else env
|
|
2317
2325
|
allowed = {
|
|
2318
2326
|
value.strip().lower()
|
|
2319
2327
|
for value in source_env.get("FORGEXA_ZCODE_ALLOWED_SHA256", "").split(",")
|
|
2320
2328
|
if value.strip()
|
|
2321
2329
|
}
|
|
2330
|
+
try:
|
|
2331
|
+
lines = ZCODE_ALLOWED_SHA256_FILE.read_text(encoding="utf-8").splitlines()
|
|
2332
|
+
except OSError:
|
|
2333
|
+
lines = []
|
|
2334
|
+
for line in lines:
|
|
2335
|
+
value = line.split("#", 1)[0].strip()
|
|
2336
|
+
if value:
|
|
2337
|
+
allowed.add(value.lower())
|
|
2338
|
+
return allowed
|
|
2339
|
+
|
|
2340
|
+
|
|
2341
|
+
def zcode_build_verification_issue(binary: str | Path, env: dict[str, str] | None = None) -> str | None:
|
|
2342
|
+
"""Return a fail-closed reason unless the binary fingerprint is allowlisted."""
|
|
2343
|
+
allowed = zcode_allowed_fingerprints(env)
|
|
2322
2344
|
if not allowed:
|
|
2345
|
+
try:
|
|
2346
|
+
actual = zcode_binary_fingerprint(binary)
|
|
2347
|
+
except OSError:
|
|
2348
|
+
actual = ""
|
|
2349
|
+
if actual:
|
|
2350
|
+
return (
|
|
2351
|
+
"ZCode build is not approved; run `forgexa zcode approve-build` or set "
|
|
2352
|
+
f"FORGEXA_ZCODE_ALLOWED_SHA256={actual}"
|
|
2353
|
+
)
|
|
2323
2354
|
return "ZCode build is not approved; configure FORGEXA_ZCODE_ALLOWED_SHA256"
|
|
2324
2355
|
try:
|
|
2325
2356
|
actual = zcode_binary_fingerprint(binary)
|
|
@@ -1150,7 +1150,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
1150
1150
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
1151
1151
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
1152
1152
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
1153
|
-
DAEMON_VERSION = "1.55.
|
|
1153
|
+
DAEMON_VERSION = "1.55.1"
|
|
1154
1154
|
|
|
1155
1155
|
|
|
1156
1156
|
def _detect_client_type() -> str:
|
|
@@ -3573,6 +3573,45 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|
|
3573
3573
|
# ── Argument parser ──
|
|
3574
3574
|
|
|
3575
3575
|
|
|
3576
|
+
def cmd_zcode_approve_build(args: argparse.Namespace) -> None:
|
|
3577
|
+
from . import agent_core
|
|
3578
|
+
|
|
3579
|
+
binary = os.environ.get("FACTORY_ZCODE_PATH") or args.binary or shutil.which("zcode")
|
|
3580
|
+
if not binary:
|
|
3581
|
+
print("error: zcode binary not found; install it or pass --binary PATH", file=sys.stderr)
|
|
3582
|
+
return
|
|
3583
|
+
try:
|
|
3584
|
+
fingerprint = agent_core.zcode_binary_fingerprint(binary)
|
|
3585
|
+
except OSError as exc:
|
|
3586
|
+
print(f"error: could not fingerprint {binary}: {exc}", file=sys.stderr)
|
|
3587
|
+
return
|
|
3588
|
+
path = agent_core.ZCODE_ALLOWED_SHA256_FILE
|
|
3589
|
+
existing = ""
|
|
3590
|
+
try:
|
|
3591
|
+
existing = path.read_text(encoding="utf-8")
|
|
3592
|
+
except OSError:
|
|
3593
|
+
pass
|
|
3594
|
+
approved = {
|
|
3595
|
+
line.split("#", 1)[0].strip().lower()
|
|
3596
|
+
for line in existing.splitlines() if line.split("#", 1)[0].strip()
|
|
3597
|
+
}
|
|
3598
|
+
if fingerprint.lower() in approved:
|
|
3599
|
+
print(f"already approved: {fingerprint}")
|
|
3600
|
+
print(f"allowlist file: {path}")
|
|
3601
|
+
return
|
|
3602
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
3603
|
+
suffix = "" if (not existing or existing.endswith("\n")) else "\n"
|
|
3604
|
+
path.write_text(
|
|
3605
|
+
existing + suffix
|
|
3606
|
+
+ f"{fingerprint} # {binary} approved by `forgexa zcode approve-build`\n",
|
|
3607
|
+
encoding="utf-8",
|
|
3608
|
+
)
|
|
3609
|
+
print(f"approved ZCode build: {fingerprint}")
|
|
3610
|
+
print(f"binary: {binary}")
|
|
3611
|
+
print(f"written to: {path}")
|
|
3612
|
+
print("running daemons and desktop sidecars pick this up on their next probe (no restart needed)")
|
|
3613
|
+
|
|
3614
|
+
|
|
3576
3615
|
def main() -> None:
|
|
3577
3616
|
global _SERVER_URL_OVERRIDE, _output_format
|
|
3578
3617
|
|
|
@@ -3789,6 +3828,19 @@ def main() -> None:
|
|
|
3789
3828
|
help="Per-agent timeout for the live execution test (default: 90)",
|
|
3790
3829
|
)
|
|
3791
3830
|
|
|
3831
|
+
# zcode
|
|
3832
|
+
zcode_p = sub.add_parser("zcode", help="ZCode integration helpers")
|
|
3833
|
+
zcode_sub = zcode_p.add_subparsers(dest="zcode_cmd")
|
|
3834
|
+
zcode_approve_p = zcode_sub.add_parser(
|
|
3835
|
+
"approve-build",
|
|
3836
|
+
help="Persistently approve the installed ZCode binary fingerprint "
|
|
3837
|
+
"(works for GUI-launched daemons that never see shell exports)",
|
|
3838
|
+
)
|
|
3839
|
+
zcode_approve_p.add_argument(
|
|
3840
|
+
"--binary", default=None, metavar="PATH",
|
|
3841
|
+
help="Path to the zcode binary (default: $FACTORY_ZCODE_PATH or `zcode` on PATH)",
|
|
3842
|
+
)
|
|
3843
|
+
|
|
3792
3844
|
# runtimes
|
|
3793
3845
|
rt_p = sub.add_parser("runtimes", help="Runtime management")
|
|
3794
3846
|
rt_sub = rt_p.add_subparsers(dest="rt_cmd")
|
|
@@ -3918,6 +3970,9 @@ def main() -> None:
|
|
|
3918
3970
|
"logs": cmd_daemon_logs,
|
|
3919
3971
|
"agents": cmd_daemon_agents,
|
|
3920
3972
|
"check": cmd_check,
|
|
3973
|
+
"zcode": lambda a: {
|
|
3974
|
+
"approve-build": cmd_zcode_approve_build,
|
|
3975
|
+
}.get(a.zcode_cmd, lambda _: zcode_p.print_help())(a),
|
|
3921
3976
|
"runtimes": lambda a: {
|
|
3922
3977
|
"list": cmd_runtimes_list,
|
|
3923
3978
|
}.get(a.rt_cmd, lambda _: rt_p.print_help())(a),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|