Source code for tui

"""
TUI for inspecting running Python kernels.

  python tui.py

Navigation:
  j/k or arrow keys  move selection
  enter              select / drill in
  q / esc            go back / quit
  r                  refresh
"""

import curses
import glob
import json
import os
import socket

SOCKET_DIR = "/tmp"
SOCKET_PREFIX = "python-kernel-"

# Claude Code dark palette (256-color approximations)
# background: #1a1a1a  → color 234
# foreground: #e0e0e0  → color 254
# accent:     #AFD7FF  → color 153  (normal accent, light blue)
# active:     #87D787  → color 114  (active/connected, green)
# error:      #FF87AF  → color 211  (error/warning, pink-red)
# muted:      #6b6b6b  → color 242
# separator:  #333333  → color 236

C_BG      = 16
C_FG      = 254
C_ACCENT  = 153
C_ACTIVE  = 114
C_ERROR   = 211
C_MUTED   = 242
C_SEP     = 236

P_NORMAL  = 1
P_SEL     = 2
P_TITLE   = 3
P_HINT    = 4
P_SEP     = 5
P_MUTED   = 6
P_ACTIVE  = 7
P_ERROR   = 8


[docs] def init_colors(): curses.start_color() curses.use_default_colors() curses.init_pair(P_NORMAL, C_FG, C_BG) curses.init_pair(P_SEL, C_BG, C_ACCENT) curses.init_pair(P_TITLE, C_ACCENT, C_BG) curses.init_pair(P_HINT, C_MUTED, C_BG) curses.init_pair(P_SEP, C_SEP, C_BG) curses.init_pair(P_MUTED, C_MUTED, C_BG) curses.init_pair(P_ACTIVE, C_ACTIVE, C_BG) curses.init_pair(P_ERROR, C_ERROR, C_BG)
[docs] def socket_path(name: str) -> str: return os.path.join(SOCKET_DIR, f"{SOCKET_PREFIX}{name}.sock")
[docs] def list_kernels() -> list[str]: socks = glob.glob(os.path.join(SOCKET_DIR, f"{SOCKET_PREFIX}*.sock")) names = [] for s in sorted(socks): name = os.path.basename(s)[len(SOCKET_PREFIX):-len(".sock")] try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as c: c.connect(s) names.append(name) except OSError: pass return names
[docs] def kernel_vars(name: str) -> dict[str, str] | str: path = socket_path(name) try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: s.connect(path) s.sendall(json.dumps({"cmd": "vars", "code": ""}).encode() + b"\n") data = json.loads(s.makefile("rb").readline()) if data["ok"]: return data["result"] return data["error"] except Exception as e: return f"{type(e).__name__}: {e}"
[docs] def fill_bg(win): h, w = win.getmaxyx() for y in range(h): try: win.addstr(y, 0, " " * (w - 1), curses.color_pair(P_NORMAL)) except curses.error: pass
[docs] def draw_hint_bar(win, text: str): h, w = win.getmaxyx() bar = text[:w-1].ljust(w - 1) try: win.addstr(h - 1, 0, bar, curses.color_pair(P_HINT)) except curses.error: pass
[docs] def draw_list(win, items: list[str], sel: int, title: str): win.erase() fill_bg(win) h, w = win.getmaxyx() win.addstr(0, 1, title[:w-2], curses.color_pair(P_TITLE) | curses.A_BOLD) sep = "─" * (w - 1) try: win.addstr(1, 0, sep, curses.color_pair(P_SEP)) except curses.error: pass for i, item in enumerate(items): y = i + 2 if y >= h - 1: break if i == sel: line = f" ▶ {item} ".ljust(w - 1) win.addstr(y, 0, line[:w-1], curses.color_pair(P_SEL) | curses.A_BOLD) else: win.addstr(y, 0, " ", curses.color_pair(P_NORMAL)) win.addstr(y, 3, item[:w-4], curses.color_pair(P_ACTIVE)) if not items: win.addstr(2, 2, "(no kernels running)", curses.color_pair(P_ERROR)) draw_hint_bar(win, " q quit ↑↓ / jk move enter select r refresh") win.refresh()
[docs] def draw_vars(win, name: str, variables: dict[str, str] | str, sel: int): win.erase() fill_bg(win) h, w = win.getmaxyx() win.addstr(0, 1, "kernel: ", curses.color_pair(P_TITLE) | curses.A_BOLD) win.addstr(0, 9, name[:w-10], curses.color_pair(P_ACTIVE) | curses.A_BOLD) sep = "─" * (w - 1) try: win.addstr(1, 0, sep, curses.color_pair(P_SEP)) except curses.error: pass if isinstance(variables, str): win.addstr(2, 2, variables[:w-3], curses.color_pair(P_ERROR)) elif not variables: win.addstr(2, 2, "(empty namespace)", curses.color_pair(P_MUTED)) else: items = list(variables.items()) for i, (k, v) in enumerate(items): y = i + 2 if y >= h - 1: break if i == sel: line = f" ▶ {k} = {v} ".ljust(w - 1) win.addstr(y, 0, line[:w-1], curses.color_pair(P_SEL) | curses.A_BOLD) else: # key in blue accent, = in muted, value in normal white key_str = f" {k}" eq_str = " = " val_str = v x = 0 win.addstr(y, x, key_str[:w-1], curses.color_pair(P_TITLE)) x += len(key_str) if x < w - 1: win.addstr(y, x, eq_str[:w-1-x], curses.color_pair(P_MUTED)) x += len(eq_str) if x < w - 1: win.addstr(y, x, val_str[:w-1-x], curses.color_pair(P_NORMAL)) draw_hint_bar(win, " q / esc back ↑↓ / jk move r refresh") win.refresh()
[docs] def run(stdscr): curses.curs_set(0) stdscr.keypad(True) init_colors() sel = 0 view = "kernels" current_kernel = "" current_vars: dict[str, str] | str = {} var_sel = 0 kernels = list_kernels() while True: if view == "kernels": sel = min(sel, max(0, len(kernels) - 1)) draw_list(stdscr, kernels, sel, "Python Kernels") key = stdscr.getch() if key in (ord("q"), 27): break elif key in (curses.KEY_UP, ord("k")) and sel > 0: sel -= 1 elif key in (curses.KEY_DOWN, ord("j")) and sel < len(kernels) - 1: sel += 1 elif key in (curses.KEY_ENTER, 10, 13) and kernels: current_kernel = kernels[sel] current_vars = kernel_vars(current_kernel) var_sel = 0 view = "vars" elif key == ord("r"): kernels = list_kernels() sel = 0 elif view == "vars": items = list(current_vars.items()) if isinstance(current_vars, dict) else [] n = len(items) var_sel = min(var_sel, max(0, n - 1)) draw_vars(stdscr, current_kernel, current_vars, var_sel) ch = stdscr.getch() if ch in (ord("q"), 27): view = "kernels" kernels = list_kernels() elif ch in (curses.KEY_UP, ord("k")) and var_sel > 0: var_sel -= 1 elif ch in (curses.KEY_DOWN, ord("j")) and var_sel < n - 1: var_sel += 1 elif ch == ord("r"): current_vars = kernel_vars(current_kernel) var_sel = 0
[docs] def main(): curses.wrapper(run)
if __name__ == "__main__": main()