|
9 | 9 | import subprocess |
10 | 10 | import shutil |
11 | 11 | from pathlib import Path |
| 12 | +from typing import Any, Mapping |
12 | 13 |
|
13 | 14 | from gauss_cli.branding import get_cli_command_name, get_product_name, rewrite_cli_references |
14 | 15 | from gauss_cli.config import get_project_root, get_gauss_home, get_env_path |
@@ -106,6 +107,189 @@ def _check_gateway_service_linger(issues: list[str]) -> None: |
106 | 107 | check_warn("Could not verify systemd linger", f"({linger_detail})") |
107 | 108 |
|
108 | 109 |
|
| 110 | +def _check_managed_workflow_requirements( |
| 111 | + issues: list[str], |
| 112 | + *, |
| 113 | + config: Mapping[str, Any] | None = None, |
| 114 | + env: Mapping[str, str] | None = None, |
| 115 | + active_cwd: str | Path | None = None, |
| 116 | + cli_name: str | None = None, |
| 117 | +) -> None: |
| 118 | + """Report Lean workflow prerequisites that are specific to Open Gauss.""" |
| 119 | + try: |
| 120 | + from gauss_cli.autoformalize import ( |
| 121 | + CODEX_AUTOFORMALIZE_BACKEND, |
| 122 | + DEFAULT_AUTOFORMALIZE_BACKEND, |
| 123 | + _codex_auth_payload_has_api_key, |
| 124 | + _codex_auth_payload_is_valid, |
| 125 | + _has_local_claude_api_key, |
| 126 | + _has_local_claude_login, |
| 127 | + _load_local_codex_auth_payload, |
| 128 | + _resolve_auth_mode, |
| 129 | + _resolve_backend_name, |
| 130 | + _resolve_claude_auth_env, |
| 131 | + _resolve_codex_api_key, |
| 132 | + _resolve_uv_runner, |
| 133 | + ) |
| 134 | + from gauss_cli.project import ( |
| 135 | + ProjectManifestError, |
| 136 | + ProjectNotFoundError, |
| 137 | + discover_gauss_project, |
| 138 | + resolve_template_source, |
| 139 | + ) |
| 140 | + except Exception as exc: |
| 141 | + print() |
| 142 | + print(color("◆ Managed Lean Workflows", Colors.CYAN, Colors.BOLD)) |
| 143 | + check_warn("Managed workflow diagnostics unavailable", f"({exc})") |
| 144 | + return |
| 145 | + |
| 146 | + from gauss_cli.config import load_config |
| 147 | + |
| 148 | + cli_name = cli_name or get_cli_command_name() |
| 149 | + environment = dict(env or os.environ) |
| 150 | + resolved_config = config if isinstance(config, Mapping) else load_config() |
| 151 | + |
| 152 | + print() |
| 153 | + print(color("◆ Managed Lean Workflows", Colors.CYAN, Colors.BOLD)) |
| 154 | + |
| 155 | + try: |
| 156 | + backend_name = _resolve_backend_name(resolved_config, environment) |
| 157 | + auth_mode = _resolve_auth_mode(resolved_config, environment) |
| 158 | + except Exception as exc: |
| 159 | + check_fail("Managed workflow config", f"({exc})") |
| 160 | + issues.append(f"Fix gauss.autoformalize config: {exc}") |
| 161 | + return |
| 162 | + |
| 163 | + check_ok("Managed backend", f"({backend_name})") |
| 164 | + check_ok("Managed auth mode", f"({auth_mode})") |
| 165 | + |
| 166 | + template_source = resolve_template_source(resolved_config, environment) |
| 167 | + if template_source: |
| 168 | + check_ok("Project template source", f"({template_source})") |
| 169 | + else: |
| 170 | + check_warn( |
| 171 | + "Project template source", |
| 172 | + "(`/project create` needs `--template-source` or `gauss.project.template_source`)", |
| 173 | + ) |
| 174 | + |
| 175 | + try: |
| 176 | + uv_runner = _resolve_uv_runner(environment) |
| 177 | + check_ok("uv / uvx", f"({Path(uv_runner[0]).name})") |
| 178 | + except Exception as exc: |
| 179 | + check_fail("uv / uvx", f"({exc})") |
| 180 | + issues.append("Install uv so Gauss can launch the managed Lean MCP server") |
| 181 | + |
| 182 | + lake_executable = shutil.which("lake", path=environment.get("PATH")) |
| 183 | + if lake_executable: |
| 184 | + check_ok("Lean toolchain (lake)", f"({lake_executable})") |
| 185 | + else: |
| 186 | + check_fail("Lean toolchain (lake)", "(required for managed Lean workflows)") |
| 187 | + issues.append("Install elan / Lean so `lake` is available on PATH") |
| 188 | + |
| 189 | + active_dir = Path(active_cwd or environment.get("TERMINAL_CWD") or os.getcwd()).expanduser().resolve() |
| 190 | + if active_dir.exists(): |
| 191 | + check_ok("Active working directory", f"({active_dir})") |
| 192 | + try: |
| 193 | + project = discover_gauss_project(active_dir) |
| 194 | + check_ok("Active Gauss project", f"({project.label})") |
| 195 | + except ProjectNotFoundError: |
| 196 | + check_warn("Active Gauss project", f"(none under {active_dir})") |
| 197 | + issues.append( |
| 198 | + "Create or select a Gauss project with `/project init`, `/project convert`, or `/project use <path>`" |
| 199 | + ) |
| 200 | + except ProjectManifestError as exc: |
| 201 | + check_fail("Gauss project manifest", f"({exc})") |
| 202 | + issues.append(f"Repair the active Gauss project manifest: {exc}") |
| 203 | + else: |
| 204 | + check_fail("Active working directory", f"({active_dir} does not exist)") |
| 205 | + issues.append(f"Fix the managed workflow working directory: {active_dir}") |
| 206 | + |
| 207 | + real_home = Path(environment.get("HOME", str(Path.home()))).expanduser().resolve() |
| 208 | + |
| 209 | + if backend_name == DEFAULT_AUTOFORMALIZE_BACKEND: |
| 210 | + claude_executable = shutil.which("claude", path=environment.get("PATH")) |
| 211 | + if claude_executable: |
| 212 | + check_ok("Claude Code CLI", f"({claude_executable})") |
| 213 | + else: |
| 214 | + check_fail("Claude Code CLI", "(install with `npm install -g @anthropic-ai/claude-code`)") |
| 215 | + issues.append("Install the Claude Code CLI for managed Lean workflows") |
| 216 | + |
| 217 | + has_local_login = _has_local_claude_login(real_home) |
| 218 | + has_local_api_key = _has_local_claude_api_key(real_home) |
| 219 | + staged_auth_env = _resolve_claude_auth_env(environment, include_persisted_env=True) |
| 220 | + staged_keys = ", ".join(sorted(staged_auth_env)) |
| 221 | + |
| 222 | + if auth_mode == "auto": |
| 223 | + if has_local_login: |
| 224 | + check_ok("Claude auth", "(local Claude login)") |
| 225 | + elif has_local_api_key: |
| 226 | + check_ok("Claude auth", "(local Claude API key)") |
| 227 | + elif staged_auth_env: |
| 228 | + check_ok("Claude auth", f"(staged via {staged_keys})") |
| 229 | + else: |
| 230 | + check_fail("Claude auth", "(not found)") |
| 231 | + issues.append( |
| 232 | + "Run `claude auth login`, save `ANTHROPIC_API_KEY`, or set `gauss.autoformalize.auth_mode: login`" |
| 233 | + ) |
| 234 | + elif auth_mode == "login": |
| 235 | + if has_local_login: |
| 236 | + check_ok("Claude auth", "(local Claude login)") |
| 237 | + elif has_local_api_key: |
| 238 | + check_warn("Claude auth", "(login mode ignores local Claude API keys; first launch will prompt)") |
| 239 | + else: |
| 240 | + check_warn("Claude auth", "(login mode will prompt on first launch)") |
| 241 | + else: |
| 242 | + if staged_auth_env: |
| 243 | + check_ok("Claude API-key auth", f"(staged via {staged_keys})") |
| 244 | + elif has_local_api_key: |
| 245 | + check_ok("Claude API-key auth", "(local Claude API key)") |
| 246 | + else: |
| 247 | + check_fail("Claude API-key auth", "(not found)") |
| 248 | + issues.append( |
| 249 | + "Save `ANTHROPIC_API_KEY` / `ANTHROPIC_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN`, or switch `gauss.autoformalize.auth_mode` back to `auto` or `login`" |
| 250 | + ) |
| 251 | + return |
| 252 | + |
| 253 | + if backend_name == CODEX_AUTOFORMALIZE_BACKEND: |
| 254 | + codex_executable = shutil.which("codex", path=environment.get("PATH")) |
| 255 | + if codex_executable: |
| 256 | + check_ok("Codex CLI", f"({codex_executable})") |
| 257 | + else: |
| 258 | + check_fail("Codex CLI", "(install the OpenAI Codex CLI)") |
| 259 | + issues.append("Install the OpenAI Codex CLI for managed Lean workflows") |
| 260 | + |
| 261 | + local_auth_payload = _load_local_codex_auth_payload(real_home, environment) |
| 262 | + has_local_auth = _codex_auth_payload_is_valid(local_auth_payload) |
| 263 | + has_local_api_key = _codex_auth_payload_has_api_key(local_auth_payload) |
| 264 | + staged_api_key = _resolve_codex_api_key(environment, include_persisted_env=True) |
| 265 | + |
| 266 | + if auth_mode == "auto": |
| 267 | + if has_local_auth: |
| 268 | + check_ok("Codex auth", "(local Codex auth)") |
| 269 | + elif staged_api_key: |
| 270 | + check_ok("Codex auth", "(staged OPENAI_API_KEY)") |
| 271 | + else: |
| 272 | + check_fail("Codex auth", "(not found)") |
| 273 | + issues.append( |
| 274 | + "Run `codex login`, save `OPENAI_API_KEY`, or set `gauss.autoformalize.auth_mode: login`" |
| 275 | + ) |
| 276 | + elif auth_mode == "login": |
| 277 | + if has_local_auth: |
| 278 | + check_ok("Codex auth", "(local Codex auth)") |
| 279 | + else: |
| 280 | + check_warn("Codex auth", "(login mode will prompt on first launch)") |
| 281 | + else: |
| 282 | + if staged_api_key: |
| 283 | + check_ok("Codex API-key auth", "(staged OPENAI_API_KEY)") |
| 284 | + elif has_local_api_key: |
| 285 | + check_ok("Codex API-key auth", "(local Codex API key)") |
| 286 | + else: |
| 287 | + check_fail("Codex API-key auth", "(not found)") |
| 288 | + issues.append( |
| 289 | + "Save `OPENAI_API_KEY`, or switch `gauss.autoformalize.auth_mode` back to `auto` or `login`" |
| 290 | + ) |
| 291 | + |
| 292 | + |
109 | 293 | def run_doctor(args): |
110 | 294 | """Run diagnostic checks.""" |
111 | 295 | should_fix = getattr(args, 'fix', False) |
@@ -485,6 +669,8 @@ def run_doctor(args): |
485 | 669 | except Exception: |
486 | 670 | pass |
487 | 671 |
|
| 672 | + _check_managed_workflow_requirements(issues, cli_name=cli_name) |
| 673 | + |
488 | 674 | # ========================================================================= |
489 | 675 | # Check: API connectivity |
490 | 676 | # ========================================================================= |
|
0 commit comments