diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e019b9..d5cef71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v2.1.0 (2026-05-19) + +- Added Codex auth status detection (reads `codex login status`) +- Auth status bar shows logged-in provider or warning if auth missing/expired +- "Re-login" button opens `codex login` in a terminal for re-authentication +- Auto re-checks auth 30s after re-login flow starts +- Pre-launch auth check warns before launching Codex Default mode if auth is invalid +- Auth status checked asynchronously at startup (non-blocking) + ## v2.0.1 (2026-05-19) - Added Codex CLI/Desktop installation verifier to main page diff --git a/codex-launcher_2.0.1_all.deb b/codex-launcher_2.0.1_all.deb deleted file mode 100644 index 141678b..0000000 Binary files a/codex-launcher_2.0.1_all.deb and /dev/null differ diff --git a/codex-launcher_2.1.0_all.deb b/codex-launcher_2.1.0_all.deb new file mode 100644 index 0000000..7bee63b Binary files /dev/null and b/codex-launcher_2.1.0_all.deb differ diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 6a65119..9170285 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -24,6 +24,12 @@ model_catalog_json = "" """ CHANGELOG = [ + ("2.1.0", "2026-05-19", [ + "Added Codex auth status detection (codex login status)", + "Added Re-login button to re-authenticate via codex login", + "Auto-checks auth before launching Codex Default mode", + "Warns if OAuth token expired or missing before launch", + ]), ("2.0.1", "2026-05-19", [ "Added Codex CLI/Desktop installation verifier to main page", "Disables Desktop/CLI launch buttons when corresponding tool is missing", @@ -450,6 +456,25 @@ def _detect_codex_desktop(): return str(START_SH) return None +def _check_codex_auth(): + try: + out = subprocess.run( + ["codex", "login", "status"], + capture_output=True, text=True, timeout=10, + ) + text = (out.stdout or "").strip() + if not text: + text = (out.stderr or "").strip() + if out.returncode == 0 and text: + return ("logged_in", text) + if text: + return ("error", text) + return ("unknown", "No output from codex login status") + except FileNotFoundError: + return ("not_installed", "codex not found") + except Exception as e: + return ("error", str(e)) + # ═══════════════════════════════════════════════════════════════════ # Main window # ═══════════════════════════════════════════════════════════════════ @@ -469,7 +494,7 @@ class LauncherWin(Gtk.Window): # header row hdr = Gtk.Box(spacing=8) vbox.pack_start(hdr, False, False, 0) - lbl = Gtk.Label(label="Codex Launcher v2.0.1") + lbl = Gtk.Label(label="Codex Launcher v2.1.0") lbl.set_use_markup(True) hdr.pack_start(lbl, False, False, 0) changelog_btn = Gtk.Button(label="Changelog") @@ -522,6 +547,19 @@ class LauncherWin(Gtk.Window): if not self._desktop_info: self._missing.append("desktop") + auth_box = Gtk.Box(spacing=12) + vbox.pack_start(auth_box, False, False, 0) + self._auth_label = Gtk.Label() + self._auth_label.set_markup("Checking auth…") + self._auth_label.set_use_markup(True) + self._auth_label.set_ellipsize(3) + auth_box.pack_start(self._auth_label, False, False, 0) + self._relogin_btn = Gtk.Button(label="Re-login") + self._relogin_btn.set_sensitive(False) + self._relogin_btn.connect("clicked", lambda b: self._codex_relogin()) + auth_box.pack_end(self._relogin_btn, False, False, 0) + threading.Thread(target=self._check_auth_async, daemon=True).start() + ops_box = Gtk.Box(spacing=8) vbox.pack_start(ops_box, False, False, 0) self._refresh_all_btn = Gtk.Button(label="Refresh Models") @@ -634,6 +672,50 @@ class LauncherWin(Gtk.Window): else: self.log("All dependencies OK.") + def _check_auth_async(self): + status, msg = _check_codex_auth() + GLib.idle_add(self._update_auth_status, status, msg) + + def _update_auth_status(self, status, msg): + if status == "logged_in": + self._auth_label.set_markup(f"✔ Auth: {msg}") + self._relogin_btn.set_sensitive("cli" not in self._missing) + elif status == "not_installed": + self._auth_label.set_markup("Auth: N/A (CLI not installed)") + else: + self._auth_label.set_markup(f"⚠ Auth: {msg}") + self._relogin_btn.set_sensitive("cli" not in self._missing) + return False + + def _codex_relogin(self): + self.log("Opening codex login in terminal…") + terms = [ + ("x-terminal-emulator", ["-e"]), + ("kgx", ["--"]), + ("gnome-terminal", ["--"]), + ("konsole", ["-e"]), + ("xterm", ["-e"]), + ] + term = None + term_args = None + for t in terms: + if shutil.which(t[0]): + term = t[0] + term_args = t[1] + break + if not term: + self.log("ERROR: no terminal emulator found for re-login") + return + cmd_parts = [term] + term_args + ["codex", "login"] + subprocess.Popen(cmd_parts, preexec_fn=os.setsid) + self.log("Login flow started in terminal. Re-checking auth in 30s…") + self._auth_label.set_markup("Auth: waiting for login…") + threading.Thread(target=self._delayed_auth_check, daemon=True).start() + + def _delayed_auth_check(self): + time.sleep(30) + self._check_auth_async() + def _set_busy(self, busy): def _update(): has_cli = "cli" not in self._missing @@ -883,6 +965,20 @@ class LauncherWin(Gtk.Window): threading.Thread(target=self._run, args=(ep, model, target), daemon=True).start() def _launch_codex_default(self, target): + if "cli" not in self._missing: + status, msg = _check_codex_auth() + if status != "logged_in": + d = Gtk.MessageDialog( + self, 0, Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO, + f"Codex auth check: {msg}\n\n" + "Launch may fail without valid authentication.\n" + "Continue anyway?" + ) + r = d.run() + d.destroy() + if r != Gtk.ResponseType.YES: + self._set_busy(False) + return self._set_busy(True) self.log(f"=== Codex Default (OAuth) → {'Desktop' if target == 'desktop' else 'CLI'} ===") threading.Thread(target=self._run_codex_default, args=(target,), daemon=True).start()