- Rust 99.2%
- Shell 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Software libre: usar, estudiar, modificar y redistribuir, con la condición de que lo redistribuido siga con la misma licencia y su código. Las 225 dependencias son permisivas —MIT, Apache-2.0, ISC, BSD, Zlib, MPL-2.0, Unicode-3.0— así que ninguna choca. Lo revisé con `cargo metadata`; la única que aparece con GPL en el nombre es r-efi, que ofrece MIT o Apache-2.0 como alternativa y encima es de un target que no usamos. El paquete ahora lleva /usr/share/doc/taiga-tui/copyright con la nota y el texto completo, porque la AGPL no está en /usr/share/common-licenses como sí lo están la GPL y la Apache. Sube la patch y no la minor: no agrega ninguna capacidad, pero cambia lo que se distribuye. |
||
| scripts | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
taiga-tui
CLI + TUI en Rust (ratatui) para llevar tus historias de usuario y tareas de Taiga desde la terminal.
El paquete se llama taiga-tui y el binario taiga, que es lo que se tipea.
(taiga-cli sería el nombre obvio, pero ya está tomado en crates.io por otra
herramienta.) La carpeta del repo sigue siendo taiga-rust.
Compilar
cargo build --release
./target/release/taiga --help
Para tenerlo en el PATH:
cargo install --path . # queda en ~/.cargo/bin/taiga
Uso
Primero se guarda una sesión. Se piden usuario y contraseña, y solo se
persisten los tokens (nunca la contraseña) en
~/.config/taiga/config.json, con permisos 600:
taiga login
Después, sin argumentos abre la interfaz interactiva:
taiga
Subcomandos
| Comando | Qué hace |
|---|---|
taiga / taiga tui |
Interfaz interactiva: kanban, historias, tareas e issues |
login |
Guarda una sesión (-u <usuario> para no tipearlo) |
logout |
Borra los tokens guardados |
whoami |
Sesión activa y ruta del config |
projects |
Proyectos donde sos miembro |
stories (alias hu) |
Tus historias asignadas (--all incluye cerradas) |
tasks |
Tus tareas asignadas (--all incluye cerradas) |
notifications (alias notificaciones) |
Tus notificaciones sin leer (--all incluye las leídas) |
kanban [proyecto] |
Kanban por columna, en texto plano |
De dónde sale la URL del servidor
De mayor a menor prioridad:
| # | Fuente | Alcance |
|---|---|---|
| 1 | --url <URL> |
Esa invocación. Si va junto con login, queda guardada |
| 2 | TAIGA_URL |
Ese entorno. También queda guardada si hacés login |
| 3 | base_url del config |
Lo que dejó el último login |
| 4 | DEFAULT_BASE_URL |
https://api.taiga.io, la instancia pública |
En el TUI, la pantalla de login trae un campo Servidor editable, precargado con el valor vigente. Lo que quede ahí al ingresar es lo que se persiste.
taiga --url https://taiga.miempresa.com login # lo fija de ahí en más
TAIGA_URL=https://otro.taiga.com taiga # solo para este comando
Atajos del TUI
| Tecla | Acción |
|---|---|
1 2 3 4 / Tab |
Cambiar de pestaña |
↑ ↓ / k j |
Mover la selección |
← → / h l |
Cambiar de columna (kanban) |
g / G |
Ir al principio / al final |
p / P |
Proyecto siguiente / anterior |
o |
Alternar filtro abiertas / todas |
Enter |
Abrir el detalle |
s |
Cambiar el estado |
A |
Asignar (Espacio marca, Enter aplica) |
a |
Crear un issue (pestaña Issues) |
v |
Convertir un issue en historia (pestaña Issues) |
1…9 |
Abrir el adjunto n-ésimo (en el detalle) |
e c t |
Editar descripción / comentar / editar asunto (en el detalle) |
< > |
Mover la tarjeta de columna (kanban) |
f / F |
Abrir / limpiar los filtros del kanban |
n |
Ver las notificaciones (Espacio marca leída, Enter abre, a alterna sin leer / todas) |
r |
Refrescar desde el servidor |
L |
Cerrar sesión |
? |
Ayuda |
q / Esc |
Salir |
El detalle
Enter abre la HU o la tarea en un popup; z lo pasa a pantalla completa y
vuelve. ↑ ↓ scrollean, Espacio y PgDn de a página, g y G a los
extremos. Muestra:
- La descripción y los comentarios, renderizados como markdown en la
terminal: títulos, negrita, tablas con bordes, listas, tareas
[x]y código. - Los criterios de aceptación, que en Taiga no son un campo nativo sino un atributo personalizado del proyecto. Se muestran todos los que tengan valor.
- Observadores y todos los asignados, no solo el principal: Taiga
admite varios en
assigned_users.
El listado de la API no trae la descripción, así que el detalle se pide al abrirlo. Mientras llega, la pantalla ya muestra los metadatos y avisa que está cargando.
Cambiar el estado
s abre un selector con los estados del proyecto, marcando el actual y cuáles
cierran la historia. Funciona en las tres pestañas y también con el detalle
abierto; usa los estados de HU o de tarea según corresponda, y los del proyecto
de esa entidad, que no tiene por qué ser el que muestra el kanban.
En el kanban, < y > mueven la tarjeta seleccionada a la columna contigua.
El cursor la sigue.
Taiga hace bloqueo optimista por campo: cada petición manda la version
que teníamos y el servidor rechaza el cambio solo si ese mismo campo se
modificó mientras tanto. Si pasa, el TUI avisa y no aplica nada; se refresca
con r y se reintenta.
Las cuatro pestañas
Todas muestran el proyecto abierto y todas se filtran con f:
| Kanban | Las historias en columnas por estado |
| Historias | Las mismas, como lista |
| Tareas | Las tareas del proyecto |
| Issues | Los issues, con tipo y severidad —teñida en Critical e Important— |
Los filtros son independientes en cada pestaña, y se guardan así. No se comparten porque no se pueden: los estados de historias, tareas e issues son catálogos distintos y un id de uno no significa nada en otro. En la práctica sirve: podés tener el kanban por sprint y la lista de tareas por lo tuyo.
Cada entidad ofrece las dimensiones que soporta —las historias tienen épica, los issues tipo, severidad y prioridad, las tareas ninguna de las dos— y en las listas de personas tu usuario aparece primero, justo después de «sin asignar», que es lo que más se filtra.
Issues
Enter abre el detalle igual que una historia, y s cambia el estado.
v convierte el issue en historia de usuario. Pide confirmación porque
escribe en el proyecto. Taiga copia el asunto y la descripción, y deja el
issue abierto: si corresponde cerrarlo, hay que hacerlo a mano.
Asignar
A abre el selector de asignados sobre lo que esté seleccionado, o sobre la
entidad del detalle si está abierto —igual que s con el estado—. Espacio
marca, Enter aplica, Esc descarta. Sin nadie marcado queda sin asignar.
Las historias admiten varios asignados y las tareas y los issues uno solo, que es lo que permite cada campo en Taiga: donde va uno, marcar a alguien reemplaza al anterior en vez de sumarlo. En las historias se manda la lista completa, así que no se pierde un compañero sin querer al cambiar la asignación.
Tu usuario aparece primero, que asignarse algo es lo que más se hace; el resto
va por orden alfabético. Si alguien cambió la asignación mientras tanto, Taiga
rechaza la escritura y el aviso dice que refresques con r.
Crear un issue
a en la pestaña Issues abre un modal que pide el título y nada más. Con
Enter se crea en el proyecto abierto y queda seleccionado arriba de todo,
con su detalle abierto: ahí se le carga la descripción con e, se comenta con
c y se le cambia el estado con s. Un título vacío no crea nada.
El resto lo completa el servidor: estado, tipo, severidad y prioridad salen de los valores por defecto del proyecto, igual que en el formulario de la web — son los únicos campos que Taiga exige además del proyecto y el asunto.
Editar con tu editor
Desde el detalle, e abre la descripción en tu $EDITOR, c un buffer vacío
para comentar y t el asunto.
El editor no toma la terminal entera: corre adentro de una ventana flotante al 80%, con un encabezado que dice sobre qué estás escribiendo —«Comentario en Historia #1045»— y el asunto al lado como contexto. Por debajo hay un pseudo-terminal propio, así que el editor cree que esa ventana es toda su pantalla.
Guardás y salís como siempre (:wq, ^O+^X) y el cambio se aplica.
Mientras la ventana está abierta todas las teclas son del editor, incluido
Esc y Ctrl-C; la única excepción es Ctrl+Alt+Q, que mata el editor y
descarta, por si algo quedó colgado.
Si el pseudo-terminal no se puede abrir, se cae al modo de siempre: suspender el TUI y darle la terminal completa al editor.
Se respeta $VISUAL sobre $EDITOR, y si no hay ninguno cae a vi. El
comando puede llevar argumentos: EDITOR="code -w" funciona. El archivo
temporal es .md, así que el editor enciende el resaltado de markdown.
Guardar sin cambios cancela. Un comentario o un asunto vacíos también cancelan; una descripción vacía sí se guarda, porque vaciarla es una edición legítima.
Si mientras editabas alguien tocó lo mismo, el guardado falla por el bloqueo
optimista y tu texto no se pierde: queda en
/tmp/taiga/borrador-<entidad>-<ref>-<campo>.md y el mensaje dice la ruta.
Refrescás con r, volvés a editar y pegás.
Adjuntos
El detalle lista los adjuntos numerados. La tecla del número lo descarga en
/tmp/taiga y lo abre con el visor del sistema (xdg-open, o open en
macOS). Si el visor falla, el mensaje dice dónde quedó el archivo para abrirlo
a mano.
Notificaciones
n abre la campanita: lo mismo que notifica la web —te asignaron algo, te
mencionaron, comentaron una historia que seguís, te sumaron como observador o
a un proyecto—, con la fecha, quién lo hizo y de qué proyecto viene. Son del
usuario, no del proyecto abierto, así que la lista cruza todos.
Arranca mostrando solo las sin leer, que es lo que se va a mirar; a pasa
al historial completo y r recarga. La cabecera muestra un contador de
pendientes, que se pide al arrancar y al refrescar con r.
Espacio marca la seleccionada como leída. La fila se queda donde está —se
apaga y baja el contador— en vez de desaparecer: sacarla correría el cursor
abajo de los dedos. En la próxima carga ya no viene.
Enter abre lo que la notificación referencia en el mismo detalle de siempre,
con descripción, comentarios y adjuntos. La entidad se pide por id, así que
funciona aunque sea de otro proyecto y no esté en ninguna lista cargada; el
detalle queda encima del panel y Esc vuelve. Las que no apuntan a nada —te
sumaron a un proyecto— y las épicas, que no tienen vista, avisan y no hacen
nada.
Cada dos minutos un hilo de fondo trae las pendientes y actualiza el contador solo. No aparece ningún cartel: la única señal es que el número cambie, y un aviso en el pie cuando llegó algo nuevo. El sondeo corre aparte del bucle de la interfaz a propósito —en el hilo principal un request sería un freeze sin explicación, peor que el cartel de cargando— y se saltea la tanda si hay algo en curso, para no pisar datos abajo de una escritura.
TAIGA_POLL_SECONDS cambia el intervalo; 0 apaga el sondeo:
TAIGA_POLL_SECONDS=600 taiga # cada diez minutos
TAIGA_POLL_SECONDS=0 taiga # sin sondeo
El mismo listado en texto plano:
taiga notifications # solo las sin leer
taiga notifications --all # el historial completo
Los filtros del kanban
El kanban muestra todas las historias del proyecto, no solo las propias:
es un tablero de equipo. Para verlo como en el kanban web, f abre un panel
con las mismas dimensiones —asignado a, estado, etiquetas, creada por, rol y
épica— cada opción con su cantidad.
← → cambian de dimensión, Espacio marca, i alterna entre incluir y
excluir, c limpia y Enter aplica; Esc descarta los cambios. El filtrado
lo resuelve el servidor, con la misma semántica que la web. Con filtros
activos la cabecera muestra un contador y el pie los detalla.
Cada proyecto recuerda sus propios filtros: los ids de estados y personas no valen en otro, así que al cambiar de proyecto se cargan los suyos.
Qué se recuerda entre sesiones
El proyecto abierto y los filtros de cada pestaña de cada proyecto se guardan en el config, así que la app vuelve a abrirse como la dejaste. Si el proyecto guardado ya no está, arranca en el primero sin protestar.
TAIGA_CONFIG apunta el config a otro archivo. Sirve para tener varias cuentas
—una del trabajo y otra de api.taiga.io, por ejemplo— y es lo que usan los
tests para no pisar el config real:
TAIGA_CONFIG=~/.config/taiga/personal.json taiga
Distribuir
scripts/empaquetar-deb.sh arma el .deb:
./scripts/empaquetar-deb.sh
Al terminar imprime la ruta del paquete y el comando para instalarlo. Se instala y se actualiza igual, sin desinstalar nada antes:
sudo apt install ./target/debian/taiga-tui_0.10.0_amd64.deb
Conviene apt install ./archivo.deb y no dpkg -i: apt resuelve las
dependencias y dpkg no. La sesión guardada en ~/.config/taiga no la toca
el paquete, así que sobrevive a la actualización.
No usa cargo-deb a propósito: la versión actual necesita rustc 1.88 y este
proyecto está clavado al 1.85 de Debian. Un .deb es un ar con tres
archivos adentro, así que el script lo arma con dpkg-deb, que ya viene
instalado.
Las dependencias no van escritas a mano: salen de los símbolos versionados del
binario, que es lo mismo que mira dpkg-shlibdeps. Hoy dan:
Depends: libc6 (>= 2.39), libgcc-s1 (>= 4.2)
Eso importa más de lo que parece. El binario compilado en Debian 13 no corre en Debian 12 ni en Ubuntu 22.04, que traen glibc más vieja. Con esa línea, apt se niega a instalarlo y lo dice; sin ella, se instala y falla recién al ejecutarse con un mensaje que no ayuda a nadie.
| Distro | glibc | ¿Anda? |
|---|---|---|
| Debian 13 trixie | 2.41 | sí |
| Ubuntu 24.04 | 2.39 | justo |
| Debian 12 bookworm | 2.36 | no |
| Ubuntu 22.04 | 2.35 | no |
Para que ande en cualquier lado haría falta un build estático con musl, que pide un toolchain que el rustc de apt no trae.
El binario solo se enlaza contra libc, libm y libgcc_s: usamos
rustls-tls en vez de native-tls, así que no arrastra OpenSSL.
Estructura
scripts/
empaquetar-deb.sh arma el paquete Debian
src/
main.rs CLI (clap) y salida en texto plano
config.rs sesión persistida en ~/.config/taiga
markdown.rs markdown a líneas de ratatui
editor.rs archivo temporal y elección del $EDITOR
pty.rs terminal embebida y traducción de teclas
poller.rs sondeo de notificaciones en segundo plano
api/
mod.rs cliente HTTP: login, refresh, endpoints
models.rs structs de la API de Taiga y estado de los filtros
ui/
mod.rs terminal, bucle de eventos y teclas
app.rs estado y navegación
draw.rs renderizado de las pantallas
tests.rs tests de render con TestBackend
Dos detalles de la API de Taiga que vale recordar:
- Las listas paginan de a 30. El cliente manda
x-disable-pagination: Truepara traer todo de una. - En los
*_extra_info, las entidades traennamey las personasfull_name_display/username. - La
descriptionsale solo del endpoint de detalle, nunca del listado. - Los comentarios viven en
/history/{tipo}/{id}, mezclados con los cambios de campos: son las entradas concommentno vacío. - Los criterios de aceptación son un atributo personalizado
(
/userstory-custom-attributes), con los valores en/userstories/custom-attributes-values/{id}, que responde 404 si la entidad nunca tuvo ninguno. - Los filtros aceptan varios valores separados por coma; repetir el
parámetro no funciona, se queda con el último.
nulles el literal para "sin asignar", y cada filtro tiene su varianteexclude_. - El
PATCHexigeversion(sin ella responde 400) pero el control es por campo: una versión vieja pasa si el atributo que tocamos no cambió, y da 400 conThe version doesn't match with the current onesi sí cambió. - En la nube de Taiga la API vive en
api.taiga.io.tree.taiga.iosirve la web y responde 405 a las peticiones de la API. - El campo
tasksde una historia viene vacío en el listado aunque tenga tareas; hay que pedirlas conGET /tasks?user_story={id}. - Los adjuntos no vienen en el listado: van por
GET /{tipo}/attachments?object_id={id}&project={p}. - Los issues traen
type,priorityyseveritycomo ids; los nombres salen de/issue-types,/prioritiesy/severities. - Promover es
POST /issues/{id}/promote_to_user_storyconproject_id(noproject), y devuelve las referencias de las historias creadas. Copia asunto y descripción, y no cierra el issue. - El editor embebido usa
portable-ptypara el pseudo-terminal ytui-termpara dibujar su pantalla; las teclas de crossterm se traducen a secuencias de terminal ensrc/pty.rs, respetando el modo de cursor que pida el editor. - Un comentario se agrega con el mismo
PATCHque el resto de los campos ({"comment": "...", "version": N}), no con un endpoint aparte. - El
refpuede venirnull: hay historias, tareas e issues reales sin referencia asignada. Se muestran como#s/r.
Licencia
AGPL-3.0-or-later. Software libre: podés usarlo, estudiarlo, modificarlo y redistribuirlo, y cualquier versión modificada que distribuyas tiene que venir con su código bajo la misma licencia.
Las 225 dependencias son permisivas —MIT, Apache-2.0, ISC, BSD, Zlib, MPL-2.0 y similares—, así que ninguna choca con la AGPL.
La cláusula de red de la AGPL (§13) obliga a ofrecer el código a quien use el programa a través de una red. Acá casi no se activa: esto es un cliente de terminal, no un servicio. Sí aplicaría si alguien lo expusiera por SSH o web para que terceros lo usen.
Cuando algo falla
No hay archivo de log. Los errores salen en el pie de la pantalla, y si lo que
falla es decodificar una respuesta el mensaje incluye el trozo de JSON donde
se cortó y guarda el cuerpo completo en /tmp/taiga/error-<endpoint>.json
para poder mirarlo con calma:
✖ no se pudo decodificar la respuesta de issues: invalid type: null, expected i64
cerca de: …"subject":"[REQ] Creación de adaptador","ref":null,…
respuesta completa en /tmp/taiga/error-issues.json
Tests
cargo test
Los tests dibujan cada pantalla en el TestBackend de ratatui y verifican el
contenido, más la lógica de navegación y filtros. No tocan la red.
Nota sobre el toolchain
Con rustc 1.85 (el de Debian trixie) hay dependencias transitivas que piden
un MSRV mayor; el Cargo.lock del repo tiene las versiones pinneadas que
compilan. Con un toolchain más nuevo vía rustup se puede correr
cargo update sin restricciones.