From f484eaab3826f7db8b146a4489ce8f8d79b0130f Mon Sep 17 00:00:00 2001 From: minguezsanzjuanjose Date: Tue, 6 Oct 2026 14:44:40 +0200 Subject: [PATCH] 06-oct: [omniroute] re-auth provider Claude + helper OAuth y vigilante portapapeles --- CHANGELOG_INFRA.md | 9 ++++ changelog_infra.json | 12 +++++ omniroute/clipboard_watch.ps1 | 24 ++++++++++ omniroute/oauth_helper.py | 90 +++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 omniroute/clipboard_watch.ps1 create mode 100644 omniroute/oauth_helper.py diff --git a/CHANGELOG_INFRA.md b/CHANGELOG_INFRA.md index 30f0b96..14b5eb3 100644 --- a/CHANGELOG_INFRA.md +++ b/CHANGELOG_INFRA.md @@ -15,9 +15,18 @@ - **2026-10-04-protocolo-encendido-sobremesa-validado-wol-falla-ciclo-regle** [servicios] Protocolo encendido sobremesa validado: WoL falla, ciclo regleta funciona (Power On Loss) (aplicado en: portatil) - **2026-10-04-rdp-activado-en-sobremesa-por-tailscale-cuenta-sin-password** [servicios] RDP activado en sobremesa por Tailscale (cuenta sin password) (aplicado en: portatil) - **2026-10-04-fix-encender-sobremesa-py-cargar-env-home-assistant** [scripts] Fix encender_sobremesa.py: cargar .env (Home Assistant) (aplicado en: portatil) +- **2026-10-06-re-auth-provider-claude-omniroute-helper-oauth-vigilante-por** [omniroute] Re-auth provider Claude OmniRoute (helper OAuth + vigilante portapapeles) (aplicado en: portatil) ## Historial +### 2026-10-06 · Re-auth provider Claude OmniRoute (helper OAuth + vigilante portapapeles) +- **id**: `2026-10-06-re-auth-provider-claude-omniroute-helper-oauth-vigilante-por` +- **Equipo**: portatil +- **Categoria**: omniroute +- **Transferible**: si +- **Aplicado en**: portatil +- **Detalle**: Scripts reutilizables en INFRAESTRUCTURA/omniroute: oauth_helper.py (mantiene vivo el proceso OAuth y le inyecta code#state por stdin, sin timeout) y clipboard_watch.ps1 (lee el codigo del portapapeles en modo -STA). Uso: lanzar helper, abrir URL con --no-browser, autorizar, copiar codigo -> auto-inyectado. Necesario cuando caduca el token OAuth del provider claude. + ### 2026-10-04 · Protocolo de encendido remoto del sobremesa + PowerShell por SSH - **id**: `2026-10-04-protocolo-de-encendido-remoto-del-sobremesa-powershell-por-s` - **Equipo**: portatil diff --git a/changelog_infra.json b/changelog_infra.json index e3639db..f23f687 100644 --- a/changelog_infra.json +++ b/changelog_infra.json @@ -462,6 +462,18 @@ "portatil" ], "detalle": "encender_sobremesa.py no cargaba el .env -> ha_alexa sin HOMEASSISTANT_URL/TOKEN, la regleta no respondia. Anadido load_dotenv(biblioteca/.env). Protocolo validado end-to-end: apagado por SSH -> verificado offline -> encendido por ciclo de regleta (Power On Loss)." + }, + { + "id": "2026-10-06-re-auth-provider-claude-omniroute-helper-oauth-vigilante-por", + "fecha": "2026-10-06", + "equipo": "portatil", + "categoria": "omniroute", + "titulo": "Re-auth provider Claude OmniRoute (helper OAuth + vigilante portapapeles)", + "transferible": true, + "aplicado_en": [ + "portatil" + ], + "detalle": "Scripts reutilizables en INFRAESTRUCTURA/omniroute: oauth_helper.py (mantiene vivo el proceso OAuth y le inyecta code#state por stdin, sin timeout) y clipboard_watch.ps1 (lee el codigo del portapapeles en modo -STA). Uso: lanzar helper, abrir URL con --no-browser, autorizar, copiar codigo -> auto-inyectado. Necesario cuando caduca el token OAuth del provider claude." } ] } diff --git a/omniroute/clipboard_watch.ps1 b/omniroute/clipboard_watch.ps1 new file mode 100644 index 0000000..73605a7 --- /dev/null +++ b/omniroute/clipboard_watch.ps1 @@ -0,0 +1,24 @@ +Add-Type -AssemblyName System.Windows.Forms +$codeFile = "C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\omniroute\oauth_code.txt" +$exitFile = "C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\omniroute\oauth_helper.exit" +$last = "" +try { $last = [System.Windows.Forms.Clipboard]::GetText() } catch {} +$deadline = (Get-Date).AddMinutes(20) +while ((Get-Date) -lt $deadline) { + if (Test-Path $exitFile) { break } + $c = "" + try { $c = [System.Windows.Forms.Clipboard]::GetText() } catch {} + if ($c -and $c -ne $last -and $c.Length -ge 20 -and ($c -match "#" -or $c -match "code=")) { + # Limpiar espacios/saltos por si copia con formato + $clean = $c.Trim() + if ($clean -match "https?://") { + $m = [regex]::Match($clean, "code=([^&#\s]+)") + $s = [regex]::Match($clean, "state=([^&#\s]+)") + if ($m.Success -and $s.Success) { $clean = $m.Groups[1].Value + "#" + $s.Groups[1].Value } + } + Set-Content -Path $codeFile -Value $clean -NoNewline + break + } + $last = $c + Start-Sleep -Milliseconds 700 +} diff --git a/omniroute/oauth_helper.py b/omniroute/oauth_helper.py new file mode 100644 index 0000000..a2e4ce0 --- /dev/null +++ b/omniroute/oauth_helper.py @@ -0,0 +1,90 @@ +import subprocess, time, os, threading + +base = r"C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\omniroute" +url_file = os.path.join(base, "oauth_url.txt") +code_file = os.path.join(base, "oauth_code.txt") +log_file = os.path.join(base, "oauth_helper.log") +exit_file = os.path.join(base, "oauth_helper.exit") +hb_file = os.path.join(base, "oauth_helper.hb") + +for f in (url_file, code_file, log_file, exit_file, hb_file): + try: + os.remove(f) + except FileNotFoundError: + pass + +proc = subprocess.Popen( + ["omniroute.cmd", "oauth", "start", "--provider", "claude-code", "--no-browser"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 +) + +log = open(log_file, "w", encoding="utf-8", buffering=1) +buf = "" + + +def reader(): + global buf + while True: + ch = proc.stdout.read(1) + if ch == "": + if proc.poll() is not None: + break + time.sleep(0.05) + continue + log.write(ch) + buf += ch + if "https://claude.ai/oauth/authorize" in buf and not os.path.exists(url_file): + start = buf.find("https://claude.ai/oauth/authorize") + nl = buf.find("\n", start) + if nl != -1: + with open(url_file, "w", encoding="utf-8") as uf: + uf.write(buf[start:nl].strip()) + if len(buf) > 200000: + buf = buf[-50000:] + + +threading.Thread(target=reader, daemon=True).start() + +# Esperar la URL (max 2 min, es rapido) +wait_url = time.time() + 120 +while time.time() < wait_url and not os.path.exists(url_file): + time.sleep(0.5) + +# Sondear el codigo SIN limite de tiempo; hasta que el proceso termine +sent = False +while proc.poll() is None: + with open(hb_file, "w") as hb: + hb.write(str(time.time())) + if not sent and os.path.exists(code_file): + try: + with open(code_file, "r", encoding="utf-8") as cf: + code = cf.read().strip() + except Exception: + code = "" + if code: + try: + os.remove(code_file) + except FileNotFoundError: + pass + log.write("\n[helper] enviando codigo al proceso...\n") + try: + proc.stdin.write(code + "\n") + proc.stdin.flush() + except Exception as e: + log.write("[helper] fallo al escribir stdin: %r\n" % e) + sent = True + time.sleep(1) + +try: + rest = proc.stdout.read() + if rest: + log.write(rest) +except Exception: + pass + +log.write("\n=== PROCESO TERMINADO rc=%s ===\n" % proc.poll()) +log.close() + +with open(exit_file, "w") as ef: + ef.write("done")