forgeqe-cli 2.2.3__tar.gz → 2.2.5__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: forgeqe-cli
3
- Version: 2.2.3
3
+ Version: 2.2.5
4
4
  Summary: pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)
5
5
  Author: CLI-Agents
6
6
  License: MIT
@@ -8,6 +8,7 @@ Keywords: forgeqe,browser,agent,cli,opentui
8
8
  Requires-Python: >=3.9
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Requires-Dist: truststore==0.10.4; python_version >= "3.10"
11
12
  Provides-Extra: dev
12
13
  Requires-Dist: pytest>=8.0; extra == "dev"
13
14
  Dynamic: license-file
@@ -4,4 +4,4 @@ The agent itself is the npm package ``@capagents/forgeqe`` (OpenTUI + Bun).
4
4
  This distribution only puts a ``forgeqe`` command on PATH.
5
5
  """
6
6
 
7
- __version__ = "2.2.3"
7
+ __version__ = "2.2.5"
@@ -5,6 +5,7 @@ import io
5
5
  import os
6
6
  import platform
7
7
  import shutil
8
+ import ssl
8
9
  import stat
9
10
  import subprocess
10
11
  import sys
@@ -93,11 +94,77 @@ def bun_asset(system: str | None = None, machine: str | None = None, musl: bool
93
94
  return f"bun-{os_name}-{cpu}{'-musl' if musl else ''}{'-baseline' if cpu == 'x64' else ''}.zip"
94
95
 
95
96
 
97
+ def _ssl_context() -> ssl.SSLContext | None:
98
+ """The system's certificate store (truststore), so downloads work on networks that inspect HTTPS."""
99
+ try:
100
+ import truststore
101
+
102
+ return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
103
+ except Exception:
104
+ return None
105
+
106
+
96
107
  def _download(url: str) -> bytes:
97
- with urllib.request.urlopen(url, timeout=120) as res:
108
+ with urllib.request.urlopen(url, timeout=120, context=_ssl_context()) as res:
98
109
  return res.read()
99
110
 
100
111
 
112
+ def github_release(repo: str, tag: str, asset: str) -> str:
113
+ """A GitHub release download, from FORGEQE_GITHUB_MIRROR when one is set (src/network.ts)."""
114
+ mirror = os.environ.get("FORGEQE_GITHUB_MIRROR", "").strip().rstrip("/")
115
+ base = mirror if mirror.lower().startswith("https://") else "https://github.com"
116
+ return f"{base}/{repo}/releases/download/{tag}/{asset}"
117
+
118
+
119
+ def certs_off() -> bool:
120
+ return os.environ.get("FORGEQE_SYSTEM_CERTS", "").strip().lower() in ("0", "off", "false", "no")
121
+
122
+
123
+ def system_ca_path() -> Path:
124
+ return forgeqe_home() / "cache" / "system-ca.pem"
125
+
126
+
127
+ def _system_roots() -> list[str]:
128
+ """PEM roots the system trusts: Windows' ROOT store, macOS' System keychains, or Linux's bundle."""
129
+ if WINDOWS:
130
+ return [ssl.DER_cert_to_PEM_cert(der) for der, enc, _ in ssl.enum_certificates("ROOT") if enc == "x509_asn"]
131
+ if sys.platform == "darwin":
132
+ keychains = [k for k in ("/Library/Keychains/System.keychain", "/System/Library/Keychains/SystemRootCertificates.keychain") if Path(k).is_file()]
133
+ try:
134
+ out = subprocess.run(["/usr/bin/security", "find-certificate", "-a", "-p", *keychains], capture_output=True, text=True, timeout=30)
135
+ except (OSError, subprocess.SubprocessError):
136
+ return []
137
+ return [b + "-----END CERTIFICATE-----\n" for b in out.stdout.split("-----END CERTIFICATE-----") if "BEGIN CERTIFICATE" in b] if out.returncode == 0 else []
138
+ for bundle in ("/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/etc/ssl/ca-bundle.pem", "/etc/ssl/cert.pem"):
139
+ if Path(bundle).is_file():
140
+ return [Path(bundle).read_text(encoding="utf-8", errors="replace")]
141
+ return []
142
+
143
+
144
+ def bun_env(env: dict[str, str] | None = None) -> dict[str, str]:
145
+ """
146
+ Bun reads NODE_EXTRA_CA_CERTS only at start: give it the system's trusted roots, written here the first time
147
+ (bun x fetches ForgeQE before ForgeQE runs) and kept up to date by ForgeQE (src/network.ts).
148
+ """
149
+ env = dict(os.environ if env is None else env)
150
+ if env.get("NODE_EXTRA_CA_CERTS") or certs_off():
151
+ return env
152
+ ca = system_ca_path()
153
+ if not ca.is_file():
154
+ try:
155
+ roots = _system_roots()
156
+ if roots:
157
+ ca.parent.mkdir(parents=True, exist_ok=True)
158
+ tmp = ca.with_name(f"{ca.name}.{os.getpid()}.tmp")
159
+ tmp.write_text("".join(r if r.endswith("\n") else r + "\n" for r in roots), encoding="utf-8")
160
+ os.replace(tmp, ca)
161
+ except OSError:
162
+ pass
163
+ if ca.is_file():
164
+ env["NODE_EXTRA_CA_CERTS"] = str(ca)
165
+ return env
166
+
167
+
101
168
  def expected_sha256(sums: str, asset: str) -> str | None:
102
169
  for line in sums.splitlines():
103
170
  parts = line.split()
@@ -110,7 +177,7 @@ def install_bun() -> str:
110
177
  asset = bun_asset()
111
178
  if not asset:
112
179
  raise LaunchError(f"no Bun build for {platform.system()}/{platform.machine()}")
113
- base = f"https://github.com/oven-sh/bun/releases/download/bun-v{BUN_VERSION}"
180
+ base = github_release("oven-sh/bun", f"bun-v{BUN_VERSION}", "").rstrip("/")
114
181
  print(f"forgeqe: downloading Bun {BUN_VERSION} ({asset}) into {tools_dir()} (one time)...", file=sys.stderr)
115
182
  data = _download(f"{base}/{asset}")
116
183
  expected = expected_sha256(_download(f"{base}/SHASUMS256.txt").decode("utf-8"), asset)
@@ -139,7 +206,8 @@ def manual_hint() -> str:
139
206
  install = 'powershell -c "irm bun.sh/install.ps1 | iex"' if WINDOWS else "curl -fsSL https://bun.sh/install | bash"
140
207
  return (
141
208
  f"Install Bun {'.'.join(map(str, MIN_BUN))}+ yourself ({install}) and re-run forgeqe,\n"
142
- f"or set FORGEQE_BUN to the full path of {BUN_EXE}."
209
+ f"or set FORGEQE_BUN to the full path of {BUN_EXE}.\n"
210
+ "Behind a company mirror of GitHub downloads, set FORGEQE_GITHUB_MIRROR to it."
143
211
  )
144
212
 
145
213
 
@@ -179,7 +247,7 @@ def main() -> None:
179
247
  os.environ["PATH"] = bindir + os.pathsep + os.environ.get("PATH", "")
180
248
  if WINDOWS:
181
249
  try:
182
- raise SystemExit(subprocess.call(cmd))
250
+ raise SystemExit(subprocess.call(cmd, env=bun_env()))
183
251
  except KeyboardInterrupt:
184
252
  raise SystemExit(130)
185
- os.execv(cmd[0], cmd)
253
+ os.execve(cmd[0], cmd, bun_env())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgeqe-cli
3
- Version: 2.2.3
3
+ Version: 2.2.5
4
4
  Summary: pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)
5
5
  Author: CLI-Agents
6
6
  License: MIT
@@ -8,6 +8,7 @@ Keywords: forgeqe,browser,agent,cli,opentui
8
8
  Requires-Python: >=3.9
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Requires-Dist: truststore==0.10.4; python_version >= "3.10"
11
12
  Provides-Extra: dev
12
13
  Requires-Dist: pytest>=8.0; extra == "dev"
13
14
  Dynamic: license-file
@@ -0,0 +1,6 @@
1
+
2
+ [:python_version >= "3.10"]
3
+ truststore==0.10.4
4
+
5
+ [dev]
6
+ pytest>=8.0
@@ -4,14 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "forgeqe-cli"
7
- version = "2.2.3"
7
+ version = "2.2.5"
8
8
  description = "pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = { text = "MIT" }
12
12
  authors = [{ name = "CLI-Agents" }]
13
13
  keywords = ["forgeqe", "browser", "agent", "cli", "opentui"]
14
- dependencies = []
14
+ # truststore: downloads check HTTPS certificates with the system's store, for networks that inspect HTTPS.
15
+ dependencies = ["truststore==0.10.4; python_version >= '3.10'"]
15
16
 
16
17
  [project.optional-dependencies]
17
18
  dev = ["pytest>=8.0"]
@@ -1,3 +0,0 @@
1
-
2
- [dev]
3
- pytest>=8.0
File without changes
File without changes
File without changes