diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 356b7ed..1664662 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,7 +12,7 @@ body: id: version attributes: label: ipcheck version - placeholder: ipcheck 0.8.2 + placeholder: ipcheck 0.9.0 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f18580..27b9165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to ipcheck are documented here. The project follows ## [Unreleased] +## [0.9.0] - 2026-07-23 + +### Changed + +- Rename the capped Cloudflare section to proxy-path reference transfers and + state directly in terminal, Markdown, JSON, and bilingual documentation that + it is not a peak-bandwidth test or AI API throughput measurement. +- Replace readiness-score rule v2 with rule v3: the measured AI service path + now supplies all positive points, while complete low Cloudflare samples can + only deduct two points per direction and incomplete samples deduct one. +- Describe Cloudflare results as high, moderate, or low samples instead of + presenting them as general-purpose fast/adequate/slow network ratings. +- Keep the existing JSON `bandwidth` object for compatibility while adding + explicit scope, method, API-path, and peak-bandwidth metadata. + +### Fixed + +- Rate reference samples using the same rounded Mbps value shown to users so a + displayed threshold value cannot contradict its label. +- Remove duplicate Cloudflare warnings from the primary AI-service diagnosis + and keep the auxiliary signal in its own clearly scoped section. + ## [0.8.2] - 2026-07-23 ### Fixed @@ -149,7 +171,8 @@ All notable changes to ipcheck are documented here. The project follows - Homebrew and direct-download packaging. - Median/P95 TTFB, jitter, reference bandwidth, and macOS `networkQuality`. -[Unreleased]: https://github.com/jacklv-coder/ipcheck/compare/v0.8.2...HEAD +[Unreleased]: https://github.com/jacklv-coder/ipcheck/compare/v0.9.0...HEAD +[0.9.0]: https://github.com/jacklv-coder/ipcheck/compare/v0.8.2...v0.9.0 [0.8.2]: https://github.com/jacklv-coder/ipcheck/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/jacklv-coder/ipcheck/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/jacklv-coder/ipcheck/compare/v0.7.0...v0.8.0 diff --git a/README.md b/README.md index c9cba48..30d8579 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ proxy before it breaks your coding flow. `ipcheck` is a zero-dependency Bash CLI that tests the real service routes used by AI coding clients. It reports reachability, median/P95 time to first byte, -jitter, reference download/upload speed, and a plain-language readiness score. +jitter, capped proxy-path transfer samples, and a plain-language readiness score. No Codex or Claude login is required. It never reads API keys, sends prompts, or makes billable model requests. @@ -35,7 +35,7 @@ brew upgrade ipcheck ```bash mkdir -p "$HOME/.local/bin" -curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.8.2/bin/ipcheck \ +curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.9.0/bin/ipcheck \ -o "$HOME/.local/bin/ipcheck" chmod +x "$HOME/.local/bin/ipcheck" ``` @@ -50,7 +50,7 @@ Make sure `$HOME/.local/bin` is on `PATH`. | --- | --- | | Speedtest | Peak bandwidth to a nearby test server | | `ping` / `curl` | Basic reachability to one address | -| `ipcheck` | Codex/Claude routes, proxy path, TTFB, P95, jitter, failures, bandwidth, and coding readiness | +| `ipcheck` | Codex/Claude routes, proxy path, TTFB, P95, jitter, failures, capped reference transfers, and coding readiness | For coding agents, a 500 Mbps connection can still feel slow when the first byte takes five seconds or the proxy route is unstable. `ipcheck` measures those @@ -62,8 +62,8 @@ interactive bottlenecks directly. broken one. - Understands OpenAI, Anthropic, LiteLLM-style gateways, and Alibaba Cloud Model Studio/DashScope Anthropic-compatible routes. -- Explains whether the main problem is reachability, TTFB, P95, jitter, - download speed, or upload speed. +- Explains whether the main problem is reachability, TTFB, P95, jitter, or a + low Cloudflare reference-transfer sample. - Produces human, Markdown, and stable versioned JSON reports. - Shows animated progress, adapts to narrow terminals, and exits cleanly with status `130` when cancelled with `Ctrl+C`. @@ -87,14 +87,14 @@ or Mantle. `ipcheck` warns instead of silently testing the wrong provider. | Command | Purpose | | --- | --- | | `ipcheck` | Auto-detect installed clients and run the standard check | -| `ipcheck --quick` | One sample, shorter timeout, no bandwidth transfer | +| `ipcheck --quick` | One sample, shorter timeout, no reference transfer | | `ipcheck codex` / `ipcheck claude` | Test only one client | | `ipcheck all` | Test Codex and Claude Code together | | `ipcheck --explain-score` | Show every score component | | `ipcheck --json` | Emit structured output for automation | | `ipcheck --markdown` | Create a shareable support report | | `ipcheck --system` | Add macOS `networkQuality` measurements | -| `ipcheck --no-bandwidth` | Skip capped Cloudflare download/upload checks | +| `ipcheck --no-bandwidth` | Skip capped Cloudflare reference transfers | Run `ipcheck --help` for every option and environment variable. @@ -115,8 +115,13 @@ TTFB is measured from a credential-free protocol request. It reflects DNS, proxy, TLS, network, and gateway ingress—not authenticated model generation or time to the model's first token. -The 0–100 score is a transparent rule, not a user percentile. It prioritizes -service reachability and latency over peak bandwidth. See +The Cloudflare samples describe small transfers from the current proxy/network +path to Cloudflare. They are not peak-bandwidth tests and do not represent +OpenAI, Anthropic, GitHub, or npm throughput. + +The 0–100 score is a transparent rule, not a user percentile. The measured AI +service path supplies all positive points; low Cloudflare samples can only make +a small deduction. See [Scoring and result rules](docs/scoring.md) for the exact calculation. ## Claude Code gateway example @@ -149,7 +154,7 @@ See [SECURITY.md](SECURITY.md) for private vulnerability reporting. ## Documentation - [Scoring and result rules](docs/scoring.md) -- [Routes, proxies, and bandwidth](docs/network.md) +- [Routes, proxies, and reference transfers](docs/network.md) - [Reports, automation, and exit codes](docs/reporting.md) - [Release history](CHANGELOG.md) diff --git a/README.zh-CN.md b/README.zh-CN.md index 0940274..db6298e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -10,7 +10,7 @@ 被阻断,还是走错了链路。 `ipcheck` 是一个零依赖 Bash CLI,检测 AI 编程客户端实际使用的服务路径, -提供可达性、TTFB 中位数/P95、抖动、参考上下行速度和直白的开发适配分。 +提供可达性、TTFB 中位数/P95、抖动、限量代理链路传输样本和直白的开发适配分。 无需登录 Codex 或 Claude;它不会读取 API Key、发送 Prompt,也不会产生 模型调用费用。 @@ -34,7 +34,7 @@ brew upgrade ipcheck ```bash mkdir -p "$HOME/.local/bin" -curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.8.2/bin/ipcheck \ +curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.9.0/bin/ipcheck \ -o "$HOME/.local/bin/ipcheck" chmod +x "$HOME/.local/bin/ipcheck" ``` @@ -49,7 +49,7 @@ chmod +x "$HOME/.local/bin/ipcheck" | --- | --- | | Speedtest | 到附近测速服务器的峰值带宽 | | `ping` / `curl` | 单个地址的基础连通性 | -| `ipcheck` | Codex/Claude 实际路径、代理、TTFB、P95、抖动、失败率、带宽和开发适配分 | +| `ipcheck` | Codex/Claude 实际路径、代理、TTFB、P95、抖动、失败率、限量参考传输和开发适配分 | 即使带宽达到 500 Mbps,只要首字节需要五秒或代理链路不稳定,AI 编程体验 仍然会很慢。`ipcheck` 直接测量这些影响交互节奏的瓶颈。 @@ -59,7 +59,8 @@ chmod +x "$HOME/.local/bin/ipcheck" - 分别检测每个客户端,避免一条健康链路掩盖另一条异常链路。 - 兼容 OpenAI、Anthropic、LiteLLM 类网关,以及阿里云百炼/DashScope Anthropic 兼容入口。 -- 直接指出主要问题来自可达性、TTFB、P95、抖动、下载还是上传。 +- 直接指出主要问题来自可达性、TTFB、P95、抖动,还是 Cloudflare 参考传输 + 样本偏低。 - 支持终端、Markdown 和稳定的版本化 JSON 报告。 - 提供动态进度、窄终端适配,并能通过 `Ctrl+C` 干净退出,返回状态码 `130`。 @@ -81,14 +82,14 @@ Claude Code 的 Bedrock、Vertex AI、Foundry、Mantle 等 provider 原生协议 | 命令 | 用途 | | --- | --- | | `ipcheck` | 自动识别客户端并执行标准检测 | -| `ipcheck --quick` | 单次采样、缩短超时、不传输测速数据 | +| `ipcheck --quick` | 单次采样、缩短超时、不运行参考传输 | | `ipcheck codex` / `ipcheck claude` | 只检测一个客户端 | | `ipcheck all` | 同时检测 Codex 和 Claude Code | | `ipcheck --explain-score` | 展开全部评分依据 | | `ipcheck --json` | 输出供自动化使用的结构化数据 | | `ipcheck --markdown` | 生成可分享的支持报告 | | `ipcheck --system` | 增加 macOS `networkQuality` 数据 | -| `ipcheck --no-bandwidth` | 跳过限量 Cloudflare 上下行检测 | +| `ipcheck --no-bandwidth` | 跳过限量 Cloudflare 参考传输 | 运行 `ipcheck --help` 可以查看全部选项和环境变量。 @@ -108,8 +109,12 @@ API 路径;HTTP `407` 表示请求被代理拦截。 TTFB 来自无凭据协议请求,反映 DNS、代理、TLS、网络与网关入口耗时;它不 包含认证后的模型生成时间,也不是模型首个 Token 的到达时间。 -0–100 分是透明的规则评分,不代表用户百分位,并且服务可达性和交互延迟 -比峰值带宽更重要。精确计算方式见[评分与结论规则](docs/scoring.zh-CN.md)。 +Cloudflare 样本反映的是当前代理/网络到 Cloudflare 的小文件传输表现,不是 +峰值宽带测速,也不代表 OpenAI、Anthropic、GitHub 或 npm 的吞吐。 + +0–100 分是透明的规则评分,不代表用户百分位。实际 AI 服务路径提供全部正向 +分数,Cloudflare 样本偏低时只会小幅扣分。精确计算方式见 +[评分与结论规则](docs/scoring.zh-CN.md)。 ## Claude Code 网关示例 @@ -141,7 +146,7 @@ TTFB 来自无凭据协议请求,反映 DNS、代理、TLS、网络与网关 ## 详细文档 - [评分与结论规则](docs/scoring.zh-CN.md) -- [服务路径、代理与带宽](docs/network.zh-CN.md) +- [服务路径、代理与参考传输](docs/network.zh-CN.md) - [报告、自动化与退出码](docs/reporting.zh-CN.md) - [版本历史](CHANGELOG.md) diff --git a/assets/ipcheck-demo-zh.gif b/assets/ipcheck-demo-zh.gif index e3eed44..28c48c6 100644 Binary files a/assets/ipcheck-demo-zh.gif and b/assets/ipcheck-demo-zh.gif differ diff --git a/assets/ipcheck-demo.gif b/assets/ipcheck-demo.gif index 3d2371e..3f0c678 100644 Binary files a/assets/ipcheck-demo.gif and b/assets/ipcheck-demo.gif differ diff --git a/assets/ipcheck-preview-zh.png b/assets/ipcheck-preview-zh.png index d7bf781..a844b5b 100644 Binary files a/assets/ipcheck-preview-zh.png and b/assets/ipcheck-preview-zh.png differ diff --git a/assets/ipcheck-preview.png b/assets/ipcheck-preview.png index ec4fce6..f4aecba 100644 Binary files a/assets/ipcheck-preview.png and b/assets/ipcheck-preview.png differ diff --git a/bin/ipcheck b/bin/ipcheck index c6f677a..6980488 100755 --- a/bin/ipcheck +++ b/bin/ipcheck @@ -10,7 +10,7 @@ unset LC_ALL LC_NUMERIC=C export LC_NUMERIC -VERSION="0.8.2" +VERSION="0.9.0" SCHEMA_VERSION=2 SAMPLES=3 TIMEOUT=15 @@ -43,8 +43,8 @@ Options: --endpoint URL Test custom GET endpoint(s) instead of client defaults. --samples N Requests per endpoint (default: 3). --timeout SECONDS Per-request timeout (default: 15). - --quick Use 1 sample, an 8s timeout, and skip bandwidth tests. - --no-bandwidth Skip reference download and upload tests. + --quick Use 1 sample, an 8s timeout, and skip reference transfers. + --no-bandwidth Skip capped Cloudflare reference transfers. --no-upload Skip only the 1 MB reference upload test. --system Also run macOS networkQuality when available. --color Always use terminal colors in human output. @@ -360,13 +360,13 @@ WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/ipcheck.XXXXXX") || die "cannot create a t # Preserve the caller's stderr so signal feedback is not swallowed by a # per-command `2>/dev/null` redirection active when the trap runs. exec 3>&2 -# shellcheck disable=SC2317 # Invoked indirectly by the EXIT trap. +# shellcheck disable=SC2317,SC2329 # Invoked indirectly by the EXIT trap. cleanup() { rm -rf "$WORK_DIR" } ACTIVE_CHILD_PID="" -# shellcheck disable=SC2317 # Invoked indirectly by signal handlers. +# shellcheck disable=SC2317,SC2329 # Invoked indirectly by signal handlers. stop_active_child() { stop_signal="$1" if [ -n "$ACTIVE_CHILD_PID" ]; then @@ -376,7 +376,7 @@ stop_active_child() { fi } -# shellcheck disable=SC2317 # Invoked indirectly by the INT trap. +# shellcheck disable=SC2317,SC2329 # Invoked indirectly by the INT trap. cancel_run() { cancel_status="$1" trap - INT @@ -394,7 +394,7 @@ cancel_run() { exit "$cancel_status" } -# shellcheck disable=SC2317 # Invoked indirectly by the TERM trap. +# shellcheck disable=SC2317,SC2329 # Invoked indirectly by the TERM trap. terminate_run() { trap - TERM stop_active_child TERM @@ -1008,14 +1008,14 @@ bandwidth_rating() { speed="$2" if [ "$direction" = "download" ]; then awk -v speed="$speed" 'BEGIN { - mbps = speed * 8 / 1000000 + mbps = sprintf("%.1f", speed * 8 / 1000000) + 0 if (mbps >= 25) print "fast" else if (mbps >= 5) print "adequate" else print "slow" }' else awk -v speed="$speed" 'BEGIN { - mbps = speed * 8 / 1000000 + mbps = sprintf("%.1f", speed * 8 / 1000000) + 0 if (mbps >= 10) print "fast" else if (mbps >= 2) print "adequate" else print "slow" @@ -1025,15 +1025,15 @@ bandwidth_rating() { localized_bandwidth_rating() { case "$UI_LANG:$1" in - zh:fast) printf '快' ;; - zh:adequate) printf '够用' ;; - zh:slow) printf '慢' ;; - zh:partial) printf '估算' ;; + zh:fast) printf '样本较高' ;; + zh:adequate) printf '样本一般' ;; + zh:slow) printf '样本偏低' ;; + zh:partial) printf '未完成估算' ;; zh:unavailable) printf '不可用' ;; - *:fast) printf 'FAST' ;; - *:adequate) printf 'ADEQUATE' ;; - *:slow) printf 'SLOW' ;; - *:partial) printf 'ESTIMATE' ;; + *:fast) printf 'HIGHER SAMPLE' ;; + *:adequate) printf 'MODERATE SAMPLE' ;; + *:slow) printf 'LOW SAMPLE' ;; + *:partial) printf 'INCOMPLETE ESTIMATE' ;; *) printf 'UNAVAILABLE' ;; esac } @@ -1041,10 +1041,10 @@ localized_bandwidth_rating() { partial_sample_note() { [ "$1" = "partial" ] || return 0 case "$2:$UI_LANG" in - human:zh) printf ' · 部分样本 -2' ;; - human:*) printf ' · partial sample -2' ;; - markdown:zh) printf ',部分样本 -2' ;; - markdown:*) printf ', partial sample -2' ;; + human:zh) printf ' · 未完成样本 -1' ;; + human:*) printf ' · incomplete sample -1' ;; + markdown:zh) printf ',未完成样本 -1' ;; + markdown:*) printf ', incomplete sample -1' ;; esac } @@ -1131,9 +1131,9 @@ download_rating="unavailable" download_speed_rating="unavailable" if [ "$RUN_BANDWIDTH" -eq 1 ]; then if [ "$UI_LANG" = "zh" ]; then - progress_note "●" "正在测试参考下载带宽(2 MB)…" + progress_note "●" "正在测试代理链路下行样本(2 MB)…" else - progress_note "●" "Testing reference download bandwidth (2 MB)…" + progress_note "●" "Testing proxy-path download sample (2 MB)…" fi bandwidth_metrics_file="$WORK_DIR/bandwidth-metrics.txt" curl_for_service "custom" --proto '=https' --http1.1 -sS -o /dev/null \ @@ -1171,9 +1171,9 @@ EOF fi if [ "$download_rating" = "slow" ] || [ "$download_rating" = "partial" ]; then progress_finish warn "Cloudflare" "$download_progress"; else progress_finish ok "Cloudflare" "$download_progress"; fi elif [ "$UI_LANG" = "zh" ]; then - progress_finish warn "Cloudflare" "参考带宽测试不可用" + progress_finish warn "Cloudflare" "参考传输样本不可用" else - progress_finish warn "Cloudflare" "reference bandwidth unavailable" + progress_finish warn "Cloudflare" "reference transfer sample unavailable" fi fi @@ -1186,9 +1186,9 @@ upload_rating="unavailable" upload_speed_rating="unavailable" if [ "$RUN_UPLOAD" -eq 1 ]; then if [ "$UI_LANG" = "zh" ]; then - progress_note "●" "正在测试参考上传带宽(1 MB 零字节)…" + progress_note "●" "正在测试代理链路上行样本(1 MB 零字节)…" else - progress_note "●" "Testing reference upload bandwidth (1 MB of zero bytes)…" + progress_note "●" "Testing proxy-path upload sample (1 MB of zero bytes)…" fi upload_file="$WORK_DIR/upload.bin" if head -c "$UPLOAD_BYTES" /dev/zero > "$upload_file" 2>/dev/null; then @@ -1258,39 +1258,39 @@ fi if [ "$UI_LANG" = "zh" ]; then if [ "$bandwidth_valid" -eq 0 ] && [ "$upload_valid" -eq 0 ]; then - bandwidth_advice="未获得有效的带宽样本,不能据此判断下载或上传速度。" + bandwidth_advice="未获得有效参考样本,不能据此判断当前代理链路的传输表现。" elif { [ "$bandwidth_valid" -eq 1 ] && [ "$bandwidth_complete" = false ]; } || { [ "$upload_valid" -eq 1 ] && [ "$upload_complete" = false ]; }; then - bandwidth_advice="测速在达到时间上限前未完成;显示值仅为部分样本估算,不能据此认定带宽充足。" + bandwidth_advice="传输在达到时间上限前未完成;显示值仅为部分样本估算,不能据此认定链路吞吐充足。" elif [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 0 ]; then - bandwidth_advice="下载测试不可用;当前建议仅依据上传结果。" + bandwidth_advice="下行样本不可用;当前提示仅依据上行样本。" elif [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 0 ]; then - bandwidth_advice="上传测试不可用;当前建议仅依据下载结果。" + bandwidth_advice="上行样本不可用;当前提示仅依据下行样本。" elif [ "$download_rating" = "slow" ] && [ "$upload_rating" = "slow" ]; then - bandwidth_advice="下载和上传都偏慢;安装依赖及发送大型代码上下文可能需要较长等待。" + bandwidth_advice="Cloudflare 上下行小文件样本都偏低,可能表示代理链路吞吐受限;请用目标服务或专用测速复核。" elif [ "$download_rating" = "slow" ]; then - bandwidth_advice="下载偏慢;拉取仓库和安装依赖可能需要较长等待。" + bandwidth_advice="Cloudflare 下行小文件样本偏低,可能表示代理链路吞吐受限;请用目标服务或专用测速复核。" elif [ "$upload_rating" = "slow" ]; then - bandwidth_advice="上传偏慢;发送大型代码上下文可能需要较长等待。" + bandwidth_advice="Cloudflare 上行小文件样本偏低,可能表示代理链路吞吐受限;请用目标服务或专用测速复核。" else - bandwidth_advice="当前带宽足以支持日常 AI 辅助开发;交互体验仍主要取决于服务延迟。" + bandwidth_advice="样本未显示明显的代理链路传输瓶颈;AI 交互体验仍主要取决于真实服务延迟。" fi else if [ "$bandwidth_valid" -eq 0 ] && [ "$upload_valid" -eq 0 ]; then - bandwidth_advice="No valid bandwidth sample was collected, so download and upload speed cannot be rated." + bandwidth_advice="No valid reference sample was collected, so the current proxy path's transfer performance cannot be assessed." elif { [ "$bandwidth_valid" -eq 1 ] && [ "$bandwidth_complete" = false ]; } || { [ "$upload_valid" -eq 1 ] && [ "$upload_complete" = false ]; }; then - bandwidth_advice="The test did not finish before its time limit; shown rates are partial estimates and do not prove bandwidth is sufficient." + bandwidth_advice="The transfer did not finish before its time limit; shown rates are partial estimates and do not prove that path throughput is sufficient." elif [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 0 ]; then - bandwidth_advice="The download test was unavailable; this advice is based on upload only." + bandwidth_advice="The download sample was unavailable; this note is based on the upload sample only." elif [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 0 ]; then - bandwidth_advice="The upload test was unavailable; this advice is based on download only." + bandwidth_advice="The upload sample was unavailable; this note is based on the download sample only." elif [ "$download_rating" = "slow" ] && [ "$upload_rating" = "slow" ]; then - bandwidth_advice="Download and upload are slow; dependencies and large code contexts may take noticeably longer." + bandwidth_advice="Both Cloudflare small-transfer samples are low, which may indicate constrained proxy throughput; verify against the target service or a dedicated speed test." elif [ "$download_rating" = "slow" ]; then - bandwidth_advice="Download is slow; repository fetches and dependency installs may take longer." + bandwidth_advice="The Cloudflare download sample is low, which may indicate constrained proxy throughput; verify against the target service or a dedicated speed test." elif [ "$upload_rating" = "slow" ]; then - bandwidth_advice="Upload is slow; sending large code contexts may take longer." + bandwidth_advice="The Cloudflare upload sample is low, which may indicate constrained proxy throughput; verify against the target service or a dedicated speed test." else - bandwidth_advice="Bandwidth is sufficient for everyday AI-assisted development; service latency remains the main interactive factor." + bandwidth_advice="The samples show no obvious proxy-path transfer bottleneck; real service latency remains the main interactive factor." fi fi @@ -1535,18 +1535,19 @@ case "$overall" in ;; esac -# Rule v2 is a transparent heuristic, not a user percentile. The worst client -# service path contributes up to 90 points: reachability 35, median TTFB 35, -# P95 10, and jitter 10. Reference bandwidth can add up to 10 points; slow or -# incomplete transfers can deduct points without outweighing service quality. +# Rule v3 is a transparent heuristic, not a user percentile. The worst client +# service path contributes up to 100 points: reachability 40, median TTFB 40, +# P95 10, and jitter 10. Capped Cloudflare transfers never add points because +# they do not represent peak bandwidth or AI API throughput. Low complete +# samples deduct two points per direction; incomplete samples deduct one. score_latency_points() { awk -v value="$1" 'BEGIN { if (value <= 0) print 0 - else if (value < 800) print 35 - else if (value < 1500) print 30 - else if (value < 3000) print 22 - else if (value < 5000) print 14 - else print 6 + else if (value < 800) print 40 + else if (value < 1500) print 34 + else if (value < 3000) print 25 + else if (value < 5000) print 16 + else print 7 }' } @@ -1570,11 +1571,9 @@ score_jitter_points() { }' } -score_bandwidth_points() { +score_reference_transfer_points() { case "$1" in - fast) printf 5 ;; - adequate) printf 3 ;; - slow) printf -- -5 ;; + slow) printf -- -2 ;; *) printf 0 ;; esac } @@ -1604,11 +1603,11 @@ while IFS="$TAB" read -r service result reason success_rate ttfb jitter service_ if [ "$result" = "blocked" ]; then availability_points=0 elif [ "$success_rate" -ge 100 ]; then - availability_points=35 + availability_points=40 elif [ "$success_rate" -ge 60 ]; then - availability_points=22 + availability_points=25 elif [ "$success_rate" -gt 0 ]; then - availability_points=10 + availability_points=11 else availability_points=0 fi @@ -1654,12 +1653,18 @@ while IFS="$TAB" read -r service result reason success_rate ttfb jitter service_ fi done < "$SERVICE_RESULT_FILE" -download_speed_points=$(score_bandwidth_points "$download_speed_rating") -upload_speed_points=$(score_bandwidth_points "$upload_speed_rating") +download_speed_points=$(score_reference_transfer_points "$download_speed_rating") +upload_speed_points=$(score_reference_transfer_points "$upload_speed_rating") download_partial_penalty=0 upload_partial_penalty=0 -[ "$download_rating" != "partial" ] || download_partial_penalty=-2 -[ "$upload_rating" != "partial" ] || upload_partial_penalty=-2 +[ "$download_rating" != "partial" ] || { + download_speed_points=0 + download_partial_penalty=-1 +} +[ "$upload_rating" != "partial" ] || { + upload_speed_points=0 + upload_partial_penalty=-1 +} download_score=$((download_speed_points + download_partial_penalty)) upload_score=$((upload_speed_points + upload_partial_penalty)) readiness_score_before_cap=$((score_service_path + download_score + upload_score)) @@ -1772,7 +1777,7 @@ localized_service_result() { render_primary_diagnosis() { [ "$overall" != "unavailable" ] || return 0 diagnosis_count=0 - if [ "$score_availability" -lt 35 ]; then + if [ "$score_availability" -lt 40 ]; then diagnosis_count=$((diagnosis_count + 1)) if [ "$UI_LANG" = "zh" ]; then printf ' %s!%s 链路可达率只有 %s%%,请求可能中断。\n' "$C_YELLOW" "$C_RESET" "$score_success_rate" @@ -1780,7 +1785,7 @@ render_primary_diagnosis() { printf ' %s!%s Reachability is only %s%%; requests may be interrupted.\n' "$C_YELLOW" "$C_RESET" "$score_success_rate" fi fi - if [ "$score_median_ttfb" -lt 30 ] && awk -v value="$score_ttfb_value" 'BEGIN { exit !(value >= 1500) }'; then + if awk -v value="$score_ttfb_value" 'BEGIN { exit !(value >= 1500) }'; then diagnosis_count=$((diagnosis_count + 1)) if [ "$UI_LANG" = "zh" ]; then printf ' %s!%s 首字节中位延迟 %s,网络或网关入口可能带来明显等待。\n' "$C_YELLOW" "$C_RESET" "$(format_ui_latency "$score_ttfb_value")" @@ -1796,14 +1801,6 @@ render_primary_diagnosis() { printf ' %s!%s Jitter is %s; wait times will vary during an interactive session.\n' "$C_YELLOW" "$C_RESET" "$(format_ui_latency "$score_jitter_value")" fi fi - if [ "$download_speed_rating" = "slow" ]; then - diagnosis_count=$((diagnosis_count + 1)) - if [ "$UI_LANG" = "zh" ]; then - printf ' %s!%s 下载 %s,拉取仓库和安装依赖会偏慢。\n' "$C_YELLOW" "$C_RESET" "$(format_mbps "$bandwidth_speed")" - else - printf ' %s!%s Download is %s; clones and dependency installs will be slower.\n' "$C_YELLOW" "$C_RESET" "$(format_mbps "$bandwidth_speed")" - fi - fi return 0 } @@ -1812,15 +1809,15 @@ render_score_breakdown() { upload_score_label=$(localized_bandwidth_rating "$upload_speed_rating") if [ "$UI_LANG" = "zh" ]; then printf ' %s评分构成%s\n' "$C_BOLD" "$C_RESET" - printf ' 服务链路 %2s/90 可达性 %s · TTFB %s · P95 %s · 抖动 %s\n' "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" - printf ' 下载 %3s %s · %s%s\n' "$(signed_points "$download_score")" "$(format_mbps "$bandwidth_speed")" "$download_score_label" "$(partial_sample_note "$download_rating" human)" - printf ' 上传 %3s %s · %s%s\n' "$(signed_points "$upload_score")" "$(format_mbps "$upload_speed")" "$upload_score_label" "$(partial_sample_note "$upload_rating" human)" + printf ' 服务链路 %3s/100 可达性 %s · TTFB %s · P95 %s · 抖动 %s\n' "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" + printf ' 下行参考样本 %3s %s · %s%s\n' "$(signed_points "$download_score")" "$(format_mbps "$bandwidth_speed")" "$download_score_label" "$(partial_sample_note "$download_rating" human)" + printf ' 上行参考样本 %3s %s · %s%s\n' "$(signed_points "$upload_score")" "$(format_mbps "$upload_speed")" "$upload_score_label" "$(partial_sample_note "$upload_rating" human)" [ "$readiness_score_before_cap" -le "$readiness_score_cap" ] || printf ' 结论限幅 %s → %s\n' "$readiness_score_before_cap" "$readiness_score_cap" else printf ' %sScore breakdown%s\n' "$C_BOLD" "$C_RESET" - printf ' Service path %2s/90 reachability %s · TTFB %s · P95 %s · jitter %s\n' "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" - printf ' Download %3s %s · %s%s\n' "$(signed_points "$download_score")" "$(format_mbps "$bandwidth_speed")" "$download_score_label" "$(partial_sample_note "$download_rating" human)" - printf ' Upload %3s %s · %s%s\n' "$(signed_points "$upload_score")" "$(format_mbps "$upload_speed")" "$upload_score_label" "$(partial_sample_note "$upload_rating" human)" + printf ' Service path %3s/100 reachability %s · TTFB %s · P95 %s · jitter %s\n' "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" + printf ' Download reference %3s %s · %s%s\n' "$(signed_points "$download_score")" "$(format_mbps "$bandwidth_speed")" "$download_score_label" "$(partial_sample_note "$download_rating" human)" + printf ' Upload reference %3s %s · %s%s\n' "$(signed_points "$upload_score")" "$(format_mbps "$upload_speed")" "$upload_score_label" "$(partial_sample_note "$upload_rating" human)" [ "$readiness_score_before_cap" -le "$readiness_score_cap" ] || printf ' Verdict cap %s → %s\n' "$readiness_score_before_cap" "$readiness_score_cap" fi } @@ -1965,24 +1962,25 @@ EOF done < "$SERVICE_RESULT_FILE" fi - if [ "$UI_LANG" = "zh" ]; then section_title "网络带宽"; else section_title "Network bandwidth"; fi + if [ "$UI_LANG" = "zh" ]; then section_title "代理链路参考传输"; else section_title "Proxy-path reference transfer"; fi case "$download_rating" in fast) download_color="$C_GREEN" ;; adequate|partial) download_color="$C_YELLOW" ;; *) download_color="$C_RED" ;; esac case "$upload_rating" in fast) upload_color="$C_GREEN" ;; adequate|partial) upload_color="$C_YELLOW" ;; *) upload_color="$C_RED" ;; esac if [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf ' 下载 %-12s %s%s%s Cloudflare,最多 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; else printf ' Download %-12s %s%s%s Cloudflare, up to 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 下行样本 %-12s %s%s%s Cloudflare,最多 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; else printf ' Down sample %-12s %s%s%s Cloudflare, up to 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; fi elif [ "$RUN_BANDWIDTH" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf ' 下载 不可用\n'; else printf ' Download unavailable\n'; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 下行样本 不可用\n'; else printf ' Down sample unavailable\n'; fi else - if [ "$UI_LANG" = "zh" ]; then printf ' 下载 已跳过\n'; else printf ' Download skipped\n'; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 下行样本 已跳过\n'; else printf ' Down sample skipped\n'; fi fi if [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf ' 上传 %-12s %s%s%s Cloudflare,最多 1 MB 零字节\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; else printf ' Upload %-12s %s%s%s Cloudflare, up to 1 MB zero-filled\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 上行样本 %-12s %s%s%s Cloudflare,最多 1 MB 零字节\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; else printf ' Up sample %-12s %s%s%s Cloudflare, up to 1 MB zero-filled\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; fi elif [ "$RUN_UPLOAD" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf ' 上传 不可用\n'; else printf ' Upload unavailable\n'; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 上行样本 不可用\n'; else printf ' Up sample unavailable\n'; fi else - if [ "$UI_LANG" = "zh" ]; then printf ' 上传 已跳过\n'; else printf ' Upload skipped\n'; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 上行样本 已跳过\n'; else printf ' Up sample skipped\n'; fi fi if [ "$RUN_BANDWIDTH" -eq 1 ] || [ "$RUN_UPLOAD" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then printf ' %s范围%s 当前代理 → Cloudflare;小文件样本,非峰值宽带,不代表 AI API 吞吐。\n' "$C_DIM" "$C_RESET"; else printf ' %sScope%s Current proxy → Cloudflare; small transfers, not peak bandwidth or AI API throughput.\n' "$C_DIM" "$C_RESET"; fi if [ "$UI_LANG" = "zh" ]; then printf ' %s建议%s %s\n' "$C_DIM" "$C_RESET" "$bandwidth_advice"; else printf ' %sAdvice%s %s\n' "$C_DIM" "$C_RESET" "$bandwidth_advice"; fi fi if [ -n "$system_throughput" ]; then @@ -2011,7 +2009,7 @@ render_markdown() { printf -- '- 现在适合开发吗?**%s** — %s\n\n' "$readiness_answer" "$readiness_summary" printf -- '- 开发适配分:**%s/100 · %s**(规则评分,不是用户百分位)\n\n' "$readiness_score" "$readiness_score_label" if [ "$EXPLAIN_SCORE" -eq 1 ]; then - printf -- '- 评分依据:服务链路 %s/90(可达性 %s + TTFB %s + P95 %s + 抖动 %s);下载 %s(%s%s);上传 %s(%s%s);限幅前 %s;结论上限 %s;总分 %s。\n\n' \ + printf -- '- 评分依据:服务链路 %s/100(可达性 %s + TTFB %s + P95 %s + 抖动 %s);下行参考样本 %s(%s%s);上行参考样本 %s(%s%s);限幅前 %s;结论上限 %s;总分 %s。\n\n' \ "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" \ "$download_score" "$(localized_bandwidth_rating "$download_speed_rating")" "$(partial_sample_note "$download_rating" markdown)" \ "$upload_score" "$(localized_bandwidth_rating "$upload_speed_rating")" "$(partial_sample_note "$upload_rating" markdown)" \ @@ -2027,7 +2025,7 @@ render_markdown() { printf -- '- Ready to code? **%s** — %s\n\n' "$readiness_answer" "$readiness_summary" printf -- '- Readiness score: **%s/100 · %s** (rule-based, not a user percentile)\n\n' "$readiness_score" "$readiness_score_label" if [ "$EXPLAIN_SCORE" -eq 1 ]; then - printf -- '- Score breakdown: service path %s/90 (reachability %s + TTFB %s + P95 %s + jitter %s); download %s (%s%s); upload %s (%s%s); pre-cap %s; verdict cap %s; total %s.\n\n' \ + printf -- '- Score breakdown: service path %s/100 (reachability %s + TTFB %s + P95 %s + jitter %s); download reference %s (%s%s); upload reference %s (%s%s); pre-cap %s; verdict cap %s; total %s.\n\n' \ "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" \ "$download_score" "$(localized_bandwidth_rating "$download_speed_rating")" "$(partial_sample_note "$download_rating" markdown)" \ "$upload_score" "$(localized_bandwidth_rating "$upload_speed_rating")" "$(partial_sample_note "$upload_rating" markdown)" \ @@ -2068,17 +2066,19 @@ render_markdown() { printf '| %s | %s | %s | %s | %s/%s | %.0f ms | %.0f ms | %.0f ms |\n' "$(service_display "$service")" "$label" "$reachable" "$http_code" "$successful_samples" "$samples" "$ttfb" "$p95" "$jitter" done < "$ENDPOINT_JSON_FILE" if [ "$UI_LANG" = "zh" ]; then - printf '\n## 网络带宽\n\n' - printf '| 方向 | 速度 | 评级 | 测试数据 |\n|---|---:|---|---|\n' - if [ "$bandwidth_valid" -eq 1 ]; then printf '| 下载 | %s | %s | Cloudflare,最多 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| 下载 | — | 不可用或已跳过 | — |\n'; fi - if [ "$upload_valid" -eq 1 ]; then printf '| 上传 | %s | %s | Cloudflare,最多 1 MB 零字节 |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| 上传 | — | 不可用或已跳过 | — |\n'; fi - printf '\n- 测速路径:%s%s%s\n- 建议:%s\n' '`' "$proxy_text" '`' "$bandwidth_advice" + printf '\n## 代理链路参考传输\n\n' + printf '> 当前代理到 Cloudflare 的小文件样本;不是峰值宽带测速,也不代表 AI API、GitHub 或 npm 的吞吐。\n\n' + printf '| 方向 | 样本速率 | 样本判断 | 测试数据 |\n|---|---:|---|---|\n' + if [ "$bandwidth_valid" -eq 1 ]; then printf '| 下行 | %s | %s | Cloudflare,最多 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| 下行 | — | 不可用或已跳过 | — |\n'; fi + if [ "$upload_valid" -eq 1 ]; then printf '| 上行 | %s | %s | Cloudflare,最多 1 MB 零字节 |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| 上行 | — | 不可用或已跳过 | — |\n'; fi + printf '\n- 参考路径:%s%s%s\n- 提示:%s\n' '`' "$proxy_text" '`' "$bandwidth_advice" else - printf '\n## Network bandwidth\n\n' - printf '| Direction | Speed | Rating | Test data |\n|---|---:|---|---|\n' - if [ "$bandwidth_valid" -eq 1 ]; then printf '| Download | %s | %s | Cloudflare, up to 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| Download | — | unavailable or skipped | — |\n'; fi - if [ "$upload_valid" -eq 1 ]; then printf '| Upload | %s | %s | Cloudflare, up to 1 MB zero-filled |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| Upload | — | unavailable or skipped | — |\n'; fi - printf '\n- Test path: %s%s%s\n- Advice: %s\n' '`' "$proxy_text" '`' "$bandwidth_advice" + printf '\n## Proxy-path reference transfer\n\n' + printf '> Small-transfer samples from the current proxy to Cloudflare; not a peak bandwidth test and not AI API, GitHub, or npm throughput.\n\n' + printf '| Direction | Sample rate | Sample assessment | Test data |\n|---|---:|---|---|\n' + if [ "$bandwidth_valid" -eq 1 ]; then printf '| Down | %s | %s | Cloudflare, up to 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| Down | — | unavailable or skipped | — |\n'; fi + if [ "$upload_valid" -eq 1 ]; then printf '| Up | %s | %s | Cloudflare, up to 1 MB zero-filled |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| Up | — | unavailable or skipped | — |\n'; fi + printf '\n- Reference path: %s%s%s\n- Note: %s\n' '`' "$proxy_text" '`' "$bandwidth_advice" fi if [ -n "$system_throughput" ]; then printf "macOS networkQuality: download **%s**" "$(format_bits_mbps "$system_throughput")" @@ -2111,7 +2111,7 @@ render_json() { warnings_json="$warnings_json$separator\"$(json_escape "$warning")\"" separator="," done < "$WARNINGS_FILE" - printf '{"schema_version":%s,"version":"%s","timestamp":"%s","proxy":"%s","result":"%s","reason":"%s","developer_readiness":{"ready":%s,"level":"%s","score":%s,"score_label":"%s","score_method":"rule_v2","score_breakdown":{"scored_service":"%s","service_path":%s,"availability":%s,"median_ttfb":%s,"p95":%s,"jitter":%s,"download":%s,"download_speed":%s,"download_partial_penalty":%s,"upload":%s,"upload_speed":%s,"upload_partial_penalty":%s,"pre_cap_total":%s,"verdict_cap":%s},"summary":"%s","recommendation":"%s"},"reachable_endpoints":%s,"primary_success_rate_pct":%s,"primary_ttfb_median_ms":%s,"primary_jitter_ms":%s,"privacy":{"credentials_used":false,"billable_requests":false,"upload_payload":"zero-filled"},"warnings":[%s],"bandwidth":{"enabled":%s,"available":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"path":"%s","download":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"},"upload":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"}},"system":{"enabled":%s,"interface":"%s","base_rtt_ms":%s,"download_bits_per_second":%s,"upload_bits_per_second":%s,"proxy_state":"%s"},"services":[%s],"endpoints":[%s]}\n' \ + printf '{"schema_version":%s,"version":"%s","timestamp":"%s","proxy":"%s","result":"%s","reason":"%s","developer_readiness":{"ready":%s,"level":"%s","score":%s,"score_label":"%s","score_method":"rule_v3","score_breakdown":{"scored_service":"%s","service_path":%s,"availability":%s,"median_ttfb":%s,"p95":%s,"jitter":%s,"download":%s,"download_speed":%s,"download_partial_penalty":%s,"upload":%s,"upload_speed":%s,"upload_partial_penalty":%s,"pre_cap_total":%s,"verdict_cap":%s},"summary":"%s","recommendation":"%s"},"reachable_endpoints":%s,"primary_success_rate_pct":%s,"primary_ttfb_median_ms":%s,"primary_jitter_ms":%s,"privacy":{"credentials_used":false,"billable_requests":false,"upload_payload":"zero-filled"},"warnings":[%s],"bandwidth":{"enabled":%s,"available":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"path":"%s","scope":"cloudflare_reference_path","method":"single_capped_transfer","represents_api_path":false,"represents_peak_bandwidth":false,"download":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"},"upload":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"}},"system":{"enabled":%s,"interface":"%s","base_rtt_ms":%s,"download_bits_per_second":%s,"upload_bits_per_second":%s,"proxy_state":"%s"},"services":[%s],"endpoints":[%s]}\n' \ "$SCHEMA_VERSION" "$VERSION" "$timestamp" "$(json_escape "$proxy_text")" "$overall" "$(json_escape "$overall_reason")" \ "$readiness_ready" "$readiness_level" "$readiness_score" "$(json_escape "$readiness_score_label_en")" \ "$(json_escape "$score_service")" "$score_service_path" "$score_availability" "$score_median_ttfb" "$score_p95" "$score_jitter" \ diff --git a/demo/render_demo.py b/demo/render_demo.py index 1a269ac..80dd172 100644 --- a/demo/render_demo.py +++ b/demo/render_demo.py @@ -180,8 +180,8 @@ def demo_output(language: str, scenario: str) -> list[str]: " █", " 评分构成", " 服务链路", - " 下载", - " 上传", + " 下行参考样本", + " 上行参考样本", " 当前网络", " 可以正常开发", " 建议先", @@ -195,9 +195,11 @@ def demo_output(language: str, scenario: str) -> list[str]: " HTTP", "◆ AI 服务结论", " ●", - "◆ 网络带宽", - " 下载", - " 上传", + "◆ 代理链路参考传输", + " 下行样本", + " 上行样本", + " 范围", + " 建议", ) else: report_prefixes = ( @@ -220,8 +222,8 @@ def demo_output(language: str, scenario: str) -> list[str]: " █", " Score breakdown", " Service path", - " Download", - " Upload", + " Download reference", + " Upload reference", " This network", " You can work normally", " Switch proxy routes", @@ -235,9 +237,11 @@ def demo_output(language: str, scenario: str) -> list[str]: " HTTP", "◆ AI service results", " ●", - "◆ Network bandwidth", - " Download", - " Upload", + "◆ Proxy-path reference transfer", + " Down sample", + " Up sample", + " Scope", + " Advice", ) report = [line for line in process.stdout.splitlines() if line.startswith(report_prefixes)] return [ANSI_RE.sub("", line).removeprefix("⌨ ") for line in progress + report] @@ -250,7 +254,7 @@ def line_color(line: str) -> str: return GREEN if score >= 90 else YELLOW if score >= 65 else RED if any(token in line for token in ("Ready to code? YES", "现在适合开发吗?适合")): return GREEN - if line.startswith(("◆ Detected clients", "◆ AI service latency", "◆ AI service results", "◆ Network bandwidth", "◆ 检测到的客户端", "◆ AI 服务延迟", "◆ AI 服务结论", "◆ 网络带宽", "ipcheck v")): + if line.startswith(("◆ Detected clients", "◆ AI service latency", "◆ AI service results", "◆ Proxy-path reference transfer", "◆ 检测到的客户端", "◆ AI 服务延迟", "◆ AI 服务结论", "◆ 代理链路参考传输", "ipcheck v")): return CYAN if line.startswith(("Checking", "Press Ctrl+C", "按 Ctrl+C")): return MUTED diff --git a/docs/launch-kit.md b/docs/launch-kit.md index fe4cc23..be27b52 100644 --- a/docs/launch-kit.md +++ b/docs/launch-kit.md @@ -15,10 +15,11 @@ I built ipcheck after repeatedly wondering whether an AI coding CLI was slow because of the model, my proxy, or the configured gateway. It is a zero-dependency Bash CLI that detects Codex and Claude Code routes, then -measures reachability, TTFB median/P95, jitter, and small reference download and -upload samples. It gives each client a separate verdict and a transparent -readiness score. HTTP 401/403 is treated as reachable, so no API key or billable -model request is needed. +measures reachability, TTFB median/P95, jitter, and capped Cloudflare +reference-transfer samples. The Cloudflare samples describe only the current +proxy path, not peak bandwidth or AI API throughput. It gives each client a +separate verdict and a transparent readiness score. HTTP 401/403 is treated as +reachable, so no API key or billable model request is needed. Install: @@ -46,8 +47,10 @@ than peak download speed. ipcheck tests the real routes configured for Codex and Claude Code without reading or sending credentials. It reports per-client latency and reachability, -small reference download/upload measurements, and a plain-language “ready to -code?” verdict. It is MIT licensed and runs with Bash + curl on macOS and Linux. +capped Cloudflare reference-transfer samples, and a plain-language “ready to +code?” verdict. Those samples describe the current proxy path, not peak +bandwidth or AI API throughput. It is MIT licensed and runs with Bash + curl on +macOS and Linux. Repo and demo: https://github.com/jacklv-coder/ipcheck @@ -63,9 +66,10 @@ Repo and demo: https://github.com/jacklv-coder/ipcheck 普通测速很难回答。 我做了一个开源命令行工具 ipcheck。它会自动识别 Codex 和 Claude Code 的真实 -网络路径,检测可达性、TTFB 中位数/P95、抖动,并用少量数据测试参考下载和 -上传速度,最后直接给出“现在是否适合开发”和透明的规则评分。HTTP 401/403 -只代表链路可达;工具不会读取或发送 API Key,也不会产生模型调用费用。 +网络路径,检测可达性、TTFB 中位数/P95、抖动,并用少量数据采集当前代理到 +Cloudflare 的参考传输样本,最后直接给出“现在是否适合开发”和透明的规则评分。 +这个样本不是峰值宽带或 AI API 吞吐;HTTP 401/403 只代表链路可达。工具不会 +读取或发送 API Key,也不会产生模型调用费用。 安装: diff --git a/docs/network.md b/docs/network.md index dc12a33..3395a54 100644 --- a/docs/network.md +++ b/docs/network.md @@ -1,4 +1,4 @@ -# Routes, proxies, and bandwidth +# Routes, proxies, and reference transfers [README](../README.md) · [简体中文](network.zh-CN.md) @@ -67,16 +67,33 @@ Given this Claude Code configuration: `https://dashscope.aliyuncs.com/apps/anthropic/v1/messages` with an empty, unauthenticated protocol request. It never reads `ANTHROPIC_AUTH_TOKEN`. -## Reference bandwidth +## Cloudflare reference transfer The service table measures time to first byte and jitter; it is not a transfer -speed test. The separate bandwidth section: +speed test. The separate reference-transfer section: -- downloads at most 2 MB from Cloudflare; -- uploads at most 1 MB of zero-filled data; +- downloads one capped sample of at most 2 MB from Cloudflare; +- uploads one capped sample of at most 1 MB of zero-filled data; - uses the reported proxy/network path; - treats incomplete transfers as estimates; -- never lets high bandwidth hide an unhealthy service route. +- never adds readiness points for a high result. + +`https://speed.cloudflare.com/__down` is the download API used by +[Cloudflare's official speed-test engine](https://github.com/cloudflare/speedtest). +The official engine combines repeated transfers at multiple sizes; ipcheck's +single capped transfer deliberately does not reproduce that methodology. + +The sample answers a narrow question: how a small transfer to Cloudflare +behaved on the current proxy/network path at that moment. It is not: + +- an ISP or peak-bandwidth benchmark; +- a measurement of OpenAI, Anthropic, GitHub, or npm throughput; +- a substitute for the service-specific reachability and TTFB probes. + +A complete low sample can deduct two readiness points per direction. A high or +moderate sample adds no points, an unavailable/skipped sample has no effect, +and an incomplete sample deducts one point per direction. This keeps the +reference signal secondary to the actual AI service path. Use `--no-upload` to skip upload only, or `--no-bandwidth` to skip both. The optional `--system` flag runs macOS `networkQuality`, which may transfer diff --git a/docs/network.zh-CN.md b/docs/network.zh-CN.md index 48a7c92..0f8a892 100644 --- a/docs/network.zh-CN.md +++ b/docs/network.zh-CN.md @@ -1,4 +1,4 @@ -# 服务路径、代理与带宽 +# 服务路径、代理与参考传输 [中文 README](../README.zh-CN.md) · [English](network.md) @@ -63,15 +63,31 @@ provider 给出结论。该 provider 会显示为“已跳过”;没有其他 `https://dashscope.aliyuncs.com/apps/anthropic/v1/messages` 发送空的、未认证的 协议检测请求,并且不会读取 `ANTHROPIC_AUTH_TOKEN`。 -## 参考带宽 +## Cloudflare 参考传输 -服务表测量的是首字节延迟和抖动,并不是传输测速。独立的带宽模块会: +服务表测量的是首字节延迟和抖动,并不是传输测速。独立的参考传输模块会: -- 从 Cloudflare 下载最多 2 MB; -- 上传最多 1 MB 全零数据; +- 从 Cloudflare 下载一个最多 2 MB 的限量样本; +- 上传一个最多 1 MB 的全零限量样本; - 使用报告中显示的代理/网络路径; - 将未完成传输明确标记为估算; -- 不允许高带宽掩盖异常的服务路径。 +- 样本较高时不增加开发适配分。 + +`https://speed.cloudflare.com/__down` 是 +[Cloudflare 官方测速引擎](https://github.com/cloudflare/speedtest)使用的下载 +API。官方引擎会组合多个尺寸并重复传输;ipcheck 的单个限量样本并未复刻这套 +完整测速方法。 + +这个样本只回答一个范围有限的问题:当前时刻,通过当前代理/网络路径向 +Cloudflare 传输小文件时表现如何。它不是: + +- 运营商或峰值宽带测速; +- OpenAI、Anthropic、GitHub 或 npm 的吞吐测量; +- AI 服务专用可达性和 TTFB 探测的替代品。 + +完整的偏低样本每个方向扣 2 分;较高或一般的样本不加分,不可用或跳过不影响 +分数,未完成样本每个方向扣 1 分。这样可以让参考信号始终从属于真实 AI +服务路径。 使用 `--no-upload` 只跳过上传,使用 `--no-bandwidth` 跳过上下行。可选的 `--system` 会运行 macOS `networkQuality`,消耗的数据量可能明显高于 ipcheck diff --git a/docs/reporting.md b/docs/reporting.md index cc4f19c..1ef3a44 100644 --- a/docs/reporting.md +++ b/docs/reporting.md @@ -38,9 +38,13 @@ automation. Output includes: - per-service and per-endpoint measurements; - score method and component breakdown; - warnings and redacted network path; -- download/upload and optional macOS system measurements; +- capped Cloudflare reference transfers and optional macOS system measurements; - explicit privacy guarantees. +The `bandwidth` object name is retained for JSON compatibility. Its +`scope`, `method`, `represents_api_path`, and `represents_peak_bandwidth` +fields define the narrower Cloudflare reference-transfer meaning. + Additive fields may appear without changing `schema_version`; breaking field changes increment it. Progress is disabled for JSON and Markdown modes. diff --git a/docs/reporting.zh-CN.md b/docs/reporting.zh-CN.md index f250d09..34ac4fc 100644 --- a/docs/reporting.zh-CN.md +++ b/docs/reporting.zh-CN.md @@ -35,9 +35,13 @@ ipcheck --json > ipcheck-report.json - 每个服务、每个端点的测量数据; - 评分方法和逐项分数; - 警告及脱敏后的网络路径; -- 上下行带宽和可选的 macOS 系统数据; +- 限量 Cloudflare 参考传输和可选的 macOS 系统数据; - 明确的隐私保证。 +JSON 为兼容已有使用方继续保留 `bandwidth` 对象名;其中的 `scope`、`method`、 +`represents_api_path` 和 `represents_peak_bandwidth` 字段明确限定其含义是 +Cloudflare 参考传输。 + 增加兼容字段不会修改 `schema_version`,破坏性变更才会升级。JSON 和 Markdown 模式不会输出动态进度。 diff --git a/docs/scoring.md b/docs/scoring.md index e625c1f..c9f8af4 100644 --- a/docs/scoring.md +++ b/docs/scoring.md @@ -6,7 +6,7 @@ - a measured service result: `GOOD`, `FAIR`, `POOR`, or `BLOCKED`, plus `SKIPPED` when a provider-specific route cannot be probed safely; -- a 0–100 development-readiness score calculated with `rule_v2`. +- a 0–100 development-readiness score calculated with `rule_v3`. The score is a transparent heuristic, not a user percentile or a benchmark of model intelligence. @@ -37,39 +37,42 @@ Skipped clients do not affect another measured client's score. If every selected client is skipped, the overall result is `UNAVAILABLE`, the score is 0, and the command exits with status 1. -## Rule v2 service-path points +## Rule v3 service-path points -The selected service path contributes up to 90 points. +The selected service path contributes up to 100 points. | Component | Maximum | Rule | | --- | ---: | --- | -| Reachability | 35 | 35 at 100% success, 22 at 60–99%, 10 above 0%, otherwise 0 | -| Median TTFB | 35 | 35 below 800 ms, 30 below 1,500, 22 below 3,000, 14 below 5,000, otherwise 6 | +| Reachability | 40 | 40 at 100% success, 25 at 60–99%, 11 above 0%, otherwise 0 | +| Median TTFB | 40 | 40 below 800 ms, 34 below 1,500, 25 below 3,000, 16 below 5,000, otherwise 7 | | P95 TTFB | 10 | 10 below 2,000 ms, 7 below 4,000, 4 below 6,000, otherwise 0 | | Jitter | 10 | 10 below 200 ms, 7 below 500, 4 below 1,000, otherwise 0 | Blocked paths receive zero latency and stability points. -## Bandwidth adjustments +## Cloudflare reference-transfer adjustments -Download and upload are scored independently. +The capped Cloudflare samples are scored independently, but never add points. +They measure the current path to Cloudflare, not peak bandwidth or AI API +throughput. | Rating | Points per direction | | --- | ---: | -| `FAST` | +5 | -| `ADEQUATE` | +3 | -| `SLOW` | -5 | +| `FAST` | 0 | +| `ADEQUATE` | 0 | +| `SLOW` | -2 | | Unavailable or skipped | 0 | -| Incomplete sample | an additional -2 | +| Incomplete sample | -1 instead of the speed rating | Download is `FAST` from 25 Mbps and `ADEQUATE` from 5 Mbps. Upload is `FAST` -from 10 Mbps and `ADEQUATE` from 2 Mbps. These thresholds represent common -development work, not general-purpose ISP quality. +from 10 Mbps and `ADEQUATE` from 2 Mbps. These thresholds only classify the +capped Cloudflare sample; they are not general-purpose ISP quality ratings. +Across both directions, reference transfers can reduce the score by at most +four points. ## Caps and labels -Service health limits the final score so bandwidth cannot conceal an unhealthy -AI route: +Service health still limits the final score: - usable `FAIR` paths are capped at 89; - temporarily unavailable `FAIR` and `POOR` paths are capped at 64; diff --git a/docs/scoring.zh-CN.md b/docs/scoring.zh-CN.md index e67e840..82254af 100644 --- a/docs/scoring.zh-CN.md +++ b/docs/scoring.zh-CN.md @@ -6,7 +6,7 @@ - 已测服务结论:`GOOD`、`FAIR`、`POOR` 或 `BLOCKED`;provider 专用路径无法 安全自动探测时为 `SKIPPED`; -- 使用 `rule_v2` 计算的 0–100 开发适配分。 +- 使用 `rule_v3` 计算的 0–100 开发适配分。 分数是透明的启发式规则,不代表用户百分位,也不评价模型智能水平。 @@ -31,37 +31,40 @@ TTFB 来自无凭据协议探测,包含 DNS、代理、TLS、网络与网关 已跳过的客户端不会拉低其他已测客户端的分数。所有选中客户端均被跳过时, 总体结论为 `UNAVAILABLE`、分数为 0,命令返回状态码 1。 -## 规则 v2 的服务链路分 +## 规则 v3 的服务链路分 -选中的最弱服务链路最多贡献 90 分。 +选中的最弱服务链路最多贡献 100 分。 | 项目 | 最高分 | 规则 | | --- | ---: | --- | -| 可达性 | 35 | 成功率 100% 得 35,60–99% 得 22,大于 0% 得 10,否则 0 | -| TTFB 中位数 | 35 | 低于 800 ms 得 35,低于 1,500 得 30,低于 3,000 得 22,低于 5,000 得 14,否则 6 | +| 可达性 | 40 | 成功率 100% 得 40,60–99% 得 25,大于 0% 得 11,否则 0 | +| TTFB 中位数 | 40 | 低于 800 ms 得 40,低于 1,500 得 34,低于 3,000 得 25,低于 5,000 得 16,否则 7 | | TTFB P95 | 10 | 低于 2,000 ms 得 10,低于 4,000 得 7,低于 6,000 得 4,否则 0 | | 抖动 | 10 | 低于 200 ms 得 10,低于 500 得 7,低于 1,000 得 4,否则 0 | 阻断链路不会获得延迟和稳定性分。 -## 带宽调整 +## Cloudflare 参考传输调整 -下载和上传分别计分。 +限量 Cloudflare 样本的上下行分别计分,但永远不会加分。它测量当前路径到 +Cloudflare 的表现,不代表峰值宽带或 AI API 吞吐。 | 评级 | 每个方向的分数 | | --- | ---: | -| 快 | +5 | -| 够用 | +3 | -| 慢 | -5 | +| 样本较高 | 0 | +| 样本一般 | 0 | +| 样本偏低 | -2 | | 不可用或跳过 | 0 | -| 未完成样本 | 额外 -2 | +| 未完成样本 | 不采用速率评级,改为 -1 | -下载达到 25 Mbps 为“快”,达到 5 Mbps 为“够用”;上传达到 10 Mbps 为“快”, -达到 2 Mbps 为“够用”。这些阈值面向日常开发,不是通用的运营商网络评级。 +下载达到 25 Mbps 为“样本较高”,达到 5 Mbps 为“样本一般”;上传达到 +10 Mbps 为“样本较高”, +达到 2 Mbps 为“样本一般”。这些阈值只用于描述限量 Cloudflare 样本,不是 +通用的运营商网络评级。两个方向合计最多让总分降低 4 分。 ## 结论限幅与标签 -服务健康度会限制最终得分,避免带宽掩盖异常的 AI 服务路径: +服务健康度仍会限制最终得分: - 可用的 `FAIR` 链路最高 89 分; - 临时不可用的 `FAIR` 和 `POOR` 链路最高 64 分; diff --git a/package.json b/package.json index 37c904a..4f5e6b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@jacklv-coder/ipcheck", "private": true, - "version": "0.8.2", + "version": "0.9.0", "description": "Diagnose proxy, latency, and API routes used by Codex and Claude Code.", "license": "MIT", "author": "Jintao", diff --git a/test/smoke.sh b/test/smoke.sh index 3369063..8b9deba 100755 --- a/test/smoke.sh +++ b/test/smoke.sh @@ -230,7 +230,7 @@ run_ipcheck_direct() { bash -n "$PROJECT_DIR/bin/ipcheck" "$PROJECT_DIR/bin/ipcheck" --help | grep -q '^ipcheck - diagnose Codex and Claude Code' -[ "$("$PROJECT_DIR/bin/ipcheck" --version)" = "ipcheck 0.8.2" ] +[ "$("$PROJECT_DIR/bin/ipcheck" --version)" = "ipcheck 0.9.0" ] "$PROJECT_DIR/bin/ipcheck" --help | grep -q -- '--explain-score' : > "$CURL_LOG" @@ -302,8 +302,8 @@ printf '%s\n' "$report" | grep -q 'https://dashscope.aliyuncs.com/apps/anthropic printf '%s\n' "$report" | grep -q '"credentials_used":false' printf '%s\n' "$report" | grep -q '"billable_requests":false' printf '%s\n' "$report" | grep -q '"developer_readiness":{"ready":true,"level":"ready"' -printf '%s\n' "$report" | grep -q '"score":90,"score_label":"COMFORTABLE","score_method":"rule_v2"' -printf '%s\n' "$report" | grep -q '"score_breakdown":{"scored_service":"codex","service_path":90,"availability":35,"median_ttfb":35,"p95":10,"jitter":10' +printf '%s\n' "$report" | grep -q '"score":100,"score_label":"COMFORTABLE","score_method":"rule_v3"' +printf '%s\n' "$report" | grep -q '"score_breakdown":{"scored_service":"codex","service_path":100,"availability":40,"median_ttfb":40,"p95":10,"jitter":10' if printf '%s\n' "$report" | grep -Eq 'fixture-secret|runtime-secret'; then printf 'Claude credential leaked into JSON report\n' >&2 exit 1 @@ -339,30 +339,30 @@ fi mixed_client_quality=$(IPCHECK_TEST_ANTHROPIC_TTFB=4.000 run_ipcheck --samples 1 --no-bandwidth --json) printf '%s\n' "$mixed_client_quality" | grep -q '"result":"poor"' -printf '%s\n' "$mixed_client_quality" | grep -q '"score":63,"score_label":"LIMITED","score_method":"rule_v2"' -printf '%s\n' "$mixed_client_quality" | grep -q '"scored_service":"claude","service_path":63' +printf '%s\n' "$mixed_client_quality" | grep -q '"score":64,"score_label":"LIMITED","score_method":"rule_v3"' +printf '%s\n' "$mixed_client_quality" | grep -q '"scored_service":"claude","service_path":70' for anthropic_status in 429 503; do mixed_service_error=$(IPCHECK_TEST_ANTHROPIC_CODE="$anthropic_status" run_ipcheck --samples 1 --no-bandwidth --json) printf '%s\n' "$mixed_service_error" | grep -q '"result":"fair"' - printf '%s\n' "$mixed_service_error" | grep -q '"score":64,"score_label":"LIMITED","score_method":"rule_v2"' - printf '%s\n' "$mixed_service_error" | grep -q '"scored_service":"claude","service_path":90' - printf '%s\n' "$mixed_service_error" | grep -q '"pre_cap_total":90,"verdict_cap":64' + printf '%s\n' "$mixed_service_error" | grep -q '"score":64,"score_label":"LIMITED","score_method":"rule_v3"' + printf '%s\n' "$mixed_service_error" | grep -q '"scored_service":"claude","service_path":100' + printf '%s\n' "$mixed_service_error" | grep -q '"pre_cap_total":100,"verdict_cap":64' done for anthropic_status in 429 503; do mixed_fair_unavailable=$(IPCHECK_TEST_TTFB=1.000 IPCHECK_TEST_ANTHROPIC_CODE="$anthropic_status" run_ipcheck --samples 1 --no-bandwidth --json) printf '%s\n' "$mixed_fair_unavailable" | grep -q '"result":"fair"' - printf '%s\n' "$mixed_fair_unavailable" | grep -q '"score":64,"score_label":"LIMITED","score_method":"rule_v2"' - printf '%s\n' "$mixed_fair_unavailable" | grep -q '"scored_service":"claude","service_path":85' - printf '%s\n' "$mixed_fair_unavailable" | grep -q '"pre_cap_total":85,"verdict_cap":64' + printf '%s\n' "$mixed_fair_unavailable" | grep -q '"score":64,"score_label":"LIMITED","score_method":"rule_v3"' + printf '%s\n' "$mixed_fair_unavailable" | grep -q '"scored_service":"claude","service_path":94' + printf '%s\n' "$mixed_fair_unavailable" | grep -q '"pre_cap_total":94,"verdict_cap":64' done mixed_blocked=$(IPCHECK_TEST_ANTHROPIC_BLOCKED=1 run_ipcheck --samples 1 --json) printf '%s\n' "$mixed_blocked" | grep -q '"result":"poor"' -printf '%s\n' "$mixed_blocked" | grep -q '"score":0,"score_label":"LIMITED","score_method":"rule_v2"' +printf '%s\n' "$mixed_blocked" | grep -q '"score":0,"score_label":"LIMITED","score_method":"rule_v3"' printf '%s\n' "$mixed_blocked" | grep -q '"scored_service":"claude","service_path":0' -printf '%s\n' "$mixed_blocked" | grep -q '"pre_cap_total":10,"verdict_cap":0' +printf '%s\n' "$mixed_blocked" | grep -q '"pre_cap_total":0,"verdict_cap":0' AUTO_HOME="$STUB_DIR/auto-home" mkdir -p "$AUTO_HOME/codex" "$AUTO_HOME/claude" @@ -577,14 +577,14 @@ printf '%s\n' "$decimal_report" | grep -q '"successful_samples":8' fair_report=$(IPCHECK_TEST_TTFB=1.000 run_ipcheck --samples 1 --no-bandwidth --endpoint https://fair.invalid --json) printf '%s\n' "$fair_report" | grep -q '"result":"fair"' printf '%s\n' "$fair_report" | grep -q '"level":"with_caution"' -printf '%s\n' "$fair_report" | grep -q '"score":85,"score_label":"GOOD"' +printf '%s\n' "$fair_report" | grep -q '"score":89,"score_label":"GOOD"' fair_fast_bandwidth_report=$(IPCHECK_TEST_TTFB=1.000 run_ipcheck --samples 1 --endpoint https://fair.invalid --json) printf '%s\n' "$fair_fast_bandwidth_report" | grep -q '"score":89,"score_label":"GOOD"' fair_slow_bandwidth_report=$(IPCHECK_TEST_TTFB=1.000 IPCHECK_TEST_BANDWIDTH_SPEED=100000 IPCHECK_TEST_UPLOAD_SPEED=100000 run_ipcheck --samples 1 --endpoint https://fair.invalid --json) -printf '%s\n' "$fair_slow_bandwidth_report" | grep -q '"score":75,"score_label":"GOOD"' +printf '%s\n' "$fair_slow_bandwidth_report" | grep -q '"score":89,"score_label":"GOOD"' just_below_poor=$(IPCHECK_TEST_TTFB=2.999 run_ipcheck --samples 1 --no-bandwidth --endpoint https://boundary.invalid --json) -printf '%s\n' "$just_below_poor" | grep -q '"score":74,"score_label":"USABLE"' +printf '%s\n' "$just_below_poor" | grep -q '"score":82,"score_label":"GOOD"' at_poor_boundary=$(IPCHECK_TEST_TTFB=3.000 run_ipcheck --samples 1 --no-bandwidth --endpoint https://boundary.invalid --json) printf '%s\n' "$at_poor_boundary" | grep -q '"score":64,"score_label":"LIMITED"' @@ -592,8 +592,8 @@ SCORE_SCENARIO_FILE="$STUB_DIR/score-scenario-attempt" : > "$SCORE_SCENARIO_FILE" score_scenario=$(IPCHECK_TEST_SCORE_FILE="$SCORE_SCENARIO_FILE" IPCHECK_TEST_BANDWIDTH_SPEED=400000 IPCHECK_TEST_UPLOAD_SPEED=425000 run_ipcheck --samples 3 --endpoint https://score-scenario.invalid --json) printf '%s\n' "$score_scenario" | grep -q '"result":"poor"' -printf '%s\n' "$score_scenario" | grep -q '"score":47,"score_label":"LIMITED","score_method":"rule_v2"' -printf '%s\n' "$score_scenario" | grep -q '"service_path":49,"availability":35,"median_ttfb":14,"p95":0,"jitter":0,"download":-5' +printf '%s\n' "$score_scenario" | grep -q '"score":54,"score_label":"LIMITED","score_method":"rule_v3"' +printf '%s\n' "$score_scenario" | grep -q '"service_path":56,"availability":40,"median_ttfb":16,"p95":0,"jitter":0,"download":-2' fair_human=$(IPCHECK_TEST_TTFB=1.000 IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --no-progress --endpoint https://fair.invalid) printf '%s\n' "$fair_human" | grep -q 'Ready to code? YES, WITH CAUTION' @@ -648,7 +648,7 @@ printf '%s\n' "$plain_human" | grep -q '█' narrow_human=$(COLUMNS=70 IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --no-color --endpoint https://narrow.invalid) printf '%s\n' "$narrow_human" | grep -q '^ HTTP 401 · 1/1 · median' -printf '%s\n' "$narrow_human" | grep -q '█████████████░' +printf '%s\n' "$narrow_human" | grep -q '██████████████' wide_human=$(COLUMNS=120 IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --no-color --endpoint https://wide.invalid) printf '%s\n' "$wide_human" | grep -q '✓ REACH.*HTTP 401.*median' @@ -816,34 +816,35 @@ printf '%s\n' "$socks_report" | grep -q 'Claude Code does not document ALL_PROXY grep -q '^env:ALL_PROXY=$' "$CURL_LOG" bandwidth_report=$(run_ipcheck --samples 1 --json) -printf '%s\n' "$bandwidth_report" | grep -q '"score":100,"score_label":"COMFORTABLE","score_method":"rule_v2"' -printf '%s\n' "$bandwidth_report" | grep -q '"score_breakdown":{"scored_service":"codex","service_path":90,"availability":35,"median_ttfb":35,"p95":10,"jitter":10,"download":5,"download_speed":5,"download_partial_penalty":0,"upload":5' +printf '%s\n' "$bandwidth_report" | grep -q '"score":100,"score_label":"COMFORTABLE","score_method":"rule_v3"' +printf '%s\n' "$bandwidth_report" | grep -q '"score_breakdown":{"scored_service":"codex","service_path":100,"availability":40,"median_ttfb":40,"p95":10,"jitter":10,"download":0,"download_speed":0,"download_partial_penalty":0,"upload":0' printf '%s\n' "$bandwidth_report" | grep -q '"bandwidth":{"enabled":true,"available":true,"http_code":"200"' +printf '%s\n' "$bandwidth_report" | grep -q '"scope":"cloudflare_reference_path","method":"single_capped_transfer","represents_api_path":false,"represents_peak_bandwidth":false' printf '%s\n' "$bandwidth_report" | grep -q '"download":{"enabled":true,"available":true,"complete":true,"http_code":"200","bytes":2000000,"bytes_per_second":10000000,"mbps":80.0,"rating":"fast"}' printf '%s\n' "$bandwidth_report" | grep -q '"upload":{"enabled":true,"available":true,"complete":true,"http_code":"200","bytes":1000000,"bytes_per_second":2000000,"mbps":16.0,"rating":"fast"}' printf '%s\n' "$bandwidth_report" | grep -q '"upload_payload":"zero-filled"' bandwidth_human=$(IPCHECK_LANG=zh run_ipcheck --samples 1) -printf '%s\n' "$bandwidth_human" | grep -q '◆ 网络带宽' +printf '%s\n' "$bandwidth_human" | grep -q '◆ 代理链路参考传输' printf '%s\n' "$bandwidth_human" | grep -q '100/100.*舒适' -printf '%s\n' "$bandwidth_human" | grep -q '下载 80.0 Mbps.*快.*Cloudflare,最多 2 MB' -printf '%s\n' "$bandwidth_human" | grep -q '上传 16.0 Mbps.*快.*Cloudflare,最多 1 MB 零字节' -printf '%s\n' "$bandwidth_human" | grep -q '路径 HTTPS_PROXY=http://127.0.0.1:1080' +printf '%s\n' "$bandwidth_human" | grep -q '下行样本 80.0 Mbps.*样本较高.*Cloudflare,最多 2 MB' +printf '%s\n' "$bandwidth_human" | grep -q '上行样本 16.0 Mbps.*样本较高.*Cloudflare,最多 1 MB 零字节' +printf '%s\n' "$bandwidth_human" | grep -q '范围.*当前代理 → Cloudflare;小文件样本,非峰值宽带,不代表 AI API 吞吐' score_explanation=$(IPCHECK_LANG=en run_ipcheck --samples 1 --explain-score) printf '%s\n' "$score_explanation" | grep -q '^ Score breakdown$' -printf '%s\n' "$score_explanation" | grep -q 'Service path.*90/90.*reachability 35.*TTFB 35.*P95 10.*jitter 10' -printf '%s\n' "$score_explanation" | grep -q 'Download.*+5.*80.0 Mbps.*FAST' -printf '%s\n' "$score_explanation" | grep -q 'Upload.*+5.*16.0 Mbps.*FAST' +printf '%s\n' "$score_explanation" | grep -q 'Service path.*100/100.*reachability 40.*TTFB 40.*P95 10.*jitter 10' +printf '%s\n' "$score_explanation" | grep -q 'Download reference.*0.*80.0 Mbps.*HIGHER SAMPLE' +printf '%s\n' "$score_explanation" | grep -q 'Upload reference.*0.*16.0 Mbps.*HIGHER SAMPLE' score_explanation_zh=$(IPCHECK_LANG=zh run_ipcheck --samples 1 --markdown --explain-score) -printf '%s\n' "$score_explanation_zh" | grep -q '评分依据:服务链路 90/90(可达性 35 + TTFB 35 + P95 10 + 抖动 10);下载 5(快);上传 5(快);限幅前 100;结论上限 100;总分 100。' +printf '%s\n' "$score_explanation_zh" | grep -q '评分依据:服务链路 100/100(可达性 40 + TTFB 40 + P95 10 + 抖动 10);下行参考样本 0(样本较高);上行参考样本 0(样本较高);限幅前 100;结论上限 100;总分 100。' score_explanation_without_bandwidth=$(IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --explain-score) -printf '%s\n' "$score_explanation_without_bandwidth" | grep -q 'Download.*0.*0.0 Mbps.*UNAVAILABLE' -printf '%s\n' "$score_explanation_without_bandwidth" | grep -q 'Upload.*0.*0.0 Mbps.*UNAVAILABLE' +printf '%s\n' "$score_explanation_without_bandwidth" | grep -q 'Download reference.*0.*0.0 Mbps.*UNAVAILABLE' +printf '%s\n' "$score_explanation_without_bandwidth" | grep -q 'Upload reference.*0.*0.0 Mbps.*UNAVAILABLE' slow_upload_human=$(IPCHECK_TEST_UPLOAD_SPEED=100000 IPCHECK_LANG=en run_ipcheck --samples 1 --explain-score) -printf '%s\n' "$slow_upload_human" | grep -q 'Upload.*0.8 Mbps.*SLOW' -printf '%s\n' "$slow_upload_human" | grep -q '90/100.*COMFORTABLE' -printf '%s\n' "$slow_upload_human" | grep -q 'Upload.*-5.*0.8 Mbps.*SLOW' -printf '%s\n' "$slow_upload_human" | grep -q 'Upload is slow; sending large code contexts may take longer.' +printf '%s\n' "$slow_upload_human" | grep -q 'Up sample.*0.8 Mbps.*LOW SAMPLE' +printf '%s\n' "$slow_upload_human" | grep -q '98/100.*COMFORTABLE' +printf '%s\n' "$slow_upload_human" | grep -q 'Upload reference.*-2.*0.8 Mbps.*LOW SAMPLE' +printf '%s\n' "$slow_upload_human" | grep -q 'Cloudflare upload sample is low' invalid_bandwidth_report=$(IPCHECK_TEST_BANDWIDTH_CODE=407 run_ipcheck --samples 1 --json) printf '%s\n' "$invalid_bandwidth_report" | grep -q '"bandwidth":{"enabled":true,"available":false,"http_code":"407","bytes":0,"bytes_per_second":0,' invalid_upload_report=$(IPCHECK_TEST_UPLOAD_CODE=503 run_ipcheck --samples 1 --json) @@ -854,24 +855,26 @@ printf '%s\n' "$no_upload_report" | grep -q '"upload":{"enabled":false,"availabl partial_upload_report=$(IPCHECK_TEST_UPLOAD_BYTES=500000 IPCHECK_TEST_UPLOAD_EXIT=28 run_ipcheck --samples 1 --json) printf '%s\n' "$partial_upload_report" | grep -q '"upload":{"enabled":true,"available":true,"complete":false,"http_code":"200","bytes":500000,"bytes_per_second":2000000,"mbps":16.0,"rating":"partial"}' -printf '%s\n' "$partial_upload_report" | grep -q '"score":98,"score_label":"COMFORTABLE"' +printf '%s\n' "$partial_upload_report" | grep -q '"score":99,"score_label":"COMFORTABLE"' partial_download_report=$(IPCHECK_TEST_BANDWIDTH_BYTES=500000 IPCHECK_TEST_BANDWIDTH_EXIT=28 run_ipcheck --samples 1 --json) printf '%s\n' "$partial_download_report" | grep -q '"bandwidth":{"enabled":true,"available":false,"http_code":"000","bytes":0,"bytes_per_second":0,' printf '%s\n' "$partial_download_report" | grep -q '"download":{"enabled":true,"available":true,"complete":false,"http_code":"200","bytes":500000,"bytes_per_second":10000000,"mbps":80.0,"rating":"partial"}' complete_slow_download=$(IPCHECK_TEST_BANDWIDTH_SPEED=12500 run_ipcheck --samples 1 --json) -printf '%s\n' "$complete_slow_download" | grep -q '"score":90,"score_label":"COMFORTABLE"' +printf '%s\n' "$complete_slow_download" | grep -q '"score":98,"score_label":"COMFORTABLE"' +rounded_threshold_report=$(IPCHECK_TEST_UPLOAD_SPEED=245000 run_ipcheck --samples 1 --json) +printf '%s\n' "$rounded_threshold_report" | grep -q '"mbps":2.0,"rating":"adequate"' partial_slow_download=$(IPCHECK_TEST_BANDWIDTH_SPEED=12500 IPCHECK_TEST_BANDWIDTH_BYTES=500000 IPCHECK_TEST_BANDWIDTH_EXIT=28 run_ipcheck --samples 1 --json) -printf '%s\n' "$partial_slow_download" | grep -q '"score":88,"score_label":"GOOD"' -printf '%s\n' "$partial_slow_download" | grep -q '"download":-7,"download_speed":-5,"download_partial_penalty":-2' +printf '%s\n' "$partial_slow_download" | grep -q '"score":99,"score_label":"COMFORTABLE"' +printf '%s\n' "$partial_slow_download" | grep -q '"download":-1,"download_speed":0,"download_partial_penalty":-1' partial_human=$(IPCHECK_TEST_UPLOAD_BYTES=500000 IPCHECK_TEST_UPLOAD_EXIT=28 IPCHECK_LANG=en run_ipcheck --samples 1) -printf '%s\n' "$partial_human" | grep -q 'Upload.*16.0 Mbps.*ESTIMATE' -printf '%s\n' "$partial_human" | grep -q 'partial estimates and do not prove bandwidth is sufficient' +printf '%s\n' "$partial_human" | grep -q 'Up sample.*16.0 Mbps.*INCOMPLETE ESTIMATE' +printf '%s\n' "$partial_human" | grep -q 'partial estimates and do not prove that path throughput is sufficient' partial_markdown=$(IPCHECK_TEST_BANDWIDTH_BYTES=500000 IPCHECK_TEST_BANDWIDTH_EXIT=28 IPCHECK_LANG=en run_ipcheck --samples 1 --markdown) -printf '%s\n' "$partial_markdown" | grep -q '| Download | 80.0 Mbps | ESTIMATE |' -printf '%s\n' "$partial_markdown" | grep -q 'partial estimates and do not prove bandwidth is sufficient' +printf '%s\n' "$partial_markdown" | grep -q '| Down | 80.0 Mbps | INCOMPLETE ESTIMATE |' +printf '%s\n' "$partial_markdown" | grep -q 'partial estimates and do not prove that path throughput is sufficient' progress_report=$(IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --progress 2>&1 >/dev/null) printf '%s\n' "$progress_report" | grep -q 'Press Ctrl+C to cancel at any time.' @@ -952,7 +955,7 @@ set -e [ "$blocked_exit" -eq 1 ] printf '%s\n' "$blocked_report" | grep -q '"http_code":"000"' printf '%s\n' "$blocked_report" | grep -q '"bandwidth":{"enabled":false,"available":false,"http_code":"000"' -printf '%s\n' "$blocked_report" | grep -q '"score":0,"score_label":"LIMITED","score_method":"rule_v2"' +printf '%s\n' "$blocked_report" | grep -q '"score":0,"score_label":"LIMITED","score_method":"rule_v3"' markdown=$(run_ipcheck --samples 1 --no-bandwidth --markdown) printf '%s\n' "$markdown" | grep -q '^# ipcheck: AI Coding Network Report'