Skip to content
ajramosPublic

About

Vim-style AI news reader for macOS that builds a local, Obsidian-compatible knowledge graph.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

117 Commits

Folders and files

Repository files navigation

GizNews

Un lector de noticias de IA que se maneja como vim y construye tu knowledge graph sobre la marcha. Desktop nativo (macOS) que agrega, clasifica, digiere y conecta las noticias de IA, y escribe el conocimiento como notas Zettelkasten en un vault compatible con Obsidian.

Lo que hace

  • Agrega noticias de RSS/Atom, Hacker News, arXiv y newsletters de Gmail, agrupando en una historia lo que varios medios cuentan a la vez — cuántos la cubrieron es la señal de importancia más fuerte del feed.
  • Clasifica con reglas deterministas ⚡ (instantáneas) + LLM en batch: categoría, importancia (0-3), tags, entidades y resumen por artículo.
  • Digiere: un digest diario por temas con overview escrito por la IA.
  • Construye el knowledge graph: cada artículo destacado se convierte en una nota Atom, los conceptos recurrentes en Electrons, y las síntesis en Molecules, con [[wikilinks]] y frontmatter — listo para Obsidian.
  • Busca semánticamente: embeddings locales (Ollama) + FTS5 fusionados con RRF, 100% sin servicios en la nube.
  • Extrae el contenido completo de cada artículo (readability → markdown) en batch durante fetch, cacheado para lectura instantánea.

Interfaz

App desktop nativa (Go + Wails + React), keyboard-first, con la gramática de giztui:

  • j/k navegar (con conteo: 5j) · el artículo se carga solo al pasar.
  • y resumen IA · a archivar (con deshacer) · m destacar · t leído.
  • s búsqueda · g grafo · d digest · : command palette · ? ayuda.
  • v selección múltiple · u/r/x/* vistas por estado · q salir.
  • Pickers keyboard-first para fuentes (:sources), conceptos (:concepts — promover uno pendiente con Enter, fundir dos grafías con m) y temas.
  • 5 temas (GizNews Dark, Dracula, Slate Blue, Nord, Light).

Quick start

Requisitos: Go 1.25+, Node 20+, Wails CLI, y Ollama con un modelo (gemma4:12b-mlx o similar) y nomic-embed-text.

# 1. Inicializa config, DB y el vault de conocimiento
go run ./cmd/giznews init

# 2. Añade fuentes (RSS, HN, arXiv, gmail)
go run ./cmd/giznews sources add --name "HN RSS" --url https://news.ycombinator.com/rss
go run ./cmd/giznews sources add --name "DeepMind" --url https://deepmind.google/blog/rss.xml --group labs

# 3. Pipeline completo
go run ./cmd/giznews fetch      # + extrae cuerpos en batch, agrupa historias
go run ./cmd/giznews rules import docs/rules/noise.json       # prefiltro: mata el ruido
go run ./cmd/giznews rules import docs/rules/high-value.json  # y marca lo que va a ★3
go run ./cmd/giznews classify --dry-run  # qué reclama cada regla, sin clasificar nada
go run ./cmd/giznews classify   # reglas ⚡ + LLM
go run ./cmd/giznews learn --dry-run     # qué dice tu forma de leer, sin guardar nada
go run ./cmd/giznews learn               # lo guarda: ajusta importancia de ahí en adelante
go run ./cmd/giznews rules suggest       # reglas propuestas desde tu historial (llegan apagadas)
go run ./cmd/giznews rules add --name "watch: gpt-5" --query "gpt-?5" --notify  # avísame cuando salga
go run ./cmd/giznews kb build --dry-run  # qué haría, sin tocar el vault
go run ./cmd/giznews kb build   # genera atoms/electrons/molecules + Index.md en el vault
go run ./cmd/giznews kb themes          # reagrupa los temas (lo hace también el build)
go run ./cmd/giznews kb sync            # importa al grafo tus notas del vault
go run ./cmd/giznews kb concepts        # conceptos por menciones (electron | pending)
go run ./cmd/giznews kb merge gpt4 gpt-5  # funde dos conceptos y reescribe los enlaces
go run ./cmd/giznews digest     # digest diario
go run ./cmd/giznews digest --out ~/digest.html          # y sácalo a un fichero
go run ./cmd/giznews digest --date 2026-08-20 --out d.md # o exporta uno guardado

# 4. ¿Sirve de algo todo esto?
go run ./cmd/giznews report              # ¿la importancia predice lo que lees?
go run ./cmd/giznews report --since 30d --format md   # para pegarlo donde se discuta

# 5. Mantenimiento
go run ./cmd/giznews prune --dry-run   # qué se liberaría, sin borrar nada
go run ./cmd/giznews prune             # libera el espacio y compacta el fichero

# 6. Desatendido
go run ./cmd/giznews serve          # bucle: fetch → classify → kb → index → digest
go run ./cmd/giznews serve --once   # una pasada y sale (para cron)
./packaging/install-launchd.sh      # y que arranque solo (macOS; Linux: install-systemd.sh)

# 7. Búsqueda semántica y preguntas
go run ./cmd/giznews search "watermarking"
go run ./cmd/giznews ask "¿qué sé de sparse attention?"   # responde citando tus notas

# 7. App desktop
cd desktop && wails build && open build/bin/giznews.app

El vault se abre con Obsidian en ~/Documents/obsidian/chronicles-ai (configurable en ~/.config/giznews/config.json).

Que te avise

Todo lo demás aquí es pull: la noticia está ahí cuando abres, llegara hace un minuto o hace tres días. Para las cuatro cosas que de verdad estás esperando, esa es la forma equivocada.

Una vigilancia es una regla con la acción notify, así que se escribe, se prueba (rules test) y se versiona como cualquier otra. No reclama el artículo ni le toca la importancia: solo pide que te lo cuenten, y compone con un boost sobre el mismo artículo.

giznews rules add --name "watch: gpt-5 ships" --query "gpt-?5" --notify
giznews rules test --rule "watch: gpt-5 ships"   # qué habría cazado hasta hoy

Se avisa una vez por artículo, para siempre: un re-fetch o un segundo classify no vuelven a anunciarlo. Y llega por tres caminos, así que no depende de tener la app abierta: notificación del sistema si está corriendo, un bloque ## Watchlist arriba de la nota del día en el vault, y una sección en el digest. :watch lista tus vigilancias y lo que han cazado.

Sacar el digest de la app

Un digest se lee en el móvil, con el café, lejos de la máquina que lo generó. giznews digest --out fichero lo escribe en markdown o en HTML autocontenido —estilos en línea, cero peticiones a la red, legible a ancho de móvil—, y --date exporta uno ya guardado en vez de generar otro. En la app, :digest export lo escribe y lo abre.

Exportar dos veces el mismo digest da los mismos bytes: así un re-export se distingue de un cambio. Y exportar un día para el que no hay digest falla en alto, no escribe un fichero vacío.

Enviarlo por correo (--send) existe pero está apagado salvo que digas a dónde: es lo único aquí que sale de tu máquina, y nunca ocurre solo.

Hay dos formas, y la primera no necesita servidor ni contraseña en el fichero de configuración:

"digest": { "gmail": { "to": ["tu@gmail.com"] } }   // usa tu login de Gmail
"digest": { "smtp":  { "host": "...", "to": [...] } }  // o un SMTP cualquiera

Con digest.gmail.to el envío va por la API de Gmail reutilizando el token que giznews ya usa para leer newsletters (el mismo de giztui, en ~/.config/giztui/token.json), que ya trae permiso de envío. Si tienes las dos, gana Gmail.

Recuperar espacio

Archivar es lógico —siempre puedes volver— y por eso el fichero solo crecía: cada artículo guardaba su cuerpo extraído, su embedding y su fila en el índice de búsqueda, para siempre.

giznews prune va de lo barato a lo destructivo: a los 180 días un artículo leído pierde el cuerpo (casi todo el espacio, casi nada del significado) y conserva su fila, su título y su clasificación; a los 365 se va entero. Luego VACUUM.

Nunca se toca, tenga la edad que tenga: lo marcado, lo que sigue sin leer y lo que tiene una nota en el vault. Y las copias de una misma historia se podan juntas: borrar la copia bajo la que está archivada dejaría huérfanas a las demás.

giznews prune --dry-run                      # cuántas y cuántos bytes, sin tocar nada
giznews prune --older-than 6m --delete-after 2y

Dejarlo funcionando solo

giznews serve mantiene el pipeline al día: cada etapa tiene su cadencia y el digest su hora del día (serve en config.json; una cadencia vacía apaga esa etapa). Una etapa que falla se registra y el bucle sigue: que el modelo esté caído no puede impedir que el feed siga trayendo noticias. Ctrl-C o SIGTERM salen entre dos etapas, nunca a mitad de una escritura.

Un solo proceso a la vez: el pipeline toma un lock (que caduca solo, así que un proceso muerto no bloquea nada) y los comandos manuales lo respetan.

"serve": {
  "fetch_every": "30m",
  "classify_every": "30m",
  "kb_every": "6h",
  "index_every": "12h",
  "digest_at": "08:00"      // hora local
}

Si prefieres cron o launchd, --once hace una pasada y sale:

# cron: cada media hora
*/30 * * * * /usr/local/bin/giznews serve --once >> ~/.local/state/giznews.log 2>&1
<!-- launchd: ~/Library/LaunchAgents/com.giznews.pipeline.plist -->
<dict>
  <key>Label</key><string>com.giznews.pipeline</string>
  <key>ProgramArguments</key>
  <array><string>/usr/local/bin/giznews</string><string>serve</string><string>--once</string></array>
  <key>StartInterval</key><integer>1800</integer>
</dict>

Preguntarle a tus notas

giznews ask "…" (y :ask en la app) responde con la prosa del modelo pero solo con lo que dicen tus notas, citando cada afirmación con [[slug]]. En la app cada cita es un botón que abre esa nota.

Dos reglas que son la razón de que se pueda confiar en la respuesta:

  • Toda cita se comprueba contra la base de datos. Una cita inventada se parece exactamente a una real, así que se le quitan los corchetes antes de que llegue a nadie y se informa de cuáles fueron. La promesa entera es poder seguir una afirmación hasta la nota de la que salió.
  • Si no hay nada, no responde. Sin notas relevantes, o sin modelo, devuelve el ranking y lo dice — nunca rellena el hueco con lo que el modelo sabe por su cuenta.

Arquitectura

internal/services/   ← lógica de negocio (fetch, classify, digest, kb, search, extract)
      ▲
pkg/desktop/         ← API + DTOs JSON (capa pura, unit-tested)
      ▲
desktop/             ← módulo Wails anidado (replace → ../) + frontend React/Vite
  • Backend en Go puro (sin CGO: modernc.org/sqlite), testable.
  • El frontend usa un mock backend para desarrollo y e2e (Playwright) sin Wails; el wire shape real de Wails (snake_case) se verifica aparte.
  • Ver docs/ARCHITECTURE.md para el detalle.

Desarrollo

make test        # go test ./...
make test-ui     # tsc + e2e (Playwright, vite dev + mock)
make build       # wails build → desktop/build/bin/GizNews.app
make install     # build + copia a /Applications

Licencia

MIT — ver LICENSE.

About

Vim-style AI news reader for macOS that builds a local, Obsidian-compatible knowledge graph.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages