¿Qué hace Slack waterline?
Lee un espacio de trabajo de Slack como lo haría una persona si tuviera tiempo: cada canal, cada DM, cada respuesta en hilos, los mensajes fijados, los archivos adjuntos y las hojas de Google que la gente sigue enlazando. Luego registra exactamente lo que se leyó — y solo lo que se leyó — para que la siguiente ejecución continúe desde donde se detuvo esta.
La línea de flotación es la línea por debajo de la cual nada puede perderse. Todo en esta skill existe para mantener esa línea honesta.
La regla única
Un comprobante de lectura solo puede describir lo que fue ENTREGADO, nunca lo que EXISTE.
Obtener no es leer. Si se construyó una carga útil pero nadie la miró, no está leída, y registrarla como leída no pierde un mensaje temporalmente — lo pierde permanentemente, porque la siguiente ejecución no tiene nada más que encontrar.
Todo lo que sigue se deriva de eso.
Configuración desde cero
Si algo no está configurado — sin espacio de trabajo, sin token, sin ámbitos — ejecute primero el doctor y siga lo que diga. No adivine el estado.
cd skills/slack-waterline/scripts
node setup.mjs # o --workspace acme
Verifica Node, el espacio de trabajo, el token, cada ámbito y si se ha leído algo, luego se detiene en el primer bloqueo real e imprime el comando o clic exacto que lo soluciona. Ejecútelo de nuevo después de cada arreglo. Guía completa en references/setup.md.
La secuencia normal para un espacio de trabajo nuevo:
node init.mjs --workspace acme --dir /path/to/acme-slack-archive
node setup.mjs --workspace acme --manifest --out slack-app.yaml
# el usuario crea la aplicación desde ese manifiesto y hace clic en Copiar en el token
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme # confirmar; luego descubrir + ponerse al día
El manifiesto importa: establece los doce ámbitos en un solo pegado en lugar de que el usuario busque casillas de verificación, y los coloca bajo Ámbitos de Token de Usuario, que es el error que de otro modo produce un token que no puede ver ni un solo DM.
Una skill, muchos espacios de trabajo. El registro en ~/.slack-waterline/config.json solo contiene rutas; los tokens viven en el .env de cada espacio de trabajo. node init.mjs --list muestra lo que está registrado.
Nunca maneje el token usted mismo
Un token activo nunca debe ingresar a un terminal, un historial de shell o una transcripción de chat.
Solo tiene que viajar desde la página de Slack a un archivo, así que muévalo mediante el portapapeles:
node setup.mjs --workspace acme --token-from-clipboard
Eso lo valida, lo escribe, lo verifica contra Slack e imprime solo a qué equipo y usuario pertenece. Nunca acepte ni pase un token como argumento de línea de comandos, nunca le pida al usuario que lo pegue en el chat y nunca lo escriba usted mismo en un archivo.
Navegación en el navegador para la configuración
Cuando el usuario pida que lo guíen en Chrome, references/setup.md tiene los pasos con las llamadas de herramienta exactas. La forma:
- Genere el manifiesto primero, para tener el YAML antes de necesitarlo.
- Cargue las herramientas del navegador en una llamada
ToolSearch, luegotabs_context_mcpantes de crear una pestaña. - Navegue: crear aplicación desde el manifiesto → instalar → llegar a OAuth y Permisos.
- Lea la lista de ámbitos al usuario antes de hacer clic en Permitir. Es su espacio de trabajo y su consentimiento.
- Deténgase en el token. Pídales que hagan clic en Copiar, luego ejecute
setup.mjs --token-from-clipboard.
No lea el token de la página — no use read_page, get_page_text, find, javascript_tool ni capturas de pantalla dirigidas a ese campo, y no haga clic en Mostrar para revelarlo. Slack lo oculta por defecto; capturarlo escribiría una credencial activa en una transcripción que sobrevive a la sesión y es mucho más difícil de revocar que de filtrar. El paso 5 existe para que nunca tenga que hacerlo.
Nunca toque el Secreto del Cliente, el Secreto de Firma ni los Tokens a Nivel de Aplicación — esta skill no necesita ninguno de ellos. Si el usuario ha cerrado sesión, pídale que inicie sesión; nunca maneje un formulario de inicio de sesión.
Tokens
SLACK_USER_TOKEN (xoxp-) | SLACK_BOT_TOKEN (xoxb-) | |
|---|---|---|
| Canales públicos | todo lo que puedas ver, sin unirte | solo aquellos a los que se unió |
| Canales privados | todos en los que estés | solo donde /invite |
| DMs / DMs grupales | todos los tuyos | nunca |
| Hilos, fijados, marcadores, archivos | sí | sí |
El token de usuario es preferido siempre que esté presente — para leer es un superconjunto estricto. Un token de bot no puede ver ni un solo DM, por lo que para una herramienta de puesta al día personal es casi inútil por sí solo.
Ámbitos del token de usuario: channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read. Agregue chat:write solo para el DM de resumen y el envío de respuestas.
Consulte references/tokens-and-scopes.md para generar uno.
Dos puntos ciegos que no se pueden solucionar
Indíquelos claramente en lugar de prometer una cobertura que no existe:
- DMs entre otras dos personas. Necesita la API de Discovery de Slack — solo Enterprise Grid.
- Un canal privado en el que nadie con un token está. Un humano tiene que hacer
/invite.conversations.joinrechaza los canales privados de plano.
Poniéndose al día — la ruta normal
node catchup.mjs --workspace acme --unread
Eso ejecuta el triaje, obtiene todo lo procesable, renderiza cada conversación e imprime un índice de los deltas renderizados más un lote de registros preparado.
Luego los lee. catchup.mjs deliberadamente no registra nada.
- Lea cada
.mdrenderizado listado en el índice. Para un gran backlog, distribuya subagentes en lotes — cada uno informa solo hallazgos y nunca lleva texto de mensaje sin procesar de vuelta al contexto principal. - Elimine de
tmp/record-batch.jsoncualquier entrada cuyo delta no haya abierto. - Registre lo que leyó, con un resumen mientras la comprensión esté fresca:
node record.mjs --workspace acme --batch tmp/record-batch.json
node record.mjs --workspace acme --from tmp/<id>.json \
--digest "El alcance del sprint se definió el martes. Abierto: la PR de Olga necesita revisión."
El resumen es el sedimento. La marca de agua evita que la siguiente ejecución vuelva a obtener mensajes antiguos; el resumen evita que vuelva a derivar su significado.
- Demuestre que nada se esconde:
node audit.mjs --workspace acme
node coverage.mjs --workspace acme --enumerate-only
Limitando la lectura
node catchup.mjs --workspace acme --all # cada conversación
node catchup.mjs --workspace acme --since 7d # una ventana
node catchup.mjs --workspace acme --channels general,eng-backend
node catchup.mjs --workspace acme --dms-only
node catchup.mjs --workspace acme --all --full # primera pasada de archivo
Las capas
Capa A — discover.mjs escanea todo el espacio de trabajo e imprime una fila por conversación, nunca cuerpos de mensajes, para que un modelo aprenda la forma del espacio de trabajo en una sola lectura pequeña. Columnas: conteo nuevo, prioridad, acción.
Capa B — fetch.mjs → render.mjs abre lo que importa.
Lea una carga útil con render.mjs. Nunca use head, tail o jq — esos le dan un prefijo de un archivo JSON, que no es un prefijo de la conversación, y silenciosamente eliminan las respuestas de hilos.
render.mjs ordena su salida deliberadamente: resumen anterior, luego fijados (canónico, siempre obtenido independientemente de la marca de agua), luego marcadores, luego hojas de cálculo y documentos, luego los mensajes nuevos. Los documentos superan al chat — la decisión suele estar en la hoja de cálculo, y el canal son personas reaccionando a ella.
Estado de lectura — tres posiciones, un escritor cada una
state.json, indexado por ID de canal porque eso sobrevive a los cambios de nombre.
| campo | significa | único escritor |
|---|---|---|
watermarkTs | LEÍDO — el ts de nivel superior más nuevo entregado a un lector | record.mjs --from |
scannedTs | TRIAGE — el ts más nuevo que el triaje puntuó | discover.mjs, y solo en filas tranquilas |
pendingTs | SLACK — lo que Slack tenía en una fila que el triaje no pudo limpiar | discover.mjs |
readAt | que se entregó una carga útil real | record.mjs --from |
readAtSource | una salvedad que LIMITA esa afirmación | --read-source |
Si agrega un escritor: mueve scannedTs, o entrega contenido a un lector. Nunca ambos, nunca ninguno.
references/read-state.md explica por qué existe cada uno y qué quedó sin leer antes de que existiera. Léalo antes de cambiar algo en lib/state.mjs.
Los hilos son donde se esconden los mensajes
Una respuesta nunca mueve a su padre. Un canal cuya conversación activa completa ocurre dentro de un hilo más antiguo parece completamente tranquilo desde el nivel superior. Por eso state.json almacena replyCount + latestReplyTs por hilo, y por qué una fila puede leer incremental (respuestas de hilos) — revisar esa conversación visualmente no muestra nada nuevo; tienes que abrir el hilo.
La puerta de señal decide PRIORIDAD, nunca si algo está leído
Las puntuaciones ordenan la tabla. Nunca hacen que una fila esté resuelta. Un canal tranquilo que finalmente dice algo importante no debe omitirse porque obtuvo 1.
Dos puertas que fallan de manera diferente
Ejecute ambas, cada vez.
node audit.mjs --workspace acme # offline, instantáneo
node coverage.mjs --workspace acme --enumerate-only # red, segundos
Una puerta que itera el registro solo puede confirmar que el registro es autoconsistente.
audit.mjs recorre state.json — por lo tanto, una conversación que nadie enumeró nunca es invisible para él. No "limpia", invisible. coverage.mjs --enumerate-only pregunta la otra pregunta contra conversations.list con exclude_archived: false, que es donde viven los canales olvidados.
Un backlog no es un fallo de compilación. Estos salen con un conteo de hallazgos para que un script pueda bifurcar; una ejecución programada que se vuelve roja porque alguien publicó en Slack entrena a todos para ignorar el rojo.
Informes
node report.mjs --workspace acme --period day
node report.mjs --workspace acme --period week --send
Secciones: necesita respuesta, te preguntó pero ya reaccionaste, contexto y lo que este resumen no pudo ver. --send te lo envía por DM, fragmentado y enhebrado debajo del primer mensaje si es largo.
Un informe es una vista, no una lectura. report.mjs nunca toca state.json. Que se te informe de un mensaje en un resumen no es lo mismo que haber leído la conversación, y confundir ambos es cómo un backlog desaparece sin que nadie lo limpie. El informe lo dice en su propio pie de página.
Programación: assets/schedule-task.ps1 registra una Tarea Programada de Windows; assets/github-workflow.tmpl es el equivalente en la nube. Cualquier ejecución automatizada pasa --no-record al triaje — una máquina nunca debe avanzar una posición de lectura.
Escribir en Slack
send.mjs publica como tú, no como un bot. Dos barreras de seguridad que no son opcionales:
- Siempre
--dry-runprimero, y muestra al usuario el destino resuelto y el texto exacto antes de enviar. Nunca envíes por iniciativa propia. - Slack no tiene desenvío. Cada intento se registra en
sent.log.jsonlantes de la llamada a la API y nuevamente después.
node send.mjs --workspace acme --to '@Saba' --file draft.md --dry-run
node send.mjs --workspace acme --to '#general' --text '…' --thread 1785…
node send.mjs --workspace acme --to '@me' --text '…'
--update <ts> edita un mensaje en su lugar y requiere --reason, que va al registro; la simulación imprime el texto remoto actual sobre el reemplazo para que veas lo que estás sobrescribiendo. Un @Name ambiguo es rechazado, nunca adivinado.
Redactando una respuesta
Lea primero el hilo (fetch.mjs + render.mjs), redacte en un archivo, muéstrelo al usuario, luego envíe. Escriba los borradores en el directorio del espacio de trabajo, nunca en el repositorio de la skill.
Archivos adjuntos, hojas, documentos
node download-file.mjs F0ABC123 out/deck.pdf --workspace acme
node extract-docx.mjs out/bio.docx
node fetch-sheet.mjs 'https://docs.google.com/spreadsheets/d/…' --workspace acme
Un gid en un enlace no es un ámbito.
El fragmento #gid= registra qué pestaña estaba mostrando el navegador de alguien cuando copió la URL. fetch-sheet.mjs lee cada pestaña y simplemente ordena la enlazada primero. Limite con --tab deliberadamente, nunca por accidente.
Tanto download-file.mjs como fetch-sheet.mjs se niegan en voz alta cuando la respuesta es una página HTML en lugar de datos — Slack y Google devuelven páginas de inicio de sesión y error con HTTP 200, por lo que un lector ingenuo escribe una página de inicio de sesión en el disco con un nombre .pdf y lo llama éxito.
Archivado
node fetch.mjs --workspace acme --many ids.txt --out-dir raw/ --skip-existing
node archive.mjs --workspace acme
Markdown, no JSON — una persona que busque quién estuvo de acuerdo con qué no debería tener que analizar nada. Los mensajes mecánicos se eliminan y se cuentan. conversations/ se borra y se reconstruye en cada ejecución, porque un cambio de nombre o un nuevo año calendario deja un archivo huérfano y grep encuentra ambas copias sin nada que indique cuál es la actual.
--skip-existing es reanudabilidad y es una trampa en una lista obsoleta: cada carga útil obsoleta ya está completa, por lo que la bandera hace que esa ejecución sea un no-ópera total. Úselo solo para la pasada de "nuevas conversaciones".
Convenciones para cualquier cosa que agregues aquí
- Stdout es la carga útil; los diagnósticos van a stderr.
fetch.mjsescribe JSON a stdout. Un registrador que comparta ese canal es un error esperando una carga. --out, nunca>. Una redirección de shell trunca el destino antes de que el script se ejecute, por lo que una obtención fallida destruye la carga útil que estaba reemplazando.- Escrituras atómicas. Archivo temporal, luego renombrar. Un manifiesto medio escrito afirma que se leyeron cosas.
- La prosa va a través de archivos.
--digest-file,--reason-file,--batch: PowerShell duplica una comilla donde bash no, y un archivo no tiene reglas de comillas en absoluto. - Rechazar la ambigüedad. Leer el DM de la persona equivocada no es recuperable.
Lo que esta skill no hace
- Publicar algo por iniciativa propia. Cada escritura se muestra primero.
- Avanzar una posición de lectura para contenido que nadie leyó.
- Marcar una conversación como resuelta porque obtuvo una puntuación baja.
- Afirmar cobertura de los dos puntos ciegos anteriores.
- Copiar secretos. Nunca mover una credencial, código de acceso o enlace de invitación privado fuera de Slack.