forgexa-cli 1.14.10__tar.gz → 1.14.11__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.14.10
3
+ Version: 1.14.11
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.14.10"
2
+ __version__ = "1.14.11"
@@ -523,7 +523,7 @@ except (ImportError, ModuleNotFoundError):
523
523
  # DAEMON_VERSION is the protocol/logic version of the daemon code.
524
524
  # Kept in sync with pyproject.toml version via bump-version.sh.
525
525
  # CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
526
- DAEMON_VERSION = "1.14.10"
526
+ DAEMON_VERSION = "1.14.11"
527
527
 
528
528
 
529
529
  def _detect_client_type() -> str:
@@ -939,6 +939,25 @@ def _daemon_extract_claude_output(raw: str) -> str:
939
939
  return raw
940
940
 
941
941
 
942
+ # OpenCode requires v1.4.0+ for --dangerously-skip-permissions (added 2026-04-08).
943
+ # Without it, headless opencode run blocks on permission prompts and exits with code 1.
944
+ _OPENCODE_MIN_VERSION = (1, 4, 0)
945
+
946
+
947
+ def _parse_semver(v: str) -> tuple[int, int, int]:
948
+ """Parse a semver string into a comparable (major, minor, patch) tuple.
949
+
950
+ Strips a leading 'v' and ignores pre-release/build suffixes so that
951
+ '1.4.0-beta.1' and 'v1.4.0' both return (1, 4, 0).
952
+ Returns (0, 0, 0) when the string cannot be parsed.
953
+ """
954
+ import re as _re
955
+ m = _re.match(r"^v?(\d+)\.(\d+)\.(\d+)", v.strip())
956
+ if m:
957
+ return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
958
+ return (0, 0, 0)
959
+
960
+
942
961
  def _daemon_extract_opencode_output(raw: str) -> str:
943
962
  """Extract text content from OpenCode JSONL output."""
944
963
  parts: list[str] = []
@@ -4318,6 +4337,28 @@ class ProcessManager:
4318
4337
  'Failed to run the query PRAGMA wal_checkpoint(PASSIVE)' errors and
4319
4338
  exit code 1 when multiple tasks run simultaneously.
4320
4339
  """
4340
+ # --dangerously-skip-permissions was introduced in opencode v1.4.0 (2026-04-08).
4341
+ # Older versions silently ignore the flag but then block on permission prompts
4342
+ # in headless mode (no TTY), eventually exiting with code 1 after a long wait.
4343
+ # Fail fast here so the error is immediately actionable.
4344
+ _oc_ver = _parse_semver(agent.version)
4345
+ if _oc_ver < _OPENCODE_MIN_VERSION:
4346
+ _min_str = ".".join(str(x) for x in _OPENCODE_MIN_VERSION)
4347
+ return TaskResult(
4348
+ status="failed",
4349
+ exit_code=-1,
4350
+ stdout="",
4351
+ stderr="",
4352
+ error=(
4353
+ f"OpenCode {agent.version} is too old for headless execution. "
4354
+ f"Version {_min_str}+ is required: --dangerously-skip-permissions "
4355
+ f"(which auto-approves permission prompts in non-interactive mode) "
4356
+ f"was added in v{_min_str} on 2026-04-08. "
4357
+ f"Upgrade with: opencode upgrade"
4358
+ ),
4359
+ failure_code="agent_version_incompatible",
4360
+ )
4361
+
4321
4362
  cmd = [
4322
4363
  agent.command, "run",
4323
4364
  "--format", "json",
@@ -73,6 +73,63 @@ except ImportError:
73
73
  _SERVER_URL_OVERRIDE: str | None = None
74
74
 
75
75
 
76
+ # ── Banner ────────────────────────────────────────────────────────────────────
77
+
78
+ _LOGO_ART = """\
79
+ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗ █████╗
80
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝╚██╗██╔╝██╔══██╗
81
+ █████╗ ██║ ██║██████╔╝██║ ███╗█████╗ ╚███╔╝ ███████║
82
+ ██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔██╗ ██╔══██║
83
+ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗██╔╝ ██╗██║ ██║
84
+ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝"""
85
+
86
+
87
+ def _supports_color() -> bool:
88
+ """Return True when stdout can render ANSI colors."""
89
+ if not sys.stdout.isatty():
90
+ return False
91
+ if os.environ.get("NO_COLOR"):
92
+ return False
93
+ if os.environ.get("TERM") in ("dumb", ""):
94
+ return False
95
+ return True
96
+
97
+
98
+ def _brand_color() -> str:
99
+ """Return the best available ANSI escape for Forgexa brand blue (#096dd9).
100
+
101
+ Priority:
102
+ 1. 24-bit truecolor — exact match (COLORTERM=truecolor|24bit)
103
+ 2. Bold blue — 16-color fallback
104
+ """
105
+ colorterm = os.environ.get("COLORTERM", "").lower()
106
+ if colorterm in ("truecolor", "24bit"):
107
+ return "\033[38;2;9;109;217m" # #096dd9 exact
108
+ return "\033[1;34m" # bold blue — 16-color fallback
109
+
110
+
111
+ def _print_banner() -> None:
112
+ """Print the Forgexa logo + tagline.
113
+
114
+ Shown only on bare invocation and top-level --help/-h.
115
+ Respects NO_COLOR, TERM=dumb, and non-TTY environments.
116
+ """
117
+ from forgexa_cli import __version__
118
+
119
+ if _supports_color():
120
+ purple = _brand_color()
121
+ reset = "\033[0m"
122
+ logo = f"{purple}{_LOGO_ART}{reset}"
123
+ tagline = f" {purple}AI-Driven Software Factory · v{__version__}{reset}"
124
+ else:
125
+ logo = _LOGO_ART
126
+ tagline = f" AI-Driven Software Factory · v{__version__}"
127
+
128
+ print(logo)
129
+ print(tagline)
130
+ print()
131
+
132
+
76
133
  # ── Config file helpers (~/.forgexa/config) ──
77
134
 
78
135
  def _config_path() -> Path:
@@ -1396,6 +1453,10 @@ def main() -> None:
1396
1453
 
1397
1454
  active_url = _api_url() # resolved before parsing (for help text)
1398
1455
 
1456
+ # Show logo on bare invocation or top-level --help / -h
1457
+ if len(sys.argv) == 1 or (len(sys.argv) >= 2 and sys.argv[1] in ("-h", "--help")):
1458
+ _print_banner()
1459
+
1399
1460
  parser = argparse.ArgumentParser(
1400
1461
  prog="forgexa",
1401
1462
  description="Forgexa CLI — communicates with the Forgexa server via REST API.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.14.10
3
+ Version: 1.14.11
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.14.10"
3
+ version = "1.14.11"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = { text = "MIT" }
File without changes
File without changes