@capagents/forgeqe 2.2.4 → 2.2.6
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.
- package/HOW_TO_USE.md +49 -13
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/bin/forgeqe.d.ts +6 -0
- package/bin/forgeqe.js +38 -6
- package/dist/forgeqe.js +872 -865
- package/docs/user-guide.html +48 -13
- package/llms.example.json +1 -1
- package/package.json +11 -1
- package/py/forgeqe_desktop/__main__.py +4 -0
- package/py/forgeqe_engine/__main__.py +4 -0
- package/py/forgeqe_engine/browser.py +24 -1
- package/py/forgeqe_engine/cli.py +4 -0
- package/py/forgeqe_engine/copilot_gateway.py +24 -9
- package/py/forgeqe_engine/systemcerts.py +23 -0
- package/py/forgeqe_mobile/__main__.py +4 -0
package/docs/user-guide.html
CHANGED
|
@@ -446,7 +446,7 @@ forgeqe models # check the profiles
|
|
|
446
446
|
forgeqe start # the shell
|
|
447
447
|
</code></pre>
|
|
448
448
|
<p>With GitHub Copilot licences instead of a model key, sign in rather than exporting one, and set <code>llm.active: copilot</code> in <code>forgeqe.yaml</code> (or <code>"default": "copilot"</code> in <code>llms.json</code>). The browser engine then runs on your Copilot seat; ForgeQE's agent and Scribe need another profile for now:</p>
|
|
449
|
-
<pre><code class="language-bash">forgeqe auth login copilot #
|
|
449
|
+
<pre><code class="language-bash">forgeqe auth login copilot # opens GitHub in your browser to sign in
|
|
450
450
|
forgeqe models # the copilot profile is active
|
|
451
451
|
</code></pre>
|
|
452
452
|
<p>In the shell, type a request and press Enter:</p>
|
|
@@ -470,7 +470,7 @@ forgeqe suite cases.yaml --parallel 3 # what pipelines call
|
|
|
470
470
|
pip install forgeqe-cli
|
|
471
471
|
forgeqe --help
|
|
472
472
|
</code></pre>
|
|
473
|
-
<p>That is the only install step on Windows, macOS and Linux.
|
|
473
|
+
<p>That is the only install step on Windows, macOS and Linux. npm installs Bun with ForgeQE (through your npm registry, so a company mirror works); with pip, or when npm skipped it, the first run downloads Bun into <code>~/.forgeqe/tools</code> (unless a Bun 1.3+ is already installed), and <code>forgeqe setup</code> downloads the agent runtime and uv, which fetches Python. Nothing is added to PATH or shell profiles; deleting <code>~/.forgeqe</code> removes it all.</p>
|
|
474
474
|
<p>Set up the ForgeQE engine once (the first live run does it automatically if you skip this):</p>
|
|
475
475
|
<pre><code class="language-bash">forgeqe setup
|
|
476
476
|
</code></pre>
|
|
@@ -488,6 +488,38 @@ bun install
|
|
|
488
488
|
bun src/index.ts --help
|
|
489
489
|
</code></pre>
|
|
490
490
|
</div></section>
|
|
491
|
+
<section class="doc sub" id="doc-how_to_use-company-networks"><h4>Company networks</h4><div class="prose"><p>On a company laptop, ForgeQE sets itself up for the network without anyone changing settings:</p>
|
|
492
|
+
<div class="tbl"><table>
|
|
493
|
+
<thead>
|
|
494
|
+
<tr><th>What the network does</th><th>What ForgeQE does</th></tr>
|
|
495
|
+
</thead>
|
|
496
|
+
<tbody>
|
|
497
|
+
<tr><td>Inspects HTTPS (Zscaler, Netskope, a firewall), with the company's own certificate</td><td>Uses the certificates this computer trusts: Windows' Trusted Root store, or the macOS System keychain. ForgeQE, the agent runtime, Playwright and Copilot's runtime get them through <code>NODE_EXTRA_CA_CERTS</code> (a copy in <code>~/.forgeqe/cache/system-ca.pem</code>, refreshed weekly); the Python engines check with the system through truststore; uv with <code>UV_SYSTEM_CERTS</code>. Certificates are still verified.</td></tr>
|
|
498
|
+
<tr><td>Sends traffic through a proxy set in Windows</td><td>Reads Windows' proxy setting (or the one proxy its automatic configuration script names) and passes it on as <code>HTTPS_PROXY</code>. ForgeQE's own servers on <code>127.0.0.1</code> always bypass it.</td></tr>
|
|
499
|
+
<tr><td>Blocks downloads from GitHub or the Playwright CDN</td><td>npm brings Bun through your registry; Microsoft Edge is used when Google Chrome is not installed, so no Chromium is downloaded; mirrors can stand in for the rest (below).</td></tr>
|
|
500
|
+
</tbody>
|
|
501
|
+
</table></div>
|
|
502
|
+
<p>To see where the network stops ForgeQE, run:</p>
|
|
503
|
+
<pre><code class="language-bash">forgeqe doctor
|
|
504
|
+
</code></pre>
|
|
505
|
+
<p>It shows the proxy and certificates in use, checks each address ForgeQE needs (GitHub downloads, Python packages, the npm registry, your models, Azure DevOps or Jira), and for anything blocked prints what to ask IT for, ready to forward.</p>
|
|
506
|
+
<p><strong>For IT: mirrors and settings.</strong> Set these as environment variables (for example by Group Policy):</p>
|
|
507
|
+
<div class="tbl"><table>
|
|
508
|
+
<thead>
|
|
509
|
+
<tr><th>Variable</th><th>What it does</th></tr>
|
|
510
|
+
</thead>
|
|
511
|
+
<tbody>
|
|
512
|
+
<tr><td><code>FORGEQE_GITHUB_MIRROR</code></td><td>A mirror of GitHub release downloads (<code>https://artifacts.example.com/github</code>, laid out as <code><owner>/<repo>/releases/download/<tag>/<file></code>): Bun, uv, the agent runtime, Python builds and Copilot's runtime come from it. Checksums are still checked.</td></tr>
|
|
513
|
+
<tr><td><code>UV_DEFAULT_INDEX</code> (or <code>PIP_INDEX_URL</code>)</td><td>A mirror of PyPI for the engine's Python packages.</td></tr>
|
|
514
|
+
<tr><td><code>PLAYWRIGHT_DOWNLOAD_HOST</code></td><td>A mirror for Chromium, needed only without Chrome or Edge.</td></tr>
|
|
515
|
+
<tr><td><code>HTTPS_PROXY</code>, <code>NO_PROXY</code></td><td>A proxy other than Windows' setting. A proxy that needs a sign-in takes <code>http://user:password@host:port</code>.</td></tr>
|
|
516
|
+
<tr><td><code>NODE_EXTRA_CA_CERTS</code></td><td>Your own certificate file instead of the system's roots.</td></tr>
|
|
517
|
+
<tr><td><code>FORGEQE_SYSTEM_CERTS=off</code></td><td>Leaves certificates to each tool's built-in list.</td></tr>
|
|
518
|
+
<tr><td><code>FORGEQE_HOME</code></td><td>Where ForgeQE keeps its tools (default <code>~/.forgeqe</code>), for machines where programs may run only from certain folders.</td></tr>
|
|
519
|
+
</tbody>
|
|
520
|
+
</table></div>
|
|
521
|
+
<p>What still needs IT: a proxy that signs in with your Windows account only (NTLM or Kerberos), and policies that block programs in the user folder (set <code>FORGEQE_HOME</code> to an allowed folder). On macOS, the company's certificate must be in the System keychain, where device management puts it.</p>
|
|
522
|
+
</div></section>
|
|
491
523
|
</section>
|
|
492
524
|
<section class="doc" id="doc-how_to_use-2-scaffold-config">
|
|
493
525
|
<h3>Scaffold config</h3>
|
|
@@ -537,19 +569,20 @@ bun src/index.ts init --global --profile azure-gpt4o
|
|
|
537
569
|
<tbody>
|
|
538
570
|
<tr><td><code>model</code></td><td>The Copilot model, as the Copilot SDK names it (<code>gpt-4.1</code>, <code>claude-sonnet-4.6</code>). It must take screenshots. The sign-in lists your seat's models and which of them take images.</td></tr>
|
|
539
571
|
<tr><td><code>engine_model</code></td><td>Optional. Another Copilot model for the browser engine and the desktop agent.</td></tr>
|
|
540
|
-
<tr><td><code>api_key_env</code></td><td>Optional.
|
|
572
|
+
<tr><td><code>api_key_env</code></td><td>Optional. A variable that must hold the GitHub token for this profile. Without it ForgeQE uses <code>COPILOT_GITHUB_TOKEN</code>, then a token <code>forgeqe auth login copilot</code> saved, then the browser sign-in.</td></tr>
|
|
541
573
|
</tbody>
|
|
542
574
|
</table></div>
|
|
543
|
-
<p>Sign in once per machine:</p>
|
|
575
|
+
<p>Sign in once per machine (Windows, macOS or Linux):</p>
|
|
544
576
|
<pre><code class="language-bash">forgeqe auth login copilot
|
|
545
577
|
</code></pre>
|
|
546
|
-
<p>It offers two ways:</p>
|
|
578
|
+
<p>It offers two ways, the ones GitHub's Copilot SDK recommends:</p>
|
|
547
579
|
<ul>
|
|
548
|
-
<li><strong>
|
|
549
|
-
<li><strong>
|
|
580
|
+
<li><strong>Sign in with GitHub in the browser</strong> (recommended on your own computer). ForgeQE runs GitHub's own Copilot sign-in (<code>copilot login</code> from GitHub's Copilot CLI, downloaded once into <code>~/.forgeqe/tools</code> and checked against GitHub's checksums). Your browser opens GitHub; approve <strong>GitHub Copilot CLI</strong> there and you are signed in. GitHub's CLI keeps the sign-in in your system's credential store (Windows Credential Manager, the macOS Keychain); ForgeQE never sees a token. On a remote machine it shows a code to enter at github.com/login/device instead.</li>
|
|
581
|
+
<li><strong>A fine-grained token</strong>, for machines without a browser. ForgeQE opens GitHub's token page: set <strong>Resource owner</strong> to your personal account and add the <strong>Copilot Requests</strong> permission, then paste the token (<code>github_pat_...</code>). Each character shows as <code>*</code>, and ForgeQE confirms what it got (<code>github_pat_11AB…Wz9Q (93 characters)</code>). The token goes to your system's credential store like ForgeQE's other secrets. Classic tokens (<code>ghp_</code>) do not work with Copilot.</li>
|
|
550
582
|
</ul>
|
|
551
|
-
<p>Either way ForgeQE checks the
|
|
552
|
-
<p>
|
|
583
|
+
<p>Either way ForgeQE checks the sign-in by asking Copilot for your seat's models and lists them; the first time, the Copilot SDK downloads Copilot's runtime from GitHub (checked against GitHub's checksums). <code>forgeqe auth</code> shows the sign-in on its <code>Copilot</code> line; <code>forgeqe auth logout copilot</code> makes ForgeQE stop using it (GitHub keeps a browser sign-in until you revoke <strong>GitHub Copilot CLI</strong> at github.com/settings/applications).</p>
|
|
584
|
+
<p>If your organization uses SAML single sign-on, GitHub asks you to authorize the organization during the browser sign-in. A Copilot Business or Enterprise seat needs your organization to allow <strong>Copilot CLI</strong> in its Copilot policies.</p>
|
|
585
|
+
<p>A token reaches only the gateway's process. The engine gets the gateway's address and a one-time key that works nowhere else; the gateway refuses requests without that key and any request a web page sends. Model-run shell commands never see the token or the key.</p>
|
|
553
586
|
<p>Which part uses what:</p>
|
|
554
587
|
<div class="tbl"><table>
|
|
555
588
|
<thead>
|
|
@@ -570,13 +603,15 @@ bun src/index.ts init --global --profile azure-gpt4o
|
|
|
570
603
|
<tr><th>ForgeQE says</th><th>What to do</th></tr>
|
|
571
604
|
</thead>
|
|
572
605
|
<tbody>
|
|
573
|
-
<tr><td><code>GitHub Copilot
|
|
574
|
-
<tr><td><code>GitHub
|
|
606
|
+
<tr><td><code>GitHub Copilot is not signed in</code></td><td>Run <code>forgeqe auth login copilot</code> (or <code>/auth copilot</code> in the shell) and sign in in the browser, or set <code>COPILOT_GITHUB_TOKEN</code>.</td></tr>
|
|
607
|
+
<tr><td><code>The GitHub sign-in did not finish</code></td><td>The browser sign-in was cancelled or denied. Run <code>forgeqe auth login copilot</code> again; with SAML single sign-on, authorize your organization when GitHub asks.</td></tr>
|
|
608
|
+
<tr><td><code>GitHub Copilot does not take classic tokens (ghp_)</code></td><td>Sign in in the browser instead, or make a fine-grained token with the Copilot Requests permission, owned by your personal account.</td></tr>
|
|
575
609
|
<tr><td><code>GitHub Copilot did not accept the token</code></td><td>The token has no Copilot Requests permission, has expired, or the account has no Copilot seat.</td></tr>
|
|
576
610
|
<tr><td><code>… must be a Copilot model id</code>, <code>engine_model must be a Copilot model id</code></td><td>Fix that field in <code>llms.json</code>: the sign-in lists the seat's models. A GitHub Models id such as <code>openai/gpt-4.1</code> is not a Copilot id; use <code>gpt-4.1</code>.</td></tr>
|
|
577
611
|
<tr><td><code>ForgeQE's agent and Scribe cannot run on a github_copilot profile yet</code></td><td>Pick another profile for them (<code>--profile</code>, or Tab in the shell), or set <code>code.model</code>.</td></tr>
|
|
578
612
|
<tr><td>The model is refused, or not found</td><td>The seat's plan or your organization's Copilot policies do not allow that model: pick one the sign-in listed, or ask a Copilot admin to enable it.</td></tr>
|
|
579
613
|
<tr><td><code>GitHub Copilot runs through the ForgeQE engine, which is not set up</code></td><td>Run <code>forgeqe setup</code>.</td></tr>
|
|
614
|
+
<tr><td><code>CERTIFICATE_VERIFY_FAILED</code> while downloading Copilot's runtime</td><td>Your network inspects HTTPS. Update ForgeQE and run <code>forgeqe setup</code>, then sign in again: it uses the certificates your computer trusts (see <a href="../HOW_TO_USE.md#company-networks">Company networks</a>). If it still fails, run <code>forgeqe doctor</code> and send its output to IT.</td></tr>
|
|
580
615
|
</tbody>
|
|
581
616
|
</table></div>
|
|
582
617
|
</div></section>
|
|
@@ -600,7 +635,7 @@ forgeqe auth logout jira
|
|
|
600
635
|
<tbody>
|
|
601
636
|
<tr><td>Azure DevOps</td><td><strong>Sign in with Microsoft in the browser</strong> through the Azure CLI (<code>az login</code>); ForgeQE saves nothing, and the Azure CLI renews the sign-in. Or paste a personal access token: the token page opens with the scopes to tick. Azure DevOps Server (on-premises) uses a personal access token.</td></tr>
|
|
602
637
|
<tr><td>GitHub</td><td><strong>Sign in with GitHub in the browser</strong> through the GitHub CLI (<code>gh auth login</code>): ForgeQE shows the one-time code and opens the page. Or paste a token. Needs <code>gh</code> installed. <code>forgeqe auth login copilot</code> can use this sign-in for a <code>github_copilot</code> profile.</td></tr>
|
|
603
|
-
<tr><td>GitHub Copilot</td><td><code>forgeqe auth login copilot</code>: <strong>
|
|
638
|
+
<tr><td>GitHub Copilot</td><td><code>forgeqe auth login copilot</code>: <strong>Sign in with GitHub in the browser</strong> (GitHub's own Copilot sign-in), or paste a fine-grained token with the Copilot Requests permission. ForgeQE checks it with Copilot and lists the models your seat has. For a <code>github_copilot</code> profile; see <a href="../HOW_TO_USE.md#github-copilot-seats-as-the-model">GitHub Copilot seats as the model</a>.</td></tr>
|
|
604
639
|
<tr><td>Jira, Confluence</td><td>Your Atlassian API tokens page opens (Cloud) or your personal access tokens page (Server/Data Center); create one and paste it. Confluence on the same Cloud site as Jira reuses the Jira sign-in.</td></tr>
|
|
605
640
|
<tr><td>TestRail, Zephyr Scale, qTest, Xray Cloud</td><td>The page with your API key opens, or ForgeQE says where to find it; paste it.</td></tr>
|
|
606
641
|
<tr><td>Anything else</td><td><code>forgeqe auth login OPENAI_API_KEY</code> (any variable name) saves that one secret: a model key, a Teams webhook URL, an SMTP or saved-login password.</td></tr>
|
|
@@ -4617,7 +4652,7 @@ the list again. For a client that ignores it, <code>forgeqe mcp --all-tools</cod
|
|
|
4617
4652
|
<tr><td><code>WP_LLMS_JSON</code></td><td>Path to <code>llms.json</code></td></tr>
|
|
4618
4653
|
<tr><td><code>WP_LLM_PROFILE</code></td><td>Active profile, unless <code>--profile</code> is set</td></tr>
|
|
4619
4654
|
<tr><td><code>WP_AZURE_API_KEY</code> / <code>OPENAI_API_KEY</code> / <code>WP_API_KEY</code></td><td>Keys referenced by profiles</td></tr>
|
|
4620
|
-
<tr><td><code>COPILOT_GITHUB_TOKEN</code></td><td>A <code>github_copilot</code> profile's GitHub token (fine-grained, with the Copilot Requests permission), unless the profile's <code>api_key_env</code> names another; else the saved token, then the
|
|
4655
|
+
<tr><td><code>COPILOT_GITHUB_TOKEN</code></td><td>A <code>github_copilot</code> profile's GitHub token (fine-grained, with the Copilot Requests permission), unless the profile's <code>api_key_env</code> names another; else the saved token, then the browser sign-in (<code>forgeqe auth login copilot</code>)</td></tr>
|
|
4621
4656
|
<tr><td><code>FORGEQE_COPILOT_TOKEN</code></td><td>The token <code>forgeqe auth login copilot</code> saves; set by ForgeQE from the credential store, not by hand</td></tr>
|
|
4622
4657
|
<tr><td><code>FORGEQE_CONFIG_DIR</code></td><td>Global config folder (default <code>~/.config/forgeqe</code>): <code>forgeqe.yaml</code>, <code>llms.json</code> and <code>.env</code> used when a project has none</td></tr>
|
|
4623
4658
|
<tr><td><code>FORGEQE_PYTHON</code></td><td>A Python that already has the engine's libraries, used instead of the managed venv (skipped, with a note, when it cannot run the engine)</td></tr>
|
package/llms.example.json
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"copilot": {
|
|
52
52
|
"provider": "github_copilot",
|
|
53
53
|
"model": "gpt-4.1",
|
|
54
|
-
"description": "GitHub Copilot seat for the browser engine, through GitHub's Copilot SDK (forgeqe auth login copilot: a fine-grained token with Copilot Requests
|
|
54
|
+
"description": "GitHub Copilot seat for the browser engine, through GitHub's Copilot SDK (forgeqe auth login copilot: sign in with GitHub in the browser, or a fine-grained token with Copilot Requests)"
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capagents/forgeqe",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"description": "ForgeQE: a browser agent CLI that runs goals and test cases in local Chrome or Chromium, or BrowserStack and TestMu AI/LambdaTest when configured, and turns passing runs into tests in your repo",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@opentui/core": "^0.5.12"
|
|
62
62
|
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"@oven/bun-darwin-aarch64": "1.4.2",
|
|
65
|
+
"@oven/bun-darwin-x64-baseline": "1.4.2",
|
|
66
|
+
"@oven/bun-linux-aarch64": "1.4.2",
|
|
67
|
+
"@oven/bun-linux-aarch64-musl": "1.4.2",
|
|
68
|
+
"@oven/bun-linux-x64-baseline": "1.4.2",
|
|
69
|
+
"@oven/bun-linux-x64-musl-baseline": "1.4.2",
|
|
70
|
+
"@oven/bun-windows-aarch64": "1.4.2",
|
|
71
|
+
"@oven/bun-windows-x64-baseline": "1.4.2"
|
|
72
|
+
},
|
|
63
73
|
"devDependencies": {
|
|
64
74
|
"@opencode-ai/sdk": "1.18.33",
|
|
65
75
|
"@opentui/react": "^0.5.12",
|
|
@@ -7,9 +7,10 @@ import contextlib
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
+
import sys
|
|
10
11
|
import time
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import Any, Dict, Optional
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
13
14
|
from urllib.parse import quote, urlparse
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger("forgeqe_engine.service")
|
|
@@ -84,6 +85,24 @@ _UNSUPPORTED_BROWSER_USE_ARGS = (
|
|
|
84
85
|
_MAC_CHROME_EXECUTABLE = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
def find_edge_executable() -> Optional[str]:
|
|
89
|
+
"""
|
|
90
|
+
Microsoft Edge, which every Windows PC has: used when Google Chrome is not installed, so that no Chromium has to be
|
|
91
|
+
downloaded (company networks often block where it comes from).
|
|
92
|
+
"""
|
|
93
|
+
candidates: List[str] = []
|
|
94
|
+
if sys.platform == "win32":
|
|
95
|
+
for var in ("PROGRAMFILES(X86)", "PROGRAMFILES", "LOCALAPPDATA"):
|
|
96
|
+
base = os.environ.get(var)
|
|
97
|
+
if base:
|
|
98
|
+
candidates.append(os.path.join(base, "Microsoft", "Edge", "Application", "msedge.exe"))
|
|
99
|
+
elif sys.platform == "darwin":
|
|
100
|
+
candidates.append("/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge")
|
|
101
|
+
else:
|
|
102
|
+
candidates += ["/usr/bin/microsoft-edge", "/usr/bin/microsoft-edge-stable", "/opt/microsoft/msedge/msedge"]
|
|
103
|
+
return next((c for c in candidates if Path(c).is_file()), None)
|
|
104
|
+
|
|
105
|
+
|
|
87
106
|
def resolve_browser_channel() -> str:
|
|
88
107
|
"""Default is installed Google Chrome (`chrome`). Override with BROWSER_USE_CHANNEL (e.g. `chromium`)."""
|
|
89
108
|
raw = (os.getenv("BROWSER_USE_CHANNEL") or "chrome").strip().lower()
|
|
@@ -125,8 +144,12 @@ def local_browser_launch_kwargs(*, headless: bool) -> Dict[str, Any]:
|
|
|
125
144
|
"ignore_default_args": list(_UNSUPPORTED_BROWSER_USE_ARGS),
|
|
126
145
|
}
|
|
127
146
|
exe = resolve_chrome_executable_path(channel)
|
|
147
|
+
edge = None if exe or channel not in ("chrome", "msedge") else find_edge_executable()
|
|
128
148
|
if exe:
|
|
129
149
|
kw["executable_path"] = exe
|
|
150
|
+
elif edge:
|
|
151
|
+
kw["executable_path"] = edge
|
|
152
|
+
kw["channel"] = "msedge"
|
|
130
153
|
elif channel == "chrome":
|
|
131
154
|
# No Google Chrome here: let the library use its own Chromium instead of failing on the channel.
|
|
132
155
|
kw["channel"] = "chromium"
|
package/py/forgeqe_engine/cli.py
CHANGED
|
@@ -78,6 +78,10 @@ def _doctor(out: TextIO) -> int:
|
|
|
78
78
|
info["copilot_sdk"] = version("github-copilot-sdk")
|
|
79
79
|
except Exception:
|
|
80
80
|
pass
|
|
81
|
+
try:
|
|
82
|
+
info["truststore"] = version("truststore")
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
81
85
|
try:
|
|
82
86
|
info["video"] = version("imageio")
|
|
83
87
|
except Exception:
|
|
@@ -7,7 +7,8 @@ tools of its own (the system prompt replaced, screenshots attached, a JSON schem
|
|
|
7
7
|
OpenAI shape.
|
|
8
8
|
|
|
9
9
|
python -m forgeqe_engine copilot-gateway
|
|
10
|
-
env COPILOT_GITHUB_TOKEN
|
|
10
|
+
env COPILOT_GITHUB_TOKEN a fine-grained token with Copilot Requests; without it, the signed-in user
|
|
11
|
+
(forgeqe auth login copilot, kept under ~/.forgeqe/copilot)
|
|
11
12
|
FORGEQE_COPILOT_GATEWAY_KEY the one-time key every request must carry as "Bearer <key>"
|
|
12
13
|
stdout one line: {"port": N, "login": "<account>"} once it listens, or {"error": "..."}
|
|
13
14
|
stdin end of input stops it, so it never outlives ForgeQE
|
|
@@ -26,6 +27,8 @@ import time
|
|
|
26
27
|
import uuid
|
|
27
28
|
from typing import Any, Dict, List, Optional, TextIO, Tuple
|
|
28
29
|
|
|
30
|
+
from .systemcerts import use_system_certificates
|
|
31
|
+
|
|
29
32
|
TOKEN_ENV = "COPILOT_GITHUB_TOKEN"
|
|
30
33
|
KEY_ENV = "FORGEQE_COPILOT_GATEWAY_KEY"
|
|
31
34
|
|
|
@@ -181,23 +184,37 @@ def local_origin(headers: Dict[str, str]) -> bool:
|
|
|
181
184
|
# The Copilot SDK
|
|
182
185
|
|
|
183
186
|
|
|
187
|
+
def copilot_home() -> str:
|
|
188
|
+
"""Copilot's home for ForgeQE, where `forgeqe auth login copilot` signed in (src/auth/copilotLogin.ts)."""
|
|
189
|
+
return os.path.join(os.environ.get("FORGEQE_HOME") or os.path.join(os.path.expanduser("~"), ".forgeqe"), "copilot")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def client_options(token: Optional[str], home: str, workdir: str) -> Dict[str, Any]:
|
|
193
|
+
"""With a token, that token only; without one, the signed-in user, as the Copilot SDK recommends on a user's machine."""
|
|
194
|
+
auth: Dict[str, Any] = {"github_token": token, "use_logged_in_user": False} if token else {"use_logged_in_user": True}
|
|
195
|
+
return {**auth, "base_directory": home, "working_directory": workdir, "log_level": "error"}
|
|
196
|
+
|
|
197
|
+
|
|
184
198
|
class Copilot:
|
|
185
|
-
def __init__(self, token: str):
|
|
199
|
+
def __init__(self, token: Optional[str]):
|
|
186
200
|
from copilot import CopilotClient
|
|
187
201
|
|
|
188
202
|
# The SDK keeps its state under this folder, apart from the user's own Copilot CLI.
|
|
189
|
-
home =
|
|
203
|
+
home = copilot_home()
|
|
190
204
|
os.makedirs(home, exist_ok=True)
|
|
191
205
|
# An empty working folder: the session reads no project files and no custom instructions.
|
|
192
206
|
self.workdir = tempfile.mkdtemp(prefix="forgeqe-copilot-")
|
|
193
|
-
self.
|
|
207
|
+
self.signed_in_user = not token
|
|
208
|
+
self.client = CopilotClient(**client_options(token, home, self.workdir))
|
|
194
209
|
|
|
195
210
|
async def start(self) -> None:
|
|
196
211
|
await self.client.start()
|
|
197
212
|
status = await self.client.get_auth_status()
|
|
198
213
|
self.login = status.login or ""
|
|
199
214
|
if not status.isAuthenticated:
|
|
200
|
-
|
|
215
|
+
if self.signed_in_user:
|
|
216
|
+
raise GatewayError(401, "GitHub Copilot is not signed in: run forgeqe auth login copilot, which opens GitHub in your browser.")
|
|
217
|
+
raise GatewayError(401, "GitHub Copilot did not accept the token: use a fine-grained token with the Copilot Requests permission, for an account with a Copilot seat.")
|
|
201
218
|
|
|
202
219
|
async def stop(self) -> None:
|
|
203
220
|
try:
|
|
@@ -338,14 +355,11 @@ async def serve(out: TextIO, copilot: Any = None) -> int:
|
|
|
338
355
|
out.write(json.dumps({"error": f"{KEY_ENV} must hold the gateway's one-time key."}) + "\n")
|
|
339
356
|
return 2
|
|
340
357
|
if copilot is None:
|
|
341
|
-
if not token:
|
|
342
|
-
out.write(json.dumps({"error": "No GitHub token for Copilot: run forgeqe auth login copilot."}) + "\n")
|
|
343
|
-
return 1
|
|
344
358
|
if token.startswith("ghp_"):
|
|
345
359
|
out.write(json.dumps({"error": "GitHub Copilot does not take classic tokens (ghp_): use a fine-grained token with the Copilot Requests permission."}) + "\n")
|
|
346
360
|
return 1
|
|
347
361
|
try:
|
|
348
|
-
copilot = Copilot(token)
|
|
362
|
+
copilot = Copilot(token or None)
|
|
349
363
|
await copilot.start()
|
|
350
364
|
except GatewayError as e:
|
|
351
365
|
out.write(json.dumps({"error": e.message}) + "\n")
|
|
@@ -366,4 +380,5 @@ async def serve(out: TextIO, copilot: Any = None) -> int:
|
|
|
366
380
|
|
|
367
381
|
|
|
368
382
|
def main(out: TextIO) -> int:
|
|
383
|
+
use_system_certificates()
|
|
369
384
|
return asyncio.run(serve(out))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTPS certificates checked with the operating system's store (truststore), as pip and uv do. On a company network that
|
|
3
|
+
inspects HTTPS, the company's certificate authority is trusted by Windows or macOS but not by Python's own bundle
|
|
4
|
+
(certifi), and Python 3.13+ refuses such a CA whose Basic Constraints are not marked critical. With this, the engines'
|
|
5
|
+
calls to the model, the Copilot SDK's runtime download and any other HTTPS request work there.
|
|
6
|
+
|
|
7
|
+
Certificates are still verified, hostnames included; only who verifies them changes. FORGEQE_SYSTEM_CERTS=off leaves
|
|
8
|
+
Python's own checks in place (src/network.ts).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def use_system_certificates() -> bool:
|
|
16
|
+
if os.environ.get("FORGEQE_SYSTEM_CERTS", "").strip().lower() in ("0", "off", "false", "no"):
|
|
17
|
+
return False
|
|
18
|
+
try:
|
|
19
|
+
import truststore
|
|
20
|
+
except ImportError:
|
|
21
|
+
return False
|
|
22
|
+
truststore.inject_into_ssl()
|
|
23
|
+
return True
|