Cancelé GitKraken y construí mi propio cliente Git en dos días

El GitKraken taba muy pesado pa' mi máquina y pa' mi bolsillo
Llegó el correo de renovación anual de GitKraken y lo miré con un cariño especial: el cariño de quien mira la factura de la luz. Pago todos los años. Por un cliente Git. Que tarda una eternidad en abrir y se traga la RAM como si fuera Chrome con 40 pestañas.
Lo peor ni siquiera era la plata, era la espera. Hacer clic en el ícono, ir a buscar café, volver, y el bicho todavía cargando. Mi máquina aguanta builds de proyectos grandes todo el día, pero sufría para renderizar un grafo de commits en Electron.
Entonces pensé: ¿y si en vez de cambiarme a otra herramienta de pago (o gratuita y capada), simplemente construyo la mía? A mi manera, ligera, sin cuota anual.
Nació GitCraque. Este post cuenta cómo.
Tiempo estimado de lectura: 5 min

Dos días de vibe coding y un chiste que no pienso soltar
Entonces construí el mío. Se llama GitCraque, y básicamente fueron dos días de vibe coding desde cero hasta convertirse en mi cliente Git de todos los días. Sin Electron, sin suscripción anual, sin esperar una pantalla de carga.
¿El nombre? Mira al GitKraken: un pulpo gigante, ocho brazos, vive en el fondo del mar y nunca ganó un Mundial. Ocho brazos y cero trofeos es un rendimiento pésimo para cualquier plantel. Del otro lado había un tipo con el corte de pelo más indefendible de la historia del fútbol, dos goles en la final y el apodo de El Fenómeno. La elección se hizo sola. Cambié la K por la C, el cefalópodo por el camiseta 9.
Sí, es un tributo. Sí, es un chiste. No, no paramos.
Para probarlo es un comando, dentro de cualquier repositorio:
npx gitcraque # dentro del directorio del repositorio
npx gitcraque ~/code/project # o apunta un path, tipo `git -C`
npx gitcraque --repo ~ --port 5271
npx gitcraque --no-open # no abre el navegador
El repo está en https://github.com/frederico-kluser/GitCraque. Y aviso de una: el proyecto está creciendo, evolucionando, no está terminado. Está jugando bien, pero todavía está en temporada.
La alineación: un backend con una sola dependencia
Equipo ajustado, sin plantel inflado y sin nómina en node_modules. La alineación quedó así:
- Arquero: Node puro con
node:http. Para la API sin framework al frente. Nada de Express, nada de Fastify, solo lo que viene en la caja. - Defensa:
child_process, marcando al binario degitde cerca, siempre con argv en array. Sin interpolación de shell en mi equipo. - Lateral:
ws. La única dependencia del backend. Una. Total. - Mediocampo: React 19 + Vite, distribuyendo el juego para la SPA.
- Camiseta 10: Tailwind + Motion UI, el primer toque y la belleza.
- Extremo:
@dnd-kit/core, arrastra el commit y deja al marcador atrás. - Camiseta 9: SVG escrito a mano. Todo el grafo es
<circle>y<path>con Bézier cúbica. Ninguna lib de gitgraph. El regate es todo nuestro.
¿Y Electron? No fue convocado. Un cliente Git empaquetado en Chromium es ese jugador que llega pesado, se come la mitad de la RAM y tarda en calentar. Aquí no.
Requisitos para alinear: Node >= 22.13 (node:sqlite solo suelta la bandera experimental a partir de esa versión) y git en el PATH. Los botines son opcionales.
As funcionalidades que me fizeram esquecer o GitKraken
El equipo ya está en la cancha. Ahora, lo que hace. Seis cosas, en el orden en que me conquistaron:
-
El grafo del historial. El backend corre
git log --all --topo-ordery entrega los datos crudos; el front calcula el(X, Y)de cada commit con un algoritmo propio, separando hijos de rama de hijos de merge para trazar rutas que nunca se cruzan. El dibujo es SVG escrito a mano,<circle>para los commits y<path>con Béziers para las ramas, con virtualización de ventana. Repo con decenas de miles de commits y el scroll sigue fluido. Sin librería de gitgraph. El dribleo es nuestro. -
Worktrees sin checkout. Haces clic en un worktree en la barra lateral, el servidor hace
process.chdir()a la ruta absoluta y avisa por WebSocket. No haygit checkout, no se toca la working tree de nadie. Es cambiar de posición sin pedirle cambio al cuarto árbitro. -
Mi favorita, y mira que las probé todas: borrar worktree, rama local y rama en origin con un solo comando. En GitKraken lo hacía en dos comandos separados, cada vez, y cada vez me irritaba. Ahora es un clic. Parece una tontería, pero es ese tipo de fricción diaria que se acumula hasta que cancelas una suscripción.
-
Drag-and-drop con intención. Arrastrar un commit sobre una rama propone cherry-pick; rama sobre rama abre la opción entre merge y rebase. El comando crudo aparece antes de ejecutarse, y cualquier cosa que reescriba historial requiere mantener presionado: el botón solo cede si lo sostienes. Tiempo para pensar antes de patear.
-
Push que nunca se cuelga. Modelo trampolín con
GIT_ASKPASSapuntando al propio script de Node, que le responde a git por stdout. El token viaja por socket unix con nonce de un solo uso. Nunca en argv, nunca en disco. -
Picker cuando abres fuera de un repo. Pantalla con recientes, escaneo de las carpetas habituales y hasta
git initdonde estás parado. ¿Empezaste fuera de un repositorio? No es error, es scouting.
El squash gráfico sin abrir vim en tu cara
Todo cliente Git que intenta hacer un rebase interactivo "bonito" cae en una de dos trampas: o emula una terminal entera dentro de la UI (pesado, frágil, horroroso), o se rinde y te abre vim en la cara. Odio las dos opciones. Git ya sabe hacer rebase interactivo solo, el problema es que insiste en abrir un editor para que edites el git-rebase-todo a mano.
Ahí vino la idea: ¿y si el "editor" fuera un script mío?
Git respeta la variable GIT_SEQUENCE_EDITOR. Cuando está seteada, genera el git-rebase-todo, llama al programa apuntado pasándole la ruta del archivo, y espera un exit 0 para continuar. Yo inyecto GIT_SEQUENCE_EDITOR="node proxy-editor.mjs" y el script hace el trabajo sucio:
// server/proxy-editor.mjs
import { readFileSync, writeFileSync } from 'node:fs';
const todoPath = process.argv[2];
const targets = new Set(process.env.GITCRaque_SQUASH.split('\n'));
const lines = readFileSync(todoPath, 'utf8').split('\n');
const edited = lines.map((line, i) => {
const [action, hash] = line.split(' ');
if (action === 'pick' && i > 0 && targets.has(hash)) {
return line.replace(/^pick/, 'squash');
}
return line;
});
writeFileSync(todoPath, edited.join('\n'));
process.exit(0);
Lee el todo que el propio git generó, cambia pick por squash en las líneas marcadas en la UI (manteniendo el primer commit como pick, si no el rebase entero se descarrila), graba y sale con exit 0. Git aplica el rewrite como si un humano hubiera editado el archivo con todo el cuidado del mundo.
Cero emulación de terminal. Cero vim.
Tres commits entran, uno sale. Y nadie vio cómo.
La remontada: sobrevivir al Chrome que te descarta la pestaña
Chrome tiene dos formas de ahorrar recursos con pestañas en segundo plano, y las dos duelen diferente.
El primero es el congelamiento: las colas de tareas se detienen y el WebSocket queda a medio abrir. readyState jura que está OPEN, pero la conexión se murió del otro lado. Es el jugador parado en la cancha, fuera de la jugada.
El segundo es peor. Descartado: Chrome borra la página entera de la memoria y vuelve desde cero, como si fuera la primera visita.
GitCraque cubre los tres caminos de regreso: visibilitychange, resume y pageshow con persisted. Y la snapshot del estado de la vista se toma cuando la pestaña se esconde, nunca al salir. ¿Por qué? Porque beforeunload y unload simplemente no se disparan cuando el navegador descarta la pestaña. Programas el handler, confías en él, y nunca corre. Lo descubrí de la manera divertida.
También hay un root boundary para contener renders que explotan, y el recargue automático tiene presupuesto. Un bucle de recarga es peor que una pantalla rota: ni siquiera puedes abrir las devtools para investigar.
Rodilla reconstruida, artillería de la Copa. It works.
Lo que aprendí probando todo con TDD
Me preguntaron cuál fue la parte difícil. ¿Honestamente? Ninguna fue muy complicada, y la razón es aburrida de simple: fui probando con calidad todo lo que hacía. TDD de principio a fin, más tests end‑to‑end por encima. Hoy npm test corre 472 tests: 319 de server, 51 del grafo, 20 de drag‑and‑drop, 82 del viewer. Y todavía hay 39 checks e2e fuera de ese montón.
Hay un trade‑off real acá, y lo aprendí a las malas: ejecutá un solo comando de test cada vez. La suite del grafo mide proporción de tiempo real (wall‑clock), así que si la corrés junto a otro proceso pesado te cobra una falta que nunca ocurrió. Y acá no hay VAR.
La seguridad fue decisión de diseño, no un detalle: el servidor escucha solo en 127.0.0.1, rechaza requests con Host/Origin externo, y nunca, jamás, debe exponerse a la red.
Y seamos claros: el proyecto está creciendo. No está terminado, va mejorando con el tiempo. Como el Fenómeno después de la rodilla, de hecho.
Probalo y contame qué se rompió
Basta de leer. Entrá a un proyecto tuyo y ejecutá:
npx gitcraque
npm install -g gitcraque
cd ~/code/project && gitcraque
La vista se abre sola en el navegador, ya parada en el repositorio. Sin instalar nada permanente, sin registro, sin tarjeta de crédito.
Una advertencia antes de que insultes al proyecto: npm i github:frederico-kluser/GitCraque no funciona. El paquete publicado ya trae la SPA compilada, y el build solo se ejecuta con npm pack. Desde el código fuente es clonar + npm run build.
¿Te gustó? Dale estrella al repo, abrí un issue, mandá un PR. O ni siquiera lo uses: inspirate y construí el tuyo. Y compartí este post en Slack o WhatsApp con esa persona que todavía paga un cliente Git cada año. Conocés a una.
Boots optional.