Add an interactive utility so users create/edit/validate canonical runtime
profiles and generate safe LLM launcher snippets without hand-editing JSON or
pasting tokens into Claude/Gemini/Codex configs.
Run: `python gitea_config.py menu` (or `python gitea_config_menu.py`).
gitea_config.py — pure, testable authoring helpers:
- is_valid_profile_name, build_profile, keychain_auth/env_auth, empty_config
- validate_config (reports missing base_url/auth, inline token/password — never
echoing the secret value)
- add_profile (preserves existing, rejects dup/invalid name/missing base_url),
upsert_profile, remove_profile
- save_config: mkdir parents + atomic temp-then-os.replace, pretty JSON
- launcher_entry: thin MCP entry (command/args + GITEA_MCP_CONFIG/PROFILE only)
- keychain_set: store a token via `security add-generic-password` (token passed
as an arg, never returned/printed/logged; injectable runner)
- `menu` __main__ dispatch
gitea_config_menu.py — interactive loop with fully injectable IO/secret/HTTP/
keychain so it is testable without a real terminal, keychain, or network:
- list / add / edit / remove / validate profiles
- test authentication + show authenticated user (calls /user only on request)
- reviewer-eligibility helper (authenticated user vs PR author, open state) —
read-only, never approves/merges
- launcher snippets for Claude / Gemini / Codex (no secrets)
Security: tokens are never written to profiles.json, launcher snippets, logs,
or errors — only keychain ids / env var names are stored. Backwards compatible:
menu is optional; env-only mode and MCP server startup are unchanged.
Tests: tests/test_config_menu.py (21 cases) — name validation, preserve-on-add,
dup/invalid/missing-field rejection, atomic write (+ replace-failure leaves the
original intact, no temp debris), keychain_set stores-without-printing, launcher
snippets secret-free, eligibility eligible/self-author/closed, and a full menu
add→list→quit flow proving the token value never reaches disk or stdout.
Stacked on #30 (canonical profiles); base branch feat/json-runtime-profiles.
Refs #10, #19. Closes#31.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>