diff --git a/.gitignore b/.gitignore index 662c1ef..07e12c2 100755 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ __pycache__/ .entire/ .claude/ CLAUDE.md + +# qwen validation reports — auto-generated, machine-specific +qwen/reports/ diff --git a/README.md b/README.md index ec75700..2e1e70b 100755 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Patched AI coding tools for use with custom API endpoints. | Folder | Tool | Status | |--------|------|--------| | [claude/](claude/) | Claude Code | Active (v2.1.112) | -| [codex/](codex/) | OpenAI Codex CLI | **Active (v0.116.0)** | +| [codex/](codex/) | OpenAI Codex CLI | **Active (v0.122.0)** | | [gemini/](gemini/) | Gemini CLI | **Active (v0.35.3)** | | [qwen/](qwen/) | Qwen Code | **Active (v0.14.5)** | | antigravity/ | Antigravity | Planned | diff --git a/README_es.md b/README_es.md index cd2f07f..96d20a8 100755 --- a/README_es.md +++ b/README_es.md @@ -9,7 +9,7 @@ Herramientas de IA para codificación con soporte para puntos finales de API per | Carpeta | Herramienta | Estado | |-------|-----------|--------| | [claude/](claude/) | Claude Code | Activo (v2.1.112) | -| [codex/](codex/) | OpenAI Codex CLI | **Activo (v0.116.0)** | +| [codex/](codex/) | OpenAI Codex CLI | **Activo (v0.122.0)** | | [gemini/](gemini/) | Gemini CLI | **Activo (v0.35.3)** | | [qwen/](qwen/) | Qwen Code | **Activo (v0.14.5)** | | antigravity/ | Antigravity | Planeado | diff --git a/README_ru.md b/README_ru.md index 5afee9a..94a7ab8 100755 --- a/README_ru.md +++ b/README_ru.md @@ -9,7 +9,7 @@ | Папка | Инструмент | Статус | |-------|-----------|--------| | [claude/](claude/) | Claude Code | Активен (v2.1.112) | -| [codex/](codex/) | OpenAI Codex CLI | **Активен (v0.116.0)** | +| [codex/](codex/) | OpenAI Codex CLI | **Активен (v0.122.0)** | | [gemini/](gemini/) | Gemini CLI | **Активен (v0.35.3)** | | [qwen/](qwen/) | Qwen Code | **Активен (v0.14.5)** | | antigravity/ | Antigravity | Планируется | diff --git a/README_zh.md b/README_zh.md index e536109..c6bf1d2 100755 --- a/README_zh.md +++ b/README_zh.md @@ -9,7 +9,7 @@ | 文件夹 | 工具 | 状态 | |-------|-----------|--------| | [claude/](claude/) | Claude Code | 活跃 (v2.1.112) | -| [codex/](codex/) | OpenAI Codex CLI | **活跃 (v0.116.0)** | +| [codex/](codex/) | OpenAI Codex CLI | **活跃 (v0.122.0)** | | [gemini/](gemini/) | Gemini CLI | **活跃 (v0.35.3)** | | [qwen/](qwen/) | Qwen Code | **活跃 (v0.14.5)** | | antigravity/ | Antigravity | 计划中 | diff --git a/codex/README.md b/codex/README.md index 213572a..620d011 100755 --- a/codex/README.md +++ b/codex/README.md @@ -1,7 +1,7 @@ # Codex CLI — Patched Patched OpenAI Codex CLI for use with custom API endpoints. -Latest: **v0.116.0** (6 config patches). +Latest: **v0.122.0** (6 config patches). > Codex CLI — это compiled Rust binary. В отличие от Claude Code и Gemini CLI (JavaScript), > патчинг выполняется через `config.toml` + переменные окружения. @@ -76,7 +76,7 @@ powershell -ExecutionPolicy Bypass -File ucodex_install.ps1 ```bash # Скачать последнюю версию с GitHub sudo bash update-codex.sh -codex --version # Должно показать: codex-cli 0.116.0 +codex --version # Должно показать: codex-cli 0.122.0 ``` **Шаг 2 — Настроить конфиг:** diff --git a/qwen/qwen_patcher.py b/qwen/qwen_patcher.py index 0e768cb..56d2aee 100755 --- a/qwen/qwen_patcher.py +++ b/qwen/qwen_patcher.py @@ -158,11 +158,19 @@ def patch_cli_js(cli_js_path, config): content, n = re.subn(pat3, rep3, content) results["telemetry_init_guard"] = f"OK ({n})" if n > 0 else "SKIP" - # Target 4: DASHSCOPE_BASE_URL - pat4 = r'(DEFAULT_DASHSCOPE_BASE_URL\s*=\s*)"https://dashscope\.aliyuncs\.com/compatible-mode/v1"' - rep4 = rf'\1"{base_url}/v1"' - content, n = re.subn(pat4, rep4, content) - results["dashscope_base_url"] = f"OK ({n})" if n > 0 else "SKIP" + # Target 4: DASHSCOPE_BASE_URL — main + regional (HK/intl/us) endpoints + count4 = 0 + for old_url in [ + "https://dashscope.aliyuncs.com/compatible-mode/v1", + "https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1", + "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "https://dashscope-us.aliyuncs.com/compatible-mode/v1", + ]: + new_url = f"{base_url}/v1" + c = content.count(old_url) + content = content.replace(old_url, new_url) + count4 += c + results["dashscope_base_url"] = f"OK ({count4})" if count4 > 0 else "SKIP" # Target 5: CODING_PLAN_URLS (string replace, not regex) count5 = 0 @@ -403,15 +411,112 @@ def rollback(cli_js_path): # ─── Validation (standalone) ─────────────────────────────────────────── def run_validation(cli_js_path): - """Run pattern validation on detected installation.""" - from updater.pattern_validator import validate_all, print_validation_report, get_summary + """Lightweight validation — checks patch markers and key invariants in + cli.js + ~/.qwen/settings.json + trustedFolders.json.""" + counts = {"green": 0, "yellow": 0, "red": 0} + print() + print(f" {BOLD}Validating Qwen patches{RESET}") + print(f" {'─' * 56}") - user_settings = str(Path.home() / ".qwen" / "settings.json") - trusted_folders = str(Path.home() / ".qwen" / "trustedFolders.json") + def report(name, status, detail=""): + if status == "GREEN": + counts["green"] += 1 + color = GREEN + elif status == "YELLOW": + counts["yellow"] += 1 + color = YELLOW + else: + counts["red"] += 1 + color = RED + suffix = f" — {detail}" if detail else "" + print(f" {color}[{status}]{RESET} {name}{suffix}") - results = validate_all(cli_js_path, user_settings, trusted_folders) - counts = print_validation_report(results) - return counts, get_summary(results) + # cli.js checks + if cli_js_path and os.path.isfile(cli_js_path): + with open(cli_js_path, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + + report("cli.js patch_marker", + "GREEN" if PATCH_MARKER in content else "RED", + "" if PATCH_MARKER in content else "marker not found") + + report("telemetry_flag", + "GREEN" if "getTelemetryEnabled() { return false" in content else "YELLOW") + + report("telemetry_log_prompts", + "GREEN" if "getTelemetryLogPromptsEnabled() { return false" in content else "YELLOW") + + # dashscope_base_url: any /compatible-mode/v1 endpoint left = RED + leftover_ds = [u for u in [ + "dashscope.aliyuncs.com/compatible-mode", + "cn-hongkong.dashscope.aliyuncs.com/compatible-mode", + "dashscope-intl.aliyuncs.com/compatible-mode", + "dashscope-us.aliyuncs.com/compatible-mode", + ] if u in content] + report("dashscope_base_url", + "GREEN" if not leftover_ds else "RED", + f"unpatched: {', '.join(leftover_ds)}" if leftover_ds else "") + + leftover_cp = [u for u in [ + "coding.dashscope.aliyuncs.com", + "coding-intl.dashscope.aliyuncs.com", + ] if u in content] + report("coding_plan_urls", + "GREEN" if not leftover_cp else "RED", + f"unpatched: {', '.join(leftover_cp)}" if leftover_cp else "") + + report("default_model", + "GREEN" if 'DEFAULT_QWEN_MODEL = "coder-model"' in content else "YELLOW") + + report("mainline_model", + "GREEN" if 'MAINLINE_CODER_MODEL = "qwen3.5-plus"' in content else "YELLOW") + + report("auto_update_registry", + "GREEN" if '"https://registry.npmjs.org/"' not in content else "YELLOW") + else: + report("cli.js", "RED", f"not found: {cli_js_path}") + + # settings.json checks + settings_path = Path.home() / ".qwen" / "settings.json" + if settings_path.is_file(): + try: + with open(settings_path, "r", encoding="utf-8") as f: + settings = json.load(f) + sec = settings.get("security", {}) + auth = sec.get("auth", {}) + telemetry = settings.get("telemetry", {}) + report("user_settings.auth.selectedType", + "GREEN" if auth.get("selectedType") == "openai" else "YELLOW", + f"value={auth.get('selectedType')!r}") + report("user_settings.telemetry.enabled", + "GREEN" if telemetry.get("enabled") is False else "YELLOW") + except Exception as e: + report("user_settings", "RED", f"parse error: {e}") + else: + report("user_settings", "YELLOW", f"missing: {settings_path}") + + # trustedFolders.json checks + trusted_path = Path.home() / ".qwen" / "trustedFolders.json" + if trusted_path.is_file(): + report("trusted_folders", "GREEN", "present") + else: + report("trusted_folders", "YELLOW", "missing") + + # env vars + has_api = bool(os.environ.get("OPENAI_API_KEY")) + has_url = bool(os.environ.get("OPENAI_BASE_URL")) + report("env.OPENAI_API_KEY", + "GREEN" if has_api else "YELLOW", + "" if has_api else "not set in current shell") + report("env.OPENAI_BASE_URL", + "GREEN" if has_url else "YELLOW", + "" if has_url else "not set in current shell") + + print(f" {'─' * 56}") + print(f" {GREEN}GREEN: {counts['green']}{RESET} " + f"{YELLOW}YELLOW: {counts['yellow']}{RESET} " + f"{RED}RED: {counts['red']}{RESET}") + return counts, f"{counts['green']}G/{counts['yellow']}Y/{counts['red']}R" # ─── CLI ───────────────────────────────────────────────────────────────