forgeqe-cli 2.2.1__tar.gz → 2.2.3__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.1
3
+ Version: 2.2.3
4
4
  Summary: pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)
5
5
  Author: CLI-Agents
6
6
  License: MIT
@@ -65,7 +65,7 @@ ForgeQE sends no telemetry and turns it off in everything it starts:
65
65
  - The agent runtime: auto-update, session sharing, OpenTelemetry spans, and the model catalogue download are off; it uses its bundled model list.
66
66
  - The repo's test command: `DO_NOT_TRACK=1`, plus the opt-outs for Next.js, Nuxt, Astro, Gatsby, Storybook, Turborepo, Angular, .NET, and Cypress.
67
67
 
68
- `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The only traffic is to your model endpoint and the sites the agent visits. The engine also downloads its ad-block and cookie-banner extensions from the Chrome Web Store once.
68
+ `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The traffic is to your model endpoint and the sites the agent visits. Besides that, ForgeQE downloads only public files: the model price lists from models.dev and LiteLLM, once a day (`FORGEQE_PRICES=off` stops them); the agent runtime from its GitHub releases, when it is not installed; and the engine's ad-block and cookie-banner extensions from the Chrome Web Store, once. Nothing about your runs is sent.
69
69
 
70
70
  ## Configure
71
71
 
@@ -102,7 +102,7 @@ The active profile is `llm.active` in `forgeqe.yaml`, else `default` in `llms.js
102
102
 
103
103
  **Model router.** With more than one model in `llms.json`, `llm.routing: auto` gives each area of work (ForgeQE's agent, browser runs, coding, review, analysis, planning) the cheapest configured model that is good enough for it. ForgeQE checks each model once and rates it, and keeps the result in `.forgeqe/model-profile.json`. `forgeqe models --routes` shows the model each area gets, and why. `--profile` and `code.model` still win. See [docs/MODEL_ROUTING.md](docs/MODEL_ROUTING.md).
104
104
 
105
- **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs ForgeQE's agent and Scribe on the Copilot seat through the agent runtime's GitHub Copilot sign-in (`forgeqe auth login copilot`, GitHub.com or GitHub Enterprise). The browser engine uses GitHub Models with your GitHub sign-in (`forgeqe auth login github`, or `GITHUB_TOKEN`), optionally billed to an organization with `github_org`. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
105
+ **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs the browser engine and the desktop and mobile agents on the Copilot seat through GitHub's Copilot SDK, behind a gateway on `127.0.0.1`. It signs in with a GitHub token (`forgeqe auth login copilot`: a fine-grained token with the Copilot Requests permission, or the GitHub CLI's sign-in), so no third-party app is authorized on the account. ForgeQE's agent and Scribe do not run on Copilot yet. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
106
106
 
107
107
  ## CLI
108
108
 
@@ -51,7 +51,7 @@ ForgeQE sends no telemetry and turns it off in everything it starts:
51
51
  - The agent runtime: auto-update, session sharing, OpenTelemetry spans, and the model catalogue download are off; it uses its bundled model list.
52
52
  - The repo's test command: `DO_NOT_TRACK=1`, plus the opt-outs for Next.js, Nuxt, Astro, Gatsby, Storybook, Turborepo, Angular, .NET, and Cypress.
53
53
 
54
- `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The only traffic is to your model endpoint and the sites the agent visits. The engine also downloads its ad-block and cookie-banner extensions from the Chrome Web Store once.
54
+ `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The traffic is to your model endpoint and the sites the agent visits. Besides that, ForgeQE downloads only public files: the model price lists from models.dev and LiteLLM, once a day (`FORGEQE_PRICES=off` stops them); the agent runtime from its GitHub releases, when it is not installed; and the engine's ad-block and cookie-banner extensions from the Chrome Web Store, once. Nothing about your runs is sent.
55
55
 
56
56
  ## Configure
57
57
 
@@ -88,7 +88,7 @@ The active profile is `llm.active` in `forgeqe.yaml`, else `default` in `llms.js
88
88
 
89
89
  **Model router.** With more than one model in `llms.json`, `llm.routing: auto` gives each area of work (ForgeQE's agent, browser runs, coding, review, analysis, planning) the cheapest configured model that is good enough for it. ForgeQE checks each model once and rates it, and keeps the result in `.forgeqe/model-profile.json`. `forgeqe models --routes` shows the model each area gets, and why. `--profile` and `code.model` still win. See [docs/MODEL_ROUTING.md](docs/MODEL_ROUTING.md).
90
90
 
91
- **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs ForgeQE's agent and Scribe on the Copilot seat through the agent runtime's GitHub Copilot sign-in (`forgeqe auth login copilot`, GitHub.com or GitHub Enterprise). The browser engine uses GitHub Models with your GitHub sign-in (`forgeqe auth login github`, or `GITHUB_TOKEN`), optionally billed to an organization with `github_org`. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
91
+ **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs the browser engine and the desktop and mobile agents on the Copilot seat through GitHub's Copilot SDK, behind a gateway on `127.0.0.1`. It signs in with a GitHub token (`forgeqe auth login copilot`: a fine-grained token with the Copilot Requests permission, or the GitHub CLI's sign-in), so no third-party app is authorized on the account. ForgeQE's agent and Scribe do not run on Copilot yet. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
92
92
 
93
93
  ## CLI
94
94
 
@@ -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.1"
7
+ __version__ = "2.2.3"
@@ -0,0 +1,132 @@
1
+ """ForgeQE plugin for pytest: an HTML report after every run, from the terminal, an IDE's test runner, or CI.
2
+
3
+ pytest loads it by itself from the forgeqe-cli wheel (the pytest11 entry point). It is on in a folder with
4
+ forgeqe.yaml or .forgeqe/, or with ``pytest --forgeqe-report`` or FORGEQE_REPORT=1; FORGEQE_REPORT=0 turns it off.
5
+ At the end of the run it writes the results, with each failure's message and traceback, to
6
+ .pytest_cache/forgeqe/results.json and runs ``forgeqe report`` on it.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+
21
+ ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
22
+
23
+
24
+ def _clean(text: str) -> str:
25
+ return ANSI.sub("", text or "")
26
+
27
+
28
+ def pytest_addoption(parser):
29
+ parser.getgroup("forgeqe").addoption(
30
+ "--forgeqe-report", action="store_true", default=False, help="write a ForgeQE HTML report after the run"
31
+ )
32
+
33
+
34
+ def _active(config) -> bool:
35
+ flag = os.environ.get("FORGEQE_REPORT")
36
+ if flag == "0":
37
+ return False
38
+ if flag == "1" or config.getoption("forgeqe_report", False):
39
+ return True
40
+ root = Path(str(config.rootpath))
41
+ return (root / "forgeqe.yaml").exists() or (root / ".forgeqe").is_dir()
42
+
43
+
44
+ def pytest_configure(config):
45
+ # An xdist worker sends its results to the controller, which writes the one report.
46
+ if hasattr(config, "workerinput") or not _active(config):
47
+ return
48
+ config.pluginmanager.register(_Collector(config), "forgeqe-collector")
49
+
50
+
51
+ def _message(report) -> str:
52
+ crash = getattr(getattr(report, "longrepr", None), "reprcrash", None)
53
+ text = crash.message if crash is not None else str(getattr(report, "longrepr", "") or "")
54
+ first = next((line.strip() for line in _clean(text).splitlines() if line.strip()), "")
55
+ return first[:300]
56
+
57
+
58
+ class _Collector:
59
+ def __init__(self, config):
60
+ self.config = config
61
+ self.tests: dict[str, dict] = {}
62
+ self.started = datetime.now(timezone.utc)
63
+ self.t0 = time.monotonic()
64
+ self.line = ""
65
+
66
+ def pytest_runtest_logreport(self, report):
67
+ nodeid = report.nodeid
68
+ test = self.tests.setdefault(
69
+ nodeid, {"name": " › ".join(nodeid.split("::")), "status": "passed", "seconds": 0.0, "file": report.location[0]}
70
+ )
71
+ if report.outcome == "rerun":
72
+ # pytest-rerunfailures: a failure that is run again; passing later makes the test flaky, not failed.
73
+ test["rerun"] = True
74
+ test.setdefault("message", _message(report))
75
+ test.setdefault("details", _clean(report.longreprtext))
76
+ return
77
+ test["seconds"] = round(test["seconds"] + (report.duration or 0), 3)
78
+ if report.failed:
79
+ if test["status"] != "failed":
80
+ test["message"] = _message(report)
81
+ test["details"] = _clean(report.longreprtext)
82
+ test["status"] = "failed"
83
+ elif report.skipped and test["status"] == "passed" and report.when in ("setup", "call"):
84
+ test["status"] = "skipped"
85
+
86
+ def pytest_sessionfinish(self, session):
87
+ if not self.tests:
88
+ return
89
+ tests = []
90
+ for test in self.tests.values():
91
+ rerun = test.pop("rerun", False)
92
+ if test["status"] == "passed" and rerun:
93
+ test["flaky"] = True
94
+ elif test["status"] != "failed" and not test.get("flaky"):
95
+ test.pop("message", None)
96
+ test.pop("details", None)
97
+ tests.append({k: v for k, v in test.items() if v not in ("", None)})
98
+ doc = {
99
+ "format": "forgeqe-results",
100
+ "version": 1,
101
+ "framework": "pytest",
102
+ "startedAt": self.started.isoformat(),
103
+ "ms": round((time.monotonic() - self.t0) * 1000),
104
+ "command": " ".join(["pytest", *self.config.invocation_params.args])[:500],
105
+ "tests": tests,
106
+ }
107
+ cache = getattr(self.config, "cache", None)
108
+ folder = Path(cache.mkdir("forgeqe")) if cache is not None else Path(tempfile.mkdtemp(prefix="forgeqe-"))
109
+ path = folder / "results.json"
110
+ path.write_text(json.dumps(doc, indent=2) + "\n", encoding="utf-8")
111
+ self.line = _report(path, Path(str(self.config.rootpath)))
112
+
113
+ def pytest_terminal_summary(self, terminalreporter):
114
+ if self.line:
115
+ terminalreporter.write_line(self.line)
116
+
117
+
118
+ def _report(path: Path, root: Path) -> str:
119
+ """Run forgeqe report on the results; the line to show."""
120
+ # This wheel's own launcher, by the Python running pytest: no PATH lookup and no .cmd shim on Windows.
121
+ bin_ = os.environ.get("FORGEQE_BIN")
122
+ cmd = [bin_, "report", str(path)] if bin_ else [sys.executable, "-m", "forgeqe_cli", "report", str(path)]
123
+ try:
124
+ run = subprocess.run(cmd, cwd=str(root), capture_output=True, text=True, timeout=300)
125
+ except (OSError, subprocess.TimeoutExpired) as exc:
126
+ return f"ForgeQE: no report ({exc.__class__.__name__}). Build it with: forgeqe report {path}"
127
+ # forgeqe report exits 1 when a test failed: that is a report, not an error.
128
+ line = next((l for l in _clean(run.stdout).splitlines() if l.startswith("Report")), "") if run.returncode <= 1 else ""
129
+ if line:
130
+ return "ForgeQE report: " + line[len("Report"):].strip()
131
+ why = (_clean(run.stderr).strip().splitlines() or ["it did not finish"])[-1]
132
+ return f"ForgeQE: no report ({why}). Build it with: forgeqe report {path}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgeqe-cli
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)
5
5
  Author: CLI-Agents
6
6
  License: MIT
@@ -65,7 +65,7 @@ ForgeQE sends no telemetry and turns it off in everything it starts:
65
65
  - The agent runtime: auto-update, session sharing, OpenTelemetry spans, and the model catalogue download are off; it uses its bundled model list.
66
66
  - The repo's test command: `DO_NOT_TRACK=1`, plus the opt-outs for Next.js, Nuxt, Astro, Gatsby, Storybook, Turborepo, Angular, .NET, and Cypress.
67
67
 
68
- `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The only traffic is to your model endpoint and the sites the agent visits. The engine also downloads its ad-block and cookie-banner extensions from the Chrome Web Store once.
68
+ `OTEL_EXPORTER_OTLP_*` variables are removed from those processes, and your environment cannot turn any of this back on. The traffic is to your model endpoint and the sites the agent visits. Besides that, ForgeQE downloads only public files: the model price lists from models.dev and LiteLLM, once a day (`FORGEQE_PRICES=off` stops them); the agent runtime from its GitHub releases, when it is not installed; and the engine's ad-block and cookie-banner extensions from the Chrome Web Store, once. Nothing about your runs is sent.
69
69
 
70
70
  ## Configure
71
71
 
@@ -102,7 +102,7 @@ The active profile is `llm.active` in `forgeqe.yaml`, else `default` in `llms.js
102
102
 
103
103
  **Model router.** With more than one model in `llms.json`, `llm.routing: auto` gives each area of work (ForgeQE's agent, browser runs, coding, review, analysis, planning) the cheapest configured model that is good enough for it. ForgeQE checks each model once and rates it, and keeps the result in `.forgeqe/model-profile.json`. `forgeqe models --routes` shows the model each area gets, and why. `--profile` and `code.model` still win. See [docs/MODEL_ROUTING.md](docs/MODEL_ROUTING.md).
104
104
 
105
- **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs ForgeQE's agent and Scribe on the Copilot seat through the agent runtime's GitHub Copilot sign-in (`forgeqe auth login copilot`, GitHub.com or GitHub Enterprise). The browser engine uses GitHub Models with your GitHub sign-in (`forgeqe auth login github`, or `GITHUB_TOKEN`), optionally billed to an organization with `github_org`. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
105
+ **GitHub Copilot.** With Copilot licences, a `github_copilot` profile runs the browser engine and the desktop and mobile agents on the Copilot seat through GitHub's Copilot SDK, behind a gateway on `127.0.0.1`. It signs in with a GitHub token (`forgeqe auth login copilot`: a fine-grained token with the Copilot Requests permission, or the GitHub CLI's sign-in), so no third-party app is authorized on the account. ForgeQE's agent and Scribe do not run on Copilot yet. See HOW_TO_USE.md, *GitHub Copilot seats as the model*.
106
106
 
107
107
  ## CLI
108
108
 
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  py/forgeqe_cli/__init__.py
5
5
  py/forgeqe_cli/__main__.py
6
6
  py/forgeqe_cli/launcher.py
7
+ py/forgeqe_cli/pytest_plugin.py
7
8
  py/forgeqe_cli.egg-info/PKG-INFO
8
9
  py/forgeqe_cli.egg-info/SOURCES.txt
9
10
  py/forgeqe_cli.egg-info/dependency_links.txt
@@ -1,3 +1,6 @@
1
1
  [console_scripts]
2
2
  forgeqe = forgeqe_cli.launcher:main
3
3
  webpilot = forgeqe_cli.launcher:main
4
+
5
+ [pytest11]
6
+ forgeqe = forgeqe_cli.pytest_plugin
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "forgeqe-cli"
7
- version = "2.2.1"
7
+ version = "2.2.3"
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"
@@ -21,6 +21,10 @@ forgeqe = "forgeqe_cli.launcher:main"
21
21
  # The command's name before the rename, kept so existing scripts still work.
22
22
  webpilot = "forgeqe_cli.launcher:main"
23
23
 
24
+ # pytest loads the plugin by itself where the wheel is installed: a ForgeQE report after every run (forgeqe report setup).
25
+ [project.entry-points.pytest11]
26
+ forgeqe = "forgeqe_cli.pytest_plugin"
27
+
24
28
  [tool.setuptools.packages.find]
25
29
  where = ["py"]
26
30
  # The engine ships in the npm package; the wheel is only the launcher.
File without changes
File without changes