@forlizzi/cli
v0.5.0
Published
El CLI de Forlizzi: tickets, proyectos y avisos desde una terminal.
Readme
@forlizzi/cli
fzz, el CLI de Forlizzi: tickets, proyectos, publicaciones, archivos y avisos desde una terminal.
Le pega a las mismas rutas que la pantalla, con un token personal de la persona: su rol, su corte de visibilidad, y lo que quede escrito lo firma ella. No hay marca de "escrito por un agente".
Instalación
npm i -g @forlizzi/cliNo se actualiza solo. Una vez por día, y solo con una terminal del otro lado, fzz pregunta a npm por la última versión y avisa en stderr si hay una más nueva. Se actualiza con el mismo npm i -g @forlizzi/cli, y el aviso se apaga con FZZ_NO_UPDATE_CHECK=1.
Necesita Node 22 o más nuevo. fzz agent necesita además git y el harness (claude o codex) en el PATH.
En Windows hay dos pasos más, los dos una sola vez por usuario y sin consola de administrador. PowerShell trae bloqueados los scripts, y fzz entra por el fzz.ps1 que genera npm, así que sin esto contesta running scripts is disabled on this system:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUserY el instalador de Claude Code deja claude.exe en %USERPROFILE%\.local\bin sin sumarlo al PATH, así que fzz agent run no encuentra el harness. Se suma y se abre una consola nueva:
[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$HOME\.local\bin", "User")Las líneas de .fzz/agent.yml corren con la consola del sistema, que en Windows es cmd.exe: un setup o un verify escrito para bash no corre ahí.
Entrar
El token se emite en la pantalla, en Preferencias, Seguridad, Tokens personales. Crearlo pide el código de la app de autenticación, y el secreto se ve una sola vez.
fzz login --host tuespacio.forlizzi.dev --with-tokenEl token se pega por la entrada estándar. Queda en ~/.config/fzz/hosts.yaml, con permisos de solo
su dueño, y el primero que se guarda queda de host por defecto. Se puede tener uno por espacio.
En PowerShell, con el token copiado, Get-Clipboard | fzz login --host tuespacio.forlizzi.dev --with-token.
En una máquina de desarrollo, fzz login --host api-dev.forlizzi.dev --bypass le pide el token a la puerta de desarrollo: entra como el owner del espacio, o como la persona del equipo que nombre --as <email>. El token no lleva rol propio: para trabajar con otro rol se entra como alguien que lo tenga.
fzz auth status # qué hosts hay, con qué prefijo y si contestan
fzz auth whoami # quién es el dueño del token con el que se trabaja
fzz auth logout --host tuespacio.forlizzi.devSalir olvida el token, no lo revoca: eso se hace en la misma pantalla que lo creó.
FZZ_HOST y FZZ_TOKEN pisan lo guardado, para una máquina donde el archivo no tiene sentido.
Los comandos
fzz ticket list --project HIE # el tablero del proyecto
fzz ticket list --mine # los propios, de todos los proyectos
fzz ticket view HIE-12 --comments # el ticket entero con su hilo, con el id de cada comentario
fzz ticket create --project HIE --title "..." --body "..." --type bug
fzz ticket create --project HIE --title "..." --relates-to HIE-12 # nace relacionado con ese ticket
fzz ticket create --project HIE --title "..." --parent HIE-3 --blocked-by HIE-12 # en la épica HIE-3, esperando a HIE-12
fzz ticket suggested HIE-12 # los parecidos del espacio, los del mismo proyecto primero
fzz ticket relate HIE-12 HIE-30 --type blocks # relaciona los dos; sobre un par ya relacionado cambia el tipo y la dirección
fzz ticket unrelate HIE-12 HIE-30 # quita la relación del par, la haya guardado cualquiera de los dos
fzz ticket criteria HIE-12 # los criterios de aceptación, numerados
fzz ticket criterion HIE-12 2 --met --answer "Se ve en la portada" --evidence captura.png # contesta el criterio 2, con su evidencia
fzz ticket comment HIE-12 --body "..." # interno; --public lo deja en el carril del cliente
fzz ticket comment HIE-12 --body "..." --attach ./captura.png
fzz ticket comment HIE-12 --body "..." --quiz ./preguntas.json # preguntas que se contestan con un click
fzz ticket comment HIE-12 --body "..." --reply-to <id> # cuelga la respuesta de ese comentario
fzz ticket comment HIE-12 --body "para @[[email protected]]" # menciona: email exacto o parte única del nombre; @palabra suelto es texto
fzz ticket status HIE-12 in_progress
fzz ticket close HIE-12 # hecho: a resuelto, pasando por los estados que el flow pida
fzz ticket discard HIE-12 # al descarte del flow
fzz ticket assign HIE-12 [email protected]
fzz ticket assign HIE-12 agent # al agente vinculado al proyecto; corta si hay cero o más de uno
fzz ticket edit HIE-12 --title "..." # el título, cuando el que tiene no dice qué se pide
fzz ticket edit HIE-12 --body-file d.md # la descripción entera, en markdown; --body la toma inline
fzz ticket label HIE-12 --add deployed # etiquetas; --remove la saca
fzz ticket release HIE-12 --add v1.5.0 # versiones del ticket; --remove la saca
fzz ticket watch HIE-12 # seguirlo; con una persona al final, sumarla a ella
fzz ticket unwatch HIE-12
fzz project list
fzz project view HIE
fzz project recipe HIE # la receta de entrega, propia o de fábrica
fzz project recipe HIE --file receta.json # la carga, como lead; --factory vuelve a la de fábrica
fzz release list --project HIE # el catálogo de Versiones del proyecto
fzz release show v1.4.0 --project HIE # qué tickets entraron en esa versión
fzz release new v1.5.0 --project HIE # abre un borrador
fzz release edit v1.5.0 --status archived # renombrar con --rename, archivar con --status
fzz ticket list --project HIE --release v1.4.0
fzz agent init # crea en el repo .fzz/AGENT.md, .fzz/AGENT.local.md y .fzz/agent.yml, sin pisar nada
fzz shot /projects/HIE --role member # levanta la app de este checkout con la receta shot, entra y saca una captura
fzz agent register --name "Agente de Ana" --bypass-permissions # el agente de esta máquina
fzz agent link --project HIE # lo vincula al proyecto, con el repo del directorio actual
fzz agent run # reclama tickets, trabaja cada uno en su worktree y atiende las órdenes de la ficha (con --once, no)
pass show claude/key | fzz agent account add --name "La de Ana" --harness claude_code --managed # secreto a la plataforma
fzz agent account list # las cuentas que el agente puede usar
fzz agent account edit <id> --weight 40 --models opus,sonnet # el peso y los modelos de una cuenta
fzz wall list --project HIE
fzz wall post --project HIE --body "..."
fzz wall delete <id> # la propia, o la de un agente si dirigís el proyecto
fzz file list --project HIE --ticket HIE-12
fzz file download <id> --out ./informe.pdf
fzz file upload ./captura.png --project HIE
fzz notifications list --unread
fzz notifications read <id>...
fzz chat channels # los canales y directos, con lo que hay sin leer
fzz chat history general # los últimos mensajes; --before <id> pagina hacia atrás
fzz chat send general --body "..." # --thread <id> contesta en el hilo, --reply-to <id> cita
fzz chat direct [email protected] # abre el directo; después se le escribe con send <id>
fzz chat search "factura de marzo" # en todos los canales que ves
fzz chat react <id> 👍 # --remove la saca; save y pin van igual
fzz chat project HIE # el chat interno de un proyecto; --channel <cliente> el del cliente
fzz chat project-send HIE --body "..." # interno salvo --channel <cliente>
fzz chat publish <id> # un mensaje del chat del proyecto, a Publicaciones
fzz client list # los clientes; --for-billing o --for-projects recortan
fzz client view acme # la ficha y cómo viene en los proyectos que ves
fzz client create --name "Acme SA" --cuit 30711111118 --doc-type 80 --iva 1
fzz client from-padron 30711111118 --account forlizzi # el alta con lo que informa el padrón
fzz client edit acme --email [email protected] # solo viaja lo que se nombra
fzz client note acme --body "..." # notes las lista, unnote <id> borra una
fzz client contacts acme # quién entra al portal; contact-add, contact-edit, contact-remove
fzz budget list --status sent # los presupuestos; --client <cliente> recorta
fzz budget view P0012R01
fzz budget create --file presupuesto.json # el contenido va en un JSON; edit <code> --file lo reemplaza
fzz budget status P0012R01 approved # draft, sent, approved, rejected
fzz budget pdf P0012R01 --out ./ # baja el PDF; --render lo rehace antes
fzz budget draft --body "..." --wait # el borrador que redacta la IA; adjust <code> lo corrige
fzz invoice list --account forlizzi --limit 20 # los comprobantes; --status, --client y --all
fzz invoice view 0001-00000012 # por número (también 1-12), o por id si es un borrador
fzz invoice create --client acme --account forlizzi --item '{"description":"Abono","quantity":1,"unit_price":1000,"iva_id":5}'
fzz invoice pay 1-12 --amount 1210 --date 05/10/2026 # un cobro; unpay <numero> <pago> lo borra
fzz invoice notes 1-12 --body "..." # la nota interna, que no se imprime
fzz invoice sent 1-12 # la marca de enviado, sin mandar nada
fzz invoice pdf 1-12 --out ./ # baja el PDF; --render lo rehace antes
fzz invoice request --account forlizzi --body "..." --wait # la IA deja borradores, nunca emite
fzz foreign-invoice list # las facturas del exterior, los borradores primero
fzz foreign-invoice view EINV-2026-15
fzz foreign-invoice create --client globex --account forlizzi --currency EUR --item '{"description":"Consulting","quantity":1,"unit_price":900}'
fzz foreign-invoice pay EINV-2026-15 --amount 900
fzz foreign-invoice pdf EINV-2026-15 --out ./
fzz foreign-invoice import ./deel.pdf --account forlizzi --dry-run # primero en seco, después sin el flag
fzz ledger movements --period 2026-09 # Cuentas; --scope fran es el otro saldo, --from y --to un rango
fzz ledger add --file movimiento.json # edit <id> --file, confirm <id>, delete <id>
fzz ledger settle --file liquidacion.json
fzz ledger fees # los abonos de servidor; fee-add y fee-edit <id> con --file
fzz ledger recurring # los gastos fijos; recurring-add y recurring-edit <id> con --file
fzz member list --project HIE # el equipo; add <persona> [--role lead], role <persona> <rol>, remove <persona>
fzz project clients HIE # los clientes del proyecto; client-add, client-edit --visibility, client-remove
fzz project summary HIE --body "..." # el resumen; --public es el que lee el cliente
fzz project activity HIE # publicaciones y movimientos, de lo más nuevo a lo más viejo
fzz project tickets HIE --q "login" --status open # la búsqueda del server, que encuentra los resueltos viejos
fzz project trash # los tickets borrados; se recuperan con ticket restore
fzz env list --project HIE # los entornos; add <nombre>, edit <nombre>, remove <nombre>
fzz env export production --project HIE --pin 123456 --out .env.production # a un archivo, nunca a la salida
fzz var list --project HIE # las variables y en qué entornos tienen valor
fzz var add API_KEY --project HIE --value production=abc # secreta salvo --plain; shared=abc rige en todos
fzz var reveal API_KEY production --project HIE --pin 123456 # imprime el valor de una secreta
fzz var import .env staging --project HIE # vista previa; con --apply escribe
fzz ai-action list --project HIE # las acciones IA; add --file, edit <id> --file, remove <id>
fzz sla view --project HIE # el acuerdo de respuesta; set --file acuerdo.json lo reemplaza
fzz recurring list --project HIE # los tickets recurrentes; add --file, edit <id> --file, remove <id>
fzz view list --project HIE # las vistas guardadas de la Lista
fzz view add "Bugs abiertos" --project HIE --file filtros.json --shared
fzz ticket criterion-add HIE-12 --body "..." # criterion-edit <key> <n>, criterion-remove <key> <n>
fzz ticket criterion-review HIE-12 2 --approve # o --return --note "...": la revisión de quien valida
fzz ticket approvals HIE-12 # los pedidos de aprobación; approval-cancel <key> <id>
fzz ticket development HIE-12 # los pull requests y commits de GitHub que lo nombran
fzz ticket clone HIE-12 # una copia en el mismo proyecto
fzz ticket move-project HIE-12 OTR # a otro proyecto, con número nuevo
fzz ticket delete HIE-12 # a la papelera; restore <key> lo trae de vuelta
fzz ticket comment-edit <id> --body "..." # corrige un comentario propio; comment-delete <id> lo borra
fzz ticket comment-visibility <id> public # mueve el hilo entero de carril, sin avisar a nadie
fzz ticket react <id> 👍 # una reacción a un comentario; --remove la saca
fzz wall view <id> # una publicación entera
fzz wall edit <id> --body "..." # reescribe una propia; react <id> <emoji> reacciona
fzz wall status --project HIE --health on_track --body "..." # un parte de estado, como lead
fzz wall publish <id> --health on_track # publica el borrador de parte que dejó la IA
fzz wall apply <id> --item <id> # aplica ítems de una propuesta de la IA
fzz file edit <id> --rename informe-final.pdf
fzz file delete <id>
fzz notifications archive <id>... # los saca de la campana sin borrarlos; --undo los devuelveEl detalle de cada flag está en fzz <comando> --help.
fzz agent es el agente de esta máquina, que reemplaza al loop cuando tenga paridad. register pide el permiso agents.manage y link ser lead del proyecto, y los dos hablan con tu token. El agente tiene el suyo, que register guarda en agent.yaml, al lado de hosts.yaml, y que no se imprime nunca. run habla con ese token: reclama, pasa el ticket a en curso, crea la rama <KEY> en un worktree en .claude/worktrees/<KEY> del checkout (o bajo FZZ_AGENT_WORKTREES), lanza el harness sin terminal y comenta el resultado. Parado en la raíz del checkout, claude --resume lista esas sesiones y las retoma adentro de su worktree. El harness es Claude Code o Codex según --harness (claude_code por defecto, o codex). Codex corre con codex exec --json y pide una sesión de codex login o CODEX_API_KEY en el ambiente. Sin bypass, Claude Code corre acotado: lo que no permiten la lista de fábrica y permissions de .fzz/agent.yml se aprueba desde la sesión de la ficha, y sin respuesta en --approval-minutes (diez por defecto) el ticket queda bloqueado. El dueño prende el bypass con --bypass-permissions en register o en run, y Codex solo corre con él. El comando del harness se pisa con FZZ_HARNESS_CLAUDE_CODE o FZZ_HARNESS_CODEX, un arreglo JSON.
Con el ticket hecho, run entrega la rama con la receta del proyecto, la lista de pasos que escribe el lead (docs/fzz-agent.md, "La receta de entrega"). Lo que verificar significa en el repo sale de su .fzz/agent.yml, leído de la rama base: los comandos con nombre, el setup y los archivos copy del worktree nuevo, y las rutas protected que el diff no puede tocar. Un paso que falla por el trabajo vuelve a la sesión del agente con su salida, y al tercer intento el ticket queda en blocked. link --recipe <archivo.json> le pone al vínculo una receta propia, si el proyecto lo permite, y --no-recipe la saca. La del proyecto la carga su lead con fzz project recipe <KEY> --file <archivo.json>, y la de FZZ está en docs/implemented/FZZ-1031/recipe-fzz.json. open_pr y wait_ci hablan con gh, que se pisa con FZZ_GH_COMMAND, y wait_ci consulta cada minuto, o cada FZZ_CI_POLL_MS.
fzz agent account son las cuentas con que corre el harness. add registra una, y el secreto entra siempre por entrada estándar, nunca como opción: con --managed va cifrado a la plataforma, con --secret-stdin queda en agent.yaml de esta máquina, y --kind subscription dice que es un token de suscripción y no una API key. --public --project <KEY> la comparte con todo agente del proyecto, con el aviso de que un proveedor puede cortar una suscripción compartida. secret <id> guarda en la máquina el secreto de una cuenta local ya registrada. --weight y --models, en add y en edit <id>, dicen qué parte de las sesiones nuevas toma la cuenta y qué modelos corre: una sesión nueva la reparte la plataforma según el peso y los modelos de cada cuenta, y una de peso 0 no se usa. Cuando una cuenta llega a su límite, run la marca y sigue la misma sesión con la de mayor peso que quede, y sin cuentas registradas corre con la credencial que ya tenga la máquina.
Editar el catálogo de Versiones pide ser lead del proyecto, y leerlo no. La excepción es
fzz release publish, que pide ser miembro: es el que corre pnpm promote al etiquetar, cierra la
versión que salió y abre el borrador siguiente, y publicar es lo que hace un deploy y no una decisión
sobre el catálogo. El nombre de una versión no se inventa, se crea con release new.
--account nombra la cuenta emisora por su CUIT, por una parte única de su nombre o por su id. El mismo emisor suele existir dos veces, una por ambiente de AFIP, con el mismo nombre y el mismo CUIT: forlizzi@prod y forlizzi@homo los distinguen. Sin --account, los listados son de todas las cuentas, y crear un borrador o dictar un pedido sale con código 2.
Emitir un comprobante, la nota de crédito, la constatación, emitir o anular una factura del exterior y todo correo no tienen comando: el token no llega a esas rutas, y lo hace una persona desde la pantalla. La lista completa de lo que cubre fzz y de lo que queda afuera, con su porqué, está en docs/fzz-api-port/INVENTORY.md.
Tabla si hay terminal, JSON si no la hay o con --json. El cuerpo de un comentario, de una
descripción o de una publicación se escribe en markdown, y se pasa con --body, con
--body-file <path> o por la entrada estándar.
Nunca pregunta. Un campo que falta sale como un error con código 2 que nombra el flag que lo arregla: se corrige el comando, no se reintenta. El 1 es una operación que falló y el 0 es que salió bien.
Preguntar con --quiz
--quiz <archivo.json> agrega al final del comentario unas preguntas que el reporter contesta con
un click desde el ticket, en vez de escribiendo. --body sigue siendo obligatorio: la pregunta va
con el contexto que la explica.
El archivo es una lista de hasta ocho preguntas, cada una con su tipo:
[
{ "id": "q1", "kind": "single", "text": "En qué pantalla pasa?", "options": ["El tablero", "La lista"] },
{ "id": "q2", "kind": "multi", "text": "En qué navegador?", "options": ["Chrome", "Firefox"] },
{ "id": "q3", "kind": "yesno", "text": "Pasa siempre?" },
{ "id": "q4", "kind": "text", "text": "Qué dice la consola?" }
]single es una opción de la lista, multi son varias, yesno no lleva options y text es
respuesta libre. Un archivo que no valida sale con código 2 nombrando el campo, sin mandar nada.
El skill de Claude
El paquete trae un SKILL.md que le explica a Claude cuándo y cómo usar el CLI.
fzz skill installLo copia a ~/.claude/skills/fzz/SKILL.md. No pisa uno que ya esté sin --force.
Los crashes se reportan
Un error que no es ni un comando mal escrito ni una respuesta de error de la API es un bug de fzz,
y se manda a Sentry: el stack, la versión, la de node y el nombre del subcomando. Nunca los
argumentos, el token ni el contenido de un ticket.
Se apaga con una variable:
export FZZ_NO_TELEMETRY=1