aboutsummaryrefslogtreecommitdiffstats
path: root/claude_sesiones/terminal.py
diff options
context:
space:
mode:
Diffstat (limited to 'claude_sesiones/terminal.py')
-rw-r--r--claude_sesiones/terminal.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/claude_sesiones/terminal.py b/claude_sesiones/terminal.py
index 2bf9031..9a16bec 100644
--- a/claude_sesiones/terminal.py
+++ b/claude_sesiones/terminal.py
@@ -249,3 +249,124 @@ def pager(text):
p.communicate(text.encode())
except (OSError, BrokenPipeError):
sys.stdout.write(text)
+
+
+# ──────────────────────────────── memorias ────────────────────────────────
+
+# Anchos mínimos para las columnas opcionales de la tabla de memorias.
+MIN_COLS_MEM_PROJECT = 78
+MIN_COLS_MEM_DESC = 104
+
+TYPE_COLOR = {"project": "amber", "user": "blue",
+ "feedback": "copper", "reference": "grey"}
+
+
+def print_memories(memories, st, now, out, width=None):
+ width = width or shutil.get_terminal_size((100, 24)).columns
+ show_project = width >= MIN_COLS_MEM_PROJECT
+ show_desc = width >= MIN_COLS_MEM_DESC
+
+ # columnas fijas: idx(3) tipo(9) fecha(6) cuándo(9) + separadores
+ fixed = 3 + 9 + 6 + 9
+ gaps = 4 + (1 if show_project else 0) + (1 if show_desc else 0)
+ flex = max(18, width - fixed - gaps)
+ if show_desc:
+ w_name, w_project = int(flex * 0.34), int(flex * 0.28)
+ elif show_project:
+ w_name, w_project = int(flex * 0.58), flex - int(flex * 0.58)
+ else:
+ w_name, w_project = flex, 0
+ w_desc = flex - w_name - w_project if show_desc else 0
+
+ head = [f"{'#':>3}", f"{st.faint}{'MEMORIA':<{w_name}}{st.reset}",
+ f"{st.faint}{'TIPO':<9}{st.reset}"]
+ if show_project:
+ head.append(f"{st.faint}{'PROYECTO':<{w_project}}{st.reset}")
+ if show_desc:
+ head.append(f"{st.faint}{'DESCRIPCIÓN':<{w_desc}}{st.reset}")
+ head.append(f"{st.faint}{'FECHA':<6} {'CUÁNDO':<9}{st.reset}")
+ print(" ".join(head), file=out)
+
+ for i, m in enumerate(memories, 1):
+ # El asterisco marca lo que no está en MEMORY.md: existe pero no se carga.
+ tag = "" if m["ix"] else f" {st.copper}*{st.reset}"
+ avail = w_name - visible_len(tag)
+ cells = [
+ f"{st.faint}{i:>3}{st.reset}",
+ f"{st.ink}{clip(m['name'], avail):<{avail}}{st.reset}{tag}",
+ f"{getattr(st, TYPE_COLOR.get(m['ty'], 'grey'))}"
+ f"{clip(m['ty'], 9):<9}{st.reset}",
+ ]
+ if show_project:
+ cells.append(f"{st.grey}{clip(m['p'], w_project):<{w_project}}{st.reset}")
+ if show_desc:
+ cells.append(f"{st.grey}{clip(m['desc'], w_desc):<{w_desc}}{st.reset}")
+ cells.append(f"{fmt_date(m['l']):<6} "
+ f"{st.faint}{fmt_rel(m['l'], now):<9}{st.reset}")
+ print(" ".join(cells), file=out)
+
+
+def print_memory(m, st, out, path=None, width=None):
+ width = min(width or shutil.get_terminal_size((100, 24)).columns, 100)
+
+ print(f"{st.amber}{st.bold}{m['name']}{st.reset}", file=out)
+ if m["desc"]:
+ for line in textwrap.wrap(m["desc"], width=width - 2):
+ print(f"{st.grey}{line}{st.reset}", file=out)
+
+ meta = (f"{m['ty']} · {m['p']} · "
+ f"{fmt_date(m['l'])} {fmt_time(m['l'])} · {fmt_size(m['k'])}")
+ print(f"{st.faint}{meta}{st.reset}", file=out)
+ if path:
+ print(f"{st.faint}{path}{st.reset}", file=out)
+
+ if not m["ix"]:
+ aviso = ("este proyecto no tiene MEMORY.md" if not m["hix"]
+ else "no figura en MEMORY.md, así que no se carga en contexto")
+ print(f"{st.copper}* {aviso}{st.reset}", file=out)
+
+ print(f"{st.faint}{'─' * min(width, 80)}{st.reset}\n", file=out)
+
+ for line in render_block(m["body"], st, width - 2, st.grey):
+ print(line, file=out)
+
+ if m["ln"]:
+ print(f"\n{st.faint}enlaces: {', '.join(m['ln'])}{st.reset}", file=out)
+ if m["src"]:
+ print(f"{st.faint}la creó la sesión {m['src'][:8]} "
+ f"(claude-sesiones -s {m['src'][:8]}){st.reset}", file=out)
+
+
+def print_audit(report, st, out):
+ """Informe de inconsistencias. Devuelve cuántas se listaron."""
+ bloques = (
+ ("sin_indice", "Proyectos con memorias pero sin MEMORY.md",
+ "sin índice no se carga ninguna de sus memorias",
+ lambda m: f"{clip(m['name'], 38):<38} {st.grey}{m['p']}{st.reset}"),
+ ("sin_listar", "Memorias que no figuran en su MEMORY.md",
+ "el índice es lo que se lee al arrancar: si no está, no se recuerda",
+ lambda m: f"{clip(m['name'], 38):<38} {st.grey}{m['p']}{st.reset}"),
+ ("indice_fantasma", "Entradas del índice sin archivo",
+ "apuntan a una memoria que ya no existe",
+ lambda t: f"{clip(t[1], 38):<38} {st.grey}{t[0]}{st.reset}"),
+ ("enlaces_rotos", "Enlaces [[...]] sin destino",
+ "el formato los permite: marcan algo que todavía no se escribió",
+ lambda t: f"{clip(t[0]['name'], 38):<38} {st.grey}→ [[{t[1]}]]{st.reset}"),
+ ("origen_perdido", "Memorias cuya sesión de origen ya no existe",
+ "la memoria sobrevivió a la conversación que la creó",
+ lambda m: f"{clip(m['name'], 38):<38} {st.grey}{m['src'][:8]}{st.reset}"),
+ )
+
+ total = 0
+ for key, titulo, nota, fmt in bloques:
+ items = report.get(key) or []
+ if not items:
+ continue
+ total += len(items)
+ print(f"{st.copper}{titulo} ({len(items)}){st.reset}", file=out)
+ print(f"{st.faint} {nota}{st.reset}", file=out)
+ for item in items:
+ print(f" {fmt(item)}", file=out)
+ print(file=out)
+
+ return total