v2.0.1: Add Codex CLI/Desktop installation verifier
- Detects codex CLI (via 'which codex' + --version) and Desktop (via /opt/codex-desktop/start.sh) - Shows green ✔ or yellow ✘ status bar on main page - 'Install' button opens guide dialog with install commands - Desktop/CLI launch buttons disabled when corresponding tool is missing - Dependency status logged on startup - _set_busy respects missing-state (won't re-enable disabled buttons) - Rebuilt .deb as v2.0.1
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.0.1 (2026-05-19)
|
||||||
|
|
||||||
|
- Added Codex CLI/Desktop installation verifier to main page
|
||||||
|
- Green check (✔) when detected, yellow cross (✘) when missing
|
||||||
|
- "Install" button next to missing tools opens install guide dialog
|
||||||
|
- Desktop/CLI launch buttons disabled with tooltip when tool is missing
|
||||||
|
- Dependency status logged on startup
|
||||||
|
- Buttons respect missing-state after busy/unbusy cycles
|
||||||
|
|
||||||
## v2.0.0 (2026-05-19)
|
## v2.0.0 (2026-05-19)
|
||||||
|
|
||||||
- Initial release: multi-provider Codex Launcher
|
- Initial release: multi-provider Codex Launcher
|
||||||
|
|||||||
Binary file not shown.
BIN
codex-launcher_2.0.1_all.deb
Normal file
BIN
codex-launcher_2.0.1_all.deb
Normal file
Binary file not shown.
@@ -24,6 +24,11 @@ model_catalog_json = ""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
("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",
|
||||||
|
"Shows install instructions in status area on startup",
|
||||||
|
]),
|
||||||
("2.0.0", "2026-05-19", [
|
("2.0.0", "2026-05-19", [
|
||||||
"Initial release: multi-provider Codex Launcher",
|
"Initial release: multi-provider Codex Launcher",
|
||||||
"Translation proxy: Responses API to Chat Completions + Anthropic Messages",
|
"Translation proxy: Responses API to Chat Completions + Anthropic Messages",
|
||||||
@@ -429,6 +434,22 @@ def _last_log_lines(n=15):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return "(no log file)"
|
return "(no log file)"
|
||||||
|
|
||||||
|
def _detect_codex_cli():
|
||||||
|
try:
|
||||||
|
path = shutil.which("codex")
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
out = subprocess.run(["codex", "--version"], capture_output=True, text=True, timeout=5)
|
||||||
|
ver = (out.stdout or "").strip() or (out.stderr or "").strip() or "unknown"
|
||||||
|
return (path, ver)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _detect_codex_desktop():
|
||||||
|
if START_SH.exists():
|
||||||
|
return str(START_SH)
|
||||||
|
return None
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# Main window
|
# Main window
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
@@ -448,7 +469,7 @@ class LauncherWin(Gtk.Window):
|
|||||||
# header row
|
# header row
|
||||||
hdr = Gtk.Box(spacing=8)
|
hdr = Gtk.Box(spacing=8)
|
||||||
vbox.pack_start(hdr, False, False, 0)
|
vbox.pack_start(hdr, False, False, 0)
|
||||||
lbl = Gtk.Label(label="<b>Codex Launcher v2.0.0</b>")
|
lbl = Gtk.Label(label="<b>Codex Launcher v2.0.1</b>")
|
||||||
lbl.set_use_markup(True)
|
lbl.set_use_markup(True)
|
||||||
hdr.pack_start(lbl, False, False, 0)
|
hdr.pack_start(lbl, False, False, 0)
|
||||||
changelog_btn = Gtk.Button(label="Changelog")
|
changelog_btn = Gtk.Button(label="Changelog")
|
||||||
@@ -458,6 +479,49 @@ class LauncherWin(Gtk.Window):
|
|||||||
mgr_btn.connect("clicked", lambda b: self._open_mgr())
|
mgr_btn.connect("clicked", lambda b: self._open_mgr())
|
||||||
hdr.pack_end(mgr_btn, False, False, 0)
|
hdr.pack_end(mgr_btn, False, False, 0)
|
||||||
|
|
||||||
|
# verification status bar
|
||||||
|
self._cli_info = _detect_codex_cli()
|
||||||
|
self._desktop_info = _detect_codex_desktop()
|
||||||
|
ver_box = Gtk.Box(spacing=12)
|
||||||
|
vbox.pack_start(ver_box, False, False, 0)
|
||||||
|
|
||||||
|
if self._cli_info:
|
||||||
|
cli_path, cli_ver = self._cli_info
|
||||||
|
cli_lbl = Gtk.Label()
|
||||||
|
cli_lbl.set_markup(f"<span foreground='#2ea043'>✔ Codex CLI</span> <small>{cli_ver} ({cli_path})</small>")
|
||||||
|
cli_lbl.set_use_markup(True)
|
||||||
|
ver_box.pack_start(cli_lbl, False, False, 0)
|
||||||
|
else:
|
||||||
|
cli_lbl = Gtk.Label()
|
||||||
|
cli_lbl.set_markup("<span foreground='#d29922'>✘ Codex CLI — not found</span>")
|
||||||
|
cli_lbl.set_use_markup(True)
|
||||||
|
ver_box.pack_start(cli_lbl, False, False, 0)
|
||||||
|
cli_install_btn = Gtk.Button(label="Install")
|
||||||
|
cli_install_btn.connect("clicked", lambda b: self._show_install_guide("cli"))
|
||||||
|
ver_box.pack_start(cli_install_btn, False, False, 0)
|
||||||
|
|
||||||
|
ver_box.pack_start(Gtk.Label(label=" "), False, False, 0)
|
||||||
|
|
||||||
|
if self._desktop_info:
|
||||||
|
desk_lbl = Gtk.Label()
|
||||||
|
desk_lbl.set_markup(f"<span foreground='#2ea043'>✔ Codex Desktop</span> <small>({self._desktop_info})</small>")
|
||||||
|
desk_lbl.set_use_markup(True)
|
||||||
|
ver_box.pack_start(desk_lbl, False, False, 0)
|
||||||
|
else:
|
||||||
|
desk_lbl = Gtk.Label()
|
||||||
|
desk_lbl.set_markup("<span foreground='#d29922'>✘ Codex Desktop — not found</span>")
|
||||||
|
desk_lbl.set_use_markup(True)
|
||||||
|
ver_box.pack_start(desk_lbl, False, False, 0)
|
||||||
|
desk_install_btn = Gtk.Button(label="Install")
|
||||||
|
desk_install_btn.connect("clicked", lambda b: self._show_install_guide("desktop"))
|
||||||
|
ver_box.pack_start(desk_install_btn, False, False, 0)
|
||||||
|
|
||||||
|
self._missing = []
|
||||||
|
if not self._cli_info:
|
||||||
|
self._missing.append("cli")
|
||||||
|
if not self._desktop_info:
|
||||||
|
self._missing.append("desktop")
|
||||||
|
|
||||||
ops_box = Gtk.Box(spacing=8)
|
ops_box = Gtk.Box(spacing=8)
|
||||||
vbox.pack_start(ops_box, False, False, 0)
|
vbox.pack_start(ops_box, False, False, 0)
|
||||||
self._refresh_all_btn = Gtk.Button(label="Refresh Models")
|
self._refresh_all_btn = Gtk.Button(label="Refresh Models")
|
||||||
@@ -488,18 +552,30 @@ class LauncherWin(Gtk.Window):
|
|||||||
vbox.pack_start(btn_box, False, False, 8)
|
vbox.pack_start(btn_box, False, False, 8)
|
||||||
self._btn_desktop = Gtk.Button(label="Launch Desktop")
|
self._btn_desktop = Gtk.Button(label="Launch Desktop")
|
||||||
self._btn_desktop.connect("clicked", lambda b: self._launch("desktop"))
|
self._btn_desktop.connect("clicked", lambda b: self._launch("desktop"))
|
||||||
|
if "desktop" in self._missing:
|
||||||
|
self._btn_desktop.set_tooltip_text("Codex Desktop is not installed")
|
||||||
|
self._btn_desktop.set_sensitive(False)
|
||||||
btn_box.pack_start(self._btn_desktop, True, True, 0)
|
btn_box.pack_start(self._btn_desktop, True, True, 0)
|
||||||
self._btn_cli = Gtk.Button(label="Launch CLI")
|
self._btn_cli = Gtk.Button(label="Launch CLI")
|
||||||
self._btn_cli.connect("clicked", lambda b: self._launch("cli"))
|
self._btn_cli.connect("clicked", lambda b: self._launch("cli"))
|
||||||
|
if "cli" in self._missing:
|
||||||
|
self._btn_cli.set_tooltip_text("Codex CLI is not installed")
|
||||||
|
self._btn_cli.set_sensitive(False)
|
||||||
btn_box.pack_start(self._btn_cli, True, True, 0)
|
btn_box.pack_start(self._btn_cli, True, True, 0)
|
||||||
|
|
||||||
btn_box2 = Gtk.Box(spacing=8, homogeneous=True)
|
btn_box2 = Gtk.Box(spacing=8, homogeneous=True)
|
||||||
vbox.pack_start(btn_box2, False, False, 0)
|
vbox.pack_start(btn_box2, False, False, 0)
|
||||||
self._btn_codex_desktop = Gtk.Button(label="Codex Default (Desktop)")
|
self._btn_codex_desktop = Gtk.Button(label="Codex Default (Desktop)")
|
||||||
self._btn_codex_desktop.connect("clicked", lambda b: self._launch_codex_default("desktop"))
|
self._btn_codex_desktop.connect("clicked", lambda b: self._launch_codex_default("desktop"))
|
||||||
|
if "desktop" in self._missing:
|
||||||
|
self._btn_codex_desktop.set_tooltip_text("Codex Desktop is not installed")
|
||||||
|
self._btn_codex_desktop.set_sensitive(False)
|
||||||
btn_box2.pack_start(self._btn_codex_desktop, True, True, 0)
|
btn_box2.pack_start(self._btn_codex_desktop, True, True, 0)
|
||||||
self._btn_codex_cli = Gtk.Button(label="Codex Default (CLI)")
|
self._btn_codex_cli = Gtk.Button(label="Codex Default (CLI)")
|
||||||
self._btn_codex_cli.connect("clicked", lambda b: self._launch_codex_default("cli"))
|
self._btn_codex_cli.connect("clicked", lambda b: self._launch_codex_default("cli"))
|
||||||
|
if "cli" in self._missing:
|
||||||
|
self._btn_codex_cli.set_tooltip_text("Codex CLI is not installed")
|
||||||
|
self._btn_codex_cli.set_sensitive(False)
|
||||||
btn_box2.pack_start(self._btn_codex_cli, True, True, 0)
|
btn_box2.pack_start(self._btn_codex_cli, True, True, 0)
|
||||||
|
|
||||||
# status
|
# status
|
||||||
@@ -529,6 +605,7 @@ class LauncherWin(Gtk.Window):
|
|||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
self._rebuild_combo()
|
self._rebuild_combo()
|
||||||
|
self._log_dependency_status()
|
||||||
|
|
||||||
# ── helpers ──────────────────────────────────────────────────
|
# ── helpers ──────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -542,14 +619,31 @@ class LauncherWin(Gtk.Window):
|
|||||||
self._tv.scroll_to_mark(m, 0.0, True, 0.0, 0.5)
|
self._tv.scroll_to_mark(m, 0.0, True, 0.0, 0.5)
|
||||||
self._buf.delete_mark(m)
|
self._buf.delete_mark(m)
|
||||||
|
|
||||||
|
def _log_dependency_status(self):
|
||||||
|
if self._cli_info:
|
||||||
|
_, ver = self._cli_info
|
||||||
|
self.log(f"✔ Codex CLI detected ({ver})")
|
||||||
|
else:
|
||||||
|
self.log("✘ Codex CLI NOT found — CLI launch disabled. Click 'Install' above.")
|
||||||
|
if self._desktop_info:
|
||||||
|
self.log(f"✔ Codex Desktop detected ({self._desktop_info})")
|
||||||
|
else:
|
||||||
|
self.log("✘ Codex Desktop NOT found — Desktop launch disabled. Click 'Install' above.")
|
||||||
|
if self._missing:
|
||||||
|
self.log("⚠ Install missing tools before using the launcher.")
|
||||||
|
else:
|
||||||
|
self.log("All dependencies OK.")
|
||||||
|
|
||||||
def _set_busy(self, busy):
|
def _set_busy(self, busy):
|
||||||
GLib.idle_add(lambda: (
|
def _update():
|
||||||
self._btn_desktop.set_sensitive(not busy),
|
has_cli = "cli" not in self._missing
|
||||||
self._btn_cli.set_sensitive(not busy),
|
has_desk = "desktop" not in self._missing
|
||||||
self._btn_codex_desktop.set_sensitive(not busy),
|
self._btn_desktop.set_sensitive(not busy and has_desk)
|
||||||
self._btn_codex_cli.set_sensitive(not busy),
|
self._btn_cli.set_sensitive(not busy and has_cli)
|
||||||
self._kill_btn.set_sensitive(busy),
|
self._btn_codex_desktop.set_sensitive(not busy and has_desk)
|
||||||
))
|
self._btn_codex_cli.set_sensitive(not busy and has_cli)
|
||||||
|
self._kill_btn.set_sensitive(busy)
|
||||||
|
GLib.idle_add(_update)
|
||||||
|
|
||||||
def _rebuild_combo(self):
|
def _rebuild_combo(self):
|
||||||
self._endpoints_data = load_endpoints()
|
self._endpoints_data = load_endpoints()
|
||||||
@@ -746,6 +840,31 @@ class LauncherWin(Gtk.Window):
|
|||||||
d.run()
|
d.run()
|
||||||
d.destroy()
|
d.destroy()
|
||||||
|
|
||||||
|
def _show_install_guide(self, which):
|
||||||
|
if which == "cli":
|
||||||
|
title = "Install Codex CLI"
|
||||||
|
guide = (
|
||||||
|
"Codex CLI is required to use CLI launch features.\n\n"
|
||||||
|
"Install with npm:\n"
|
||||||
|
" npm install -g @openai/codex\n\n"
|
||||||
|
"Or download from:\n"
|
||||||
|
" https://github.com/openai/codex\n\n"
|
||||||
|
"After installing, restart the launcher."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
title = "Install Codex Desktop"
|
||||||
|
guide = (
|
||||||
|
"Codex Desktop is required to use Desktop launch features.\n\n"
|
||||||
|
"Expected location: /opt/codex-desktop/start.sh\n\n"
|
||||||
|
"Download from:\n"
|
||||||
|
" https://codex.desktop.openai.com\n\n"
|
||||||
|
"After installing, restart the launcher."
|
||||||
|
)
|
||||||
|
d = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, guide)
|
||||||
|
d.set_title(title)
|
||||||
|
d.run()
|
||||||
|
d.destroy()
|
||||||
|
|
||||||
# ── launch ───────────────────────────────────────────────────
|
# ── launch ───────────────────────────────────────────────────
|
||||||
|
|
||||||
def _launch(self, target):
|
def _launch(self, target):
|
||||||
|
|||||||
Reference in New Issue
Block a user