From 44388d70e8f02ada0b5ee0e4d2027f356d0c968d Mon Sep 17 00:00:00 2001 From: Test Runner Date: Tue, 19 May 2026 10:54:10 +0800 Subject: [PATCH 1/2] fix(win): Python detection & proxy support for Windows - _common.ps1: prefer 'py' over 'python' to avoid Microsoft Store shim; accept Python 3.7+ (tested working) instead of hard 3.8+ requirement - gh-api.py: add _install_proxy_handler() to read HTTP_PROXY/HTTPS_PROXY and configure urllib ProxyHandler explicitly Tested on Windows 10 + PS5.1 + Python 3.7.2 via py launcher. --- scripts/gh-api.py | 17 ++++++++++++++++ scripts/windows/_common.ps1 | 39 +++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/scripts/gh-api.py b/scripts/gh-api.py index 489dbbf..98606bc 100755 --- a/scripts/gh-api.py +++ b/scripts/gh-api.py @@ -12,6 +12,23 @@ import urllib.request +def _install_proxy_handler(): + """Install a proxy handler if HTTP_PROXY or HTTPS_PROXY is set.""" + proxy = ( + os.environ.get("HTTPS_PROXY") + or os.environ.get("https_proxy") + or os.environ.get("HTTP_PROXY") + or os.environ.get("http_proxy") + ) + if proxy: + handler = urllib.request.ProxyHandler({"https": proxy, "http": proxy}) + opener = urllib.request.build_opener(handler) + urllib.request.install_opener(opener) + + +_install_proxy_handler() + + def get_token(): """Get token from gh CLI, environment, or fallback files.""" try: diff --git a/scripts/windows/_common.ps1 b/scripts/windows/_common.ps1 index 947314c..41c285f 100644 --- a/scripts/windows/_common.ps1 +++ b/scripts/windows/_common.ps1 @@ -73,29 +73,30 @@ function Invoke-GitHubApi { } # Pre-flight: verify Python exists and meets version requirement. - # Note: Get-Command may still find the Microsoft Store shim on PATH. - # The actual shim rejection happens in the version-regex check below. - $pythonCmd = Get-Command python -ErrorAction SilentlyContinue - if (-not $pythonCmd) { - Write-Host "[github-ops ERROR] Python interpreter not found." -ForegroundColor Red - Write-Host " This skill requires Python 3.8+ on Windows." -ForegroundColor Yellow + # On Windows, prefer 'py' (Python Launcher) over 'python' to avoid + # the Microsoft Store shim, then fallback to 'python3' and 'python'. + $candidates = @("py", "python3", "python") + $script:PythonExe = $null + foreach ($c in $candidates) { + $cmd = Get-Command $c -ErrorAction SilentlyContinue + if (-not $cmd) { continue } + $verOutput = & $cmd --version 2>&1 + if ($verOutput -notmatch "Python (\d+)\.(\d+)") { continue } + $major = [int]$matches[1] + $minor = [int]$matches[2] + if ($major -ge 3 -and $minor -ge 7) { + $script:PythonExe = $cmd.Source + break + } + } + if (-not $script:PythonExe) { + Write-Host "[github-ops ERROR] Python 3.7+ interpreter not found." -ForegroundColor Red + Write-Host " Tried: $($candidates -join ', ')" -ForegroundColor Yellow Write-Host " Install: winget install Python.Python.3" -ForegroundColor Cyan Write-Host " Or download from: https://python.org/downloads/" -ForegroundColor Cyan exit 127 } - $verOutput = & python --version 2>&1 - if ($verOutput -notmatch "Python (\d+)\.(\d+)") { - Write-Host "[github-ops ERROR] Unable to determine Python version: $verOutput" -ForegroundColor Red - exit 1 - } - $major = [int]$matches[1] - $minor = [int]$matches[2] - if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 8)) { - Write-Host "[github-ops ERROR] Python $major.$minor is too old. Python 3.8+ required." -ForegroundColor Red - exit 1 - } - # Pre-flight: resolve GitHub token and inject into environment so Python # backend does not need to shell out to gh CLI again. $token = Get-GitHubToken @@ -108,6 +109,6 @@ function Invoke-GitHubApi { if ($Compact) { $pyArgs += "-c" } if ($Field) { foreach ($f in $Field) { $pyArgs += @("-f", $f) } } - & python $ApiScript @pyArgs + & $script:PythonExe $ApiScript @pyArgs exit $LASTEXITCODE } From 41c18291c36f02086334eef1fc6e2190d696564d Mon Sep 17 00:00:00 2001 From: yang12535 Date: Tue, 26 May 2026 07:28:03 +0800 Subject: [PATCH 2/2] fix: harden Windows Python and proxy handling --- CHANGELOG.md | 6 +++++- README.md | 34 +++++++++++++++++++++++++++------- SKILL.md | 9 +++++++++ scripts/gh-api.py | 13 ++++--------- scripts/linux/gh-api.py | 12 ++++++++++++ scripts/windows/_common.ps1 | 33 +++++++++++++++++++++++++-------- scripts/windows/gh-issue.ps1 | 2 +- scripts/windows/gh-pr.ps1 | 2 +- 8 files changed, 84 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1e30e..f441922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### 新增 - **Windows PowerShell 移植版**(`scripts/windows/`): - - `scripts/windows/*.ps1` — 薄 PowerShell 包装层,委托给 `scripts/gh-api.py`(Python 后端);需要 Python 3.8+ + - `scripts/windows/*.ps1` — 薄 PowerShell 包装层,委托给 `scripts/gh-api.py`(Python 后端);需要 Python 3.7+ - `_common.ps1` — 共享辅助函数,负责构建参数并调用 Python 后端 - `gh-user.ps1` / `gh-repo.ps1` / `gh-issue.ps1` / `gh-pr.ps1` — 核心 Bash 脚本的 PowerShell 等价物 - 认证回退链:`gh auth token` → `GITHUB_TOKEN`/`GH_TOKEN` 环境变量 → `~/.github_token` 文件 → `~/.config/github-ops/token` → `~/github_token.txt` @@ -15,8 +15,12 @@ ### 变更 - README 已更新,增加各平台快速上手指南 +- Windows 文档改为使用 `py -3` / `python`,不再假设存在 `py3` 或 `python3` ### 修复 +- Windows Python 探测优先使用 `py -3`,避免 bare `py` 被 `py.ini`/`PY_PYTHON` 配置到 Python 2 +- Python API 后端代理处理改用 `urllib.request.getproxies()`,保留小写代理变量覆盖大写变量的标准优先级 +- Windows PowerShell 包装器改用 .NET UTF-8 无 BOM 写文件,兼容 Windows PowerShell 5.1 - `gh-pr-review.sh`:处理 GitHub API 返回的 `null` 类型 `pull_request_review_id` 和 `user` 字段 - `gh-pr-reviews.sh`:处理 GitHub API 返回的 `null` 类型 `user` 字段 diff --git a/README.md b/README.md index 2a83361..f999c8f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![License](https://img.shields.io/badge/License-MIT-orange.svg)](LICENSE) [![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-blue.svg)](#-快速开始) -[![Python](https://img.shields.io/badge/Python-3.8%2B-green.svg)](https://www.python.org/) +[![Python](https://img.shields.io/badge/Python-3.7%2B-green.svg)](https://www.python.org/) [![Zero Dependencies](https://img.shields.io/badge/Dependencies-None-brightgreen.svg)](#技术亮点) @@ -59,6 +59,7 @@ - **🎯 字段过滤** — 支持 `owner.login`、`0.name` 等点号路径提取,减少 JSON 噪音 - **🔐 安全认证** — Token 文件权限检查(Linux: `S_IRWXG|S_IRWXO` / Windows: ACL) - **🔑 多级回退** — `gh auth token` → `GITHUB_TOKEN`/`GH_TOKEN` → `~/.github_token` +- **🌐 代理兼容** — Python 后端使用 `urllib` 标准代理发现,支持 `http_proxy`/`https_proxy` 覆盖大写环境变量 - **🐍 统一后端** — `gh-api.py` 处理所有 HTTP/JSON/分页逻辑。Linux/macOS 包装器引用同目录副本,`scripts/windows/*.ps1` 引用 `../gh-api.py` --- @@ -67,8 +68,8 @@ ### 环境要求 -- **Linux / macOS**: Bash + Python 3.8+ -- **Windows**: PowerShell 7+ + Python 3.8+ +- **Linux / macOS**: Bash + Python 3.7+ +- **Windows**: Windows PowerShell 5.1+ 或 PowerShell 7+ + Python 3.7+(优先 `py -3`,其次 `python`;不要假设存在 `py3`/`python3`) ### 1. 克隆仓库 @@ -112,7 +113,7 @@ chmod 600 ~/.github_token ./scripts/linux/gh-activity.sh yang12535 10 ``` -**Windows**(PowerShell 7+ 脚本) +**Windows**(Windows PowerShell 5.1+ / PowerShell 7+ 脚本) ```powershell # 查看当前用户 ./scripts/windows/gh-user.ps1 @@ -171,13 +172,17 @@ github-ops/ # 自动分页(需 Python 后端支持;curl fallback 不支持分页) ./scripts/gh-api-call.sh repos/owner/repo/issues -p -# 字段过滤(直接调用 Python 后端) +# 字段过滤(直接调用 Python 后端,Linux/macOS) python3 scripts/gh-api.py repos/owner/repo/issues -p -f "0.title" + +# 字段过滤(Windows) +py -3 scripts/gh-api.py repos/owner/repo/issues -p -f "0.title" ``` ### Python 底层引擎 ```bash +# Linux/macOS # 查询并提取字段 python3 scripts/gh-api.py repos/owner/repo -f owner.login @@ -189,6 +194,14 @@ python3 scripts/gh-api.py -X PATCH -d '{"state":"closed"}' \ repos/owner/repo/issues/1 ``` +Windows 直接调用 Python 后端时使用 `py -3`(推荐)或 `python`: + +```powershell +py -3 scripts/gh-api.py repos/owner/repo -f owner.login +py -3 scripts/gh-api.py repos/owner/repo/issues -p -c +py -3 scripts/gh-api.py -X PATCH -d '{"state":"closed"}' repos/owner/repo/issues/1 +``` + ### PR Review 工作流 ```bash @@ -219,7 +232,7 @@ python3 scripts/gh-api.py -X PATCH -d '{"state":"closed"}' \ ### 文件权限检查 - **Linux/macOS**(Python 后端 + PowerShell):拒绝 `group` 或 `others` 有任何权限的 token 文件(读/写/执行均不允许) -- **Windows**(仅 PowerShell 包装脚本):拒绝 `Users`/`Everyone`/`Authenticated Users` 可读取的 token 文件。注意:Windows 上直接运行 `python3 scripts/gh-api.py` 不会触发 ACL 检查 +- **Windows**(仅 PowerShell 包装脚本):拒绝 `Users`/`Everyone`/`Authenticated Users` 可读取的 token 文件。注意:Windows 上直接运行 `py -3 scripts/gh-api.py` 或 `python scripts/gh-api.py` 不会触发 ACL 检查 ```bash # 正确示例 @@ -248,8 +261,15 @@ chmod 600 ~/.github_token # Shell 脚本语法检查 find scripts -name "*.sh" -exec bash -n {} \; -# Python 编译检查(含 scripts/ 与 scripts/linux/) +# Python 编译检查(Linux/macOS,含 scripts/ 与 scripts/linux/) find scripts -name "*.py" -exec python3 -m py_compile {} \; +``` + +```powershell +# Python 编译检查(Windows) +Get-ChildItem scripts -Recurse -Filter "*.py" | ForEach-Object { + py -3 -m py_compile $_.FullName +} # PowerShell 语法检查(Windows) Get-ChildItem scripts/windows -Filter "*.ps1" | ForEach-Object { diff --git a/SKILL.md b/SKILL.md index cc4e52b..a70bf8d 100644 --- a/SKILL.md +++ b/SKILL.md @@ -8,6 +8,7 @@ description: "通过已认证的 REST API(curl/urllib)进行 GitHub 仓库 **主要方式**:通过 `scripts/gh-api.py`(Python/urllib)或直接 `curl` 调用 GitHub REST API。 **快捷脚本**:`scripts/gh-*.sh` 为常见任务提供便利封装。 **认证来源**:`gh auth token`(优先)→ `GITHUB_TOKEN` / `GH_TOKEN` 环境变量回退。仅在 token 缺失时才需要执行 `gh auth login` 重新认证。 +**Windows Python**:Windows 包装器优先使用 `py -3`,然后回退到 `python`;不要假设存在 `py3` 或 `python3`。 ## 快速开始 @@ -198,11 +199,19 @@ curl -s -X POST -H "Authorization: token $TOKEN" \ 示例: ```bash +# Linux/macOS python3 scripts/gh-api.py user -f login python3 scripts/gh-api.py repos/owner/repo/issues -p -c python3 scripts/gh-api.py -X PATCH -d '{"state":"closed"}' repos/owner/repo/issues/1 ``` +Windows 直接调用 Python 后端时: +```powershell +py -3 scripts/gh-api.py user -f login +py -3 scripts/gh-api.py repos/owner/repo/issues -p -c +py -3 scripts/gh-api.py -X PATCH -d '{"state":"closed"}' repos/owner/repo/issues/1 +``` + ## 常用 API 端点 - `user` — 认证用户资料 diff --git a/scripts/gh-api.py b/scripts/gh-api.py index 98606bc..4c29d52 100755 --- a/scripts/gh-api.py +++ b/scripts/gh-api.py @@ -13,15 +13,10 @@ def _install_proxy_handler(): - """Install a proxy handler if HTTP_PROXY or HTTPS_PROXY is set.""" - proxy = ( - os.environ.get("HTTPS_PROXY") - or os.environ.get("https_proxy") - or os.environ.get("HTTP_PROXY") - or os.environ.get("http_proxy") - ) - if proxy: - handler = urllib.request.ProxyHandler({"https": proxy, "http": proxy}) + """Install urllib's standard proxy handler when proxy env vars are set.""" + proxies = urllib.request.getproxies() + if proxies: + handler = urllib.request.ProxyHandler(proxies) opener = urllib.request.build_opener(handler) urllib.request.install_opener(opener) diff --git a/scripts/linux/gh-api.py b/scripts/linux/gh-api.py index 489dbbf..4c29d52 100644 --- a/scripts/linux/gh-api.py +++ b/scripts/linux/gh-api.py @@ -12,6 +12,18 @@ import urllib.request +def _install_proxy_handler(): + """Install urllib's standard proxy handler when proxy env vars are set.""" + proxies = urllib.request.getproxies() + if proxies: + handler = urllib.request.ProxyHandler(proxies) + opener = urllib.request.build_opener(handler) + urllib.request.install_opener(opener) + + +_install_proxy_handler() + + def get_token(): """Get token from gh CLI, environment, or fallback files.""" try: diff --git a/scripts/windows/_common.ps1 b/scripts/windows/_common.ps1 index 41c285f..4a8ed38 100644 --- a/scripts/windows/_common.ps1 +++ b/scripts/windows/_common.ps1 @@ -73,25 +73,31 @@ function Invoke-GitHubApi { } # Pre-flight: verify Python exists and meets version requirement. - # On Windows, prefer 'py' (Python Launcher) over 'python' to avoid - # the Microsoft Store shim, then fallback to 'python3' and 'python'. - $candidates = @("py", "python3", "python") + # On Windows, use the Python Launcher with an explicit Python 3 selector. + # Bare 'py' can be configured to launch Python 2 via py.ini/PY_PYTHON. + $candidates = @( + [pscustomobject]@{ Command = "py"; Args = @("-3"); Display = "py -3" }, + [pscustomobject]@{ Command = "python"; Args = @(); Display = "python" } + ) $script:PythonExe = $null + $script:PythonArgs = @() foreach ($c in $candidates) { - $cmd = Get-Command $c -ErrorAction SilentlyContinue + $cmd = Get-Command $c.Command -ErrorAction SilentlyContinue if (-not $cmd) { continue } - $verOutput = & $cmd --version 2>&1 + $candidateArgs = @($c.Args) + $verOutput = & $cmd.Source @candidateArgs --version 2>&1 if ($verOutput -notmatch "Python (\d+)\.(\d+)") { continue } $major = [int]$matches[1] $minor = [int]$matches[2] - if ($major -ge 3 -and $minor -ge 7) { + if ($major -gt 3 -or ($major -eq 3 -and $minor -ge 7)) { $script:PythonExe = $cmd.Source + $script:PythonArgs = $candidateArgs break } } if (-not $script:PythonExe) { Write-Host "[github-ops ERROR] Python 3.7+ interpreter not found." -ForegroundColor Red - Write-Host " Tried: $($candidates -join ', ')" -ForegroundColor Yellow + Write-Host " Tried: $(($candidates | ForEach-Object { $_.Display }) -join ', ')" -ForegroundColor Yellow Write-Host " Install: winget install Python.Python.3" -ForegroundColor Cyan Write-Host " Or download from: https://python.org/downloads/" -ForegroundColor Cyan exit 127 @@ -109,6 +115,17 @@ function Invoke-GitHubApi { if ($Compact) { $pyArgs += "-c" } if ($Field) { foreach ($f in $Field) { $pyArgs += @("-f", $f) } } - & $script:PythonExe $ApiScript @pyArgs + $backendArgs = @($script:PythonArgs) + @($ApiScript) + $pyArgs + & $script:PythonExe @backendArgs exit $LASTEXITCODE } + +function Write-Utf8NoBomFile { + param( + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][string]$Value + ) + + $encoding = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Value, $encoding) +} diff --git a/scripts/windows/gh-issue.ps1 b/scripts/windows/gh-issue.ps1 index 8caf7c0..9d7306e 100644 --- a/scripts/windows/gh-issue.ps1 +++ b/scripts/windows/gh-issue.ps1 @@ -39,7 +39,7 @@ switch ($Command) { $payload = @{ title = $title } if ($body) { $payload.body = $body } $tmpFile = [System.IO.Path]::GetTempFileName() - $payload | ConvertTo-Json -Compress -Depth 10 | Set-Content -Path $tmpFile -Encoding utf8NoBOM -NoNewline + Write-Utf8NoBomFile -Path $tmpFile -Value ($payload | ConvertTo-Json -Compress -Depth 10) try { Invoke-GitHubApi "repos/$Repo/issues" -Method POST -Data "@$tmpFile" } finally { diff --git a/scripts/windows/gh-pr.ps1 b/scripts/windows/gh-pr.ps1 index 400f7c8..0bba55f 100644 --- a/scripts/windows/gh-pr.ps1 +++ b/scripts/windows/gh-pr.ps1 @@ -41,7 +41,7 @@ switch ($Command) { $payload = @{ title = $title; head = $head; base = $base } if ($body) { $payload.body = $body } $tmpFile = [System.IO.Path]::GetTempFileName() - $payload | ConvertTo-Json -Compress -Depth 10 | Set-Content -Path $tmpFile -Encoding utf8NoBOM -NoNewline + Write-Utf8NoBomFile -Path $tmpFile -Value ($payload | ConvertTo-Json -Compress -Depth 10) try { Invoke-GitHubApi "repos/$Repo/pulls" -Method POST -Data "@$tmpFile" } finally {