diff options
Diffstat (limited to 'claude_sesiones/terminal.py')
| -rw-r--r-- | claude_sesiones/terminal.py | 121 |
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 |