diff --git a/codex-launcher_2.3.2_all.deb b/codex-launcher_2.3.2_all.deb index 2385cf9..c002ee0 100644 Binary files a/codex-launcher_2.3.2_all.deb and b/codex-launcher_2.3.2_all.deb differ diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 18190ef..2a64b47 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -1645,6 +1645,14 @@ class EditEndpointDialog(Gtk.Dialog): if initial and self._data.get("models"): self._refresh_default_combo(self._data.get("default_model", "")) + def _on_reasoning_toggled(self, *_): + active = self._switch_reasoning.get_active() + self._combo_effort.set_sensitive(active) + if active: + self._lbl_reasoning.set_markup('ON') + else: + self._lbl_reasoning.set_markup('OFF') + def _do_oauth_login(self): preset_name = self._combo_preset.get_active_text() or "Custom" preset = PROVIDER_PRESETS.get(preset_name, {}) @@ -1657,21 +1665,36 @@ class EditEndpointDialog(Gtk.Dialog): client_secret_path = os.path.expanduser("~/.cache/codex-proxy/client_secret.json") if not os.path.exists(client_secret_path): - d = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, - "Google OAuth Setup\n\n" - "1. Go to https://console.cloud.google.com/\n" - "2. Create a project → Enable 'Generative Language API'\n" - "3. Go to APIs & Services → Credentials → Create OAuth 2.0 Client ID\n" - "4. Application type: Desktop app\n" - "5. Add redirect URI: http://localhost:8085\n" - "6. Download client_secret.json\n" - f"7. Save it to: {client_secret_path}\n\n" - "Then click OAuth Login again.") - d.run(); d.destroy() + dlg = Gtk.Dialog(title="Google OAuth Setup", parent=self, modal=True) + dlg.add_button("Open Google Console", 1) + dlg.add_button("I have client_secret.json", 2) + dlg.add_button("Cancel", Gtk.ResponseType.CANCEL) + dlg.set_default_size(500, 320) + area = dlg.get_content_area() + area.set_margin_start(16) + area.set_margin_end(16) + area.set_margin_top(12) + area.set_margin_bottom(12) + area.set_spacing(8) + area.pack_start(Gtk.Label(label="One-time Google OAuth Setup", use_markup=True), False, False, 0) + steps = Gtk.Label(label=( + "1. Create project at Google Cloud Console\n" + " (enable 'Generative Language API')\n" + "2. APIs & Services → Credentials\n" + " → Create OAuth 2.0 Client ID (Desktop app)\n" + "3. Add redirect URI: http://localhost:8085\n" + "4. Download client_secret.json\n" + f"5. Save to: {client_secret_path}\n\n" + "Then click 'I have client_secret.json'" + ), xalign=0) + area.pack_start(steps, False, False, 4) + area.show_all() + r = dlg.run() + dlg.destroy() + if r == 1: + subprocess.Popen(["xdg-open", "https://console.cloud.google.com/apis/credentials"]) return - import http.server, threading, urllib.parse, json, urllib.request - with open(client_secret_path) as f: cs = json.load(f) installed = cs.get("installed", cs.get("web", {})) @@ -1686,7 +1709,36 @@ class EditEndpointDialog(Gtk.Dialog): f"&response_type=code&scope={urllib.parse.quote(scope)}&access_type=offline&prompt=consent" ) + dlg = Gtk.Dialog(title="Google OAuth", parent=self, modal=True) + dlg.add_button("Cancel", Gtk.ResponseType.CANCEL) + dlg.set_default_size(520, 260) + area = dlg.get_content_area() + area.set_margin_start(16) + area.set_margin_end(16) + area.set_margin_top(12) + area.set_margin_bottom(12) + area.set_spacing(8) + + area.pack_start(Gtk.Label(label="Step 1: Authorize with Google", use_markup=True, xalign=0), False, False, 0) + link_lbl = Gtk.Label(label=f'Click here to open Google authorization', use_markup=True) + area.pack_start(link_lbl, False, False, 4) + + area.pack_start(Gtk.Label(label="Opening browser automatically…", xalign=0), False, False, 0) + + area.pack_start(Gtk.Label(label="Waiting for authorization…", use_markup=True, xalign=0), False, False, 12) + self._oauth_status = Gtk.Label(label="Listening on http://localhost:8085 …", xalign=0) + area.pack_start(self._oauth_status, False, False, 0) + + spinner = Gtk.Spinner() + spinner.start() + area.pack_start(spinner, False, False, 8) + + area.show_all() + + import http.server code_holder = [None] + error_holder = [None] + class OAuthHandler(http.server.BaseHTTPRequestHandler): def do_GET(self2): qs = urllib.parse.urlparse(self2.path).query @@ -1696,37 +1748,59 @@ class EditEndpointDialog(Gtk.Dialog): self2.send_response(200) self2.send_header("Content-Type", "text/html") self2.end_headers() - self2.wfile.write(b"
You can close this tab and return to Codex Launcher.
") else: + error_holder[0] = params.get("error", ["unknown"])[0] self2.send_response(400) + self2.send_header("Content-Type", "text/html") self2.end_headers() - self2.wfile.write(b"Authorization failed.") + self2.wfile.write(b"" + b"Please close this tab and try again.
") def log_message(self2, *a): pass - server = http.server.HTTPServer(("127.0.0.1", 8085), OAuthHandler) - t = threading.Thread(target=server.handle_request, daemon=True) - t.start() - - os.system(f"xdg-open '{auth_url}' 2>/dev/null &") - - t.join(timeout=120) - server.server_close() - - if not code_holder[0]: - d = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, "OAuth timed out or failed.") - d.run(); d.destroy() + try: + server = http.server.HTTPServer(("127.0.0.1", 8085), OAuthHandler) + except OSError: + self._oauth_status.set_text("Port 8085 already in use — close other apps and retry.") + spinner.stop() + dlg.run(); dlg.destroy() return - token_data = urllib.parse.urlencode({ - "code": code_holder[0], - "client_id": client_id, - "client_secret": client_secret, - "redirect_uri": redirect_uri, - "grant_type": "authorization_code", - }).encode() - req = urllib.request.Request("https://oauth2.googleapis.com/token", data=token_data, - headers={"Content-Type": "application/x-www-form-urlencoded"}) + def wait_for_code(): + server.handle_request() + server.server_close() + GLib.idle_add(self._google_oauth_complete, dlg, code_holder, error_holder, + client_id, client_secret, redirect_uri, token_path, spinner) + + threading.Thread(target=wait_for_code, daemon=True).start() + subprocess.Popen(["xdg-open", auth_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + dlg.run() + dlg.destroy() + + def _google_oauth_complete(self, dlg, code_holder, error_holder, + client_id, client_secret, redirect_uri, token_path, spinner): + spinner.stop() + if error_holder[0]: + self._oauth_status.set_markup(f'Error: {error_holder[0]}') + return + if not code_holder[0]: + self._oauth_status.set_text("No authorization code received.") + return + + self._oauth_status.set_text("Exchanging code for token…") try: + token_data = urllib.parse.urlencode({ + "code": code_holder[0], + "client_id": client_id, + "client_secret": client_secret, + "redirect_uri": redirect_uri, + "grant_type": "authorization_code", + }).encode() + req = urllib.request.Request("https://oauth2.googleapis.com/token", data=token_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}) resp = urllib.request.urlopen(req, timeout=30) tokens = json.loads(resp.read()) tokens["client_id"] = client_id @@ -1735,18 +1809,10 @@ class EditEndpointDialog(Gtk.Dialog): with open(token_path, "w") as f: json.dump(tokens, f, indent=2) self._entry_key.set_text(tokens.get("access_token", "")) - d = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, - f"Google OAuth successful!\n\nAccess token saved.\nIt will auto-refresh when expired.") - d.run(); d.destroy() + self._oauth_status.set_markup('✓ Authorization successful! Token saved.') + dlg.set_title("Google OAuth — Success") except Exception as e: - d = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, f"Token exchange failed:\n{e}") - d.run(); d.destroy() - active = self._switch_reasoning.get_active() - self._combo_effort.set_sensitive(active) - if active: - self._lbl_reasoning.set_markup('ON') - else: - self._lbl_reasoning.set_markup('OFF') + self._oauth_status.set_markup(f'Token exchange failed: {e}') def _remove_model(self, path): current = self._combo_default.get_active_text()