feedctl — локальный терминальный inbox для статей, постов и лент. Он забирает контент из RSS и публичных Telegram-каналов, хранит состояние локально в SQLite, а сами материалы сохраняет как обычные Markdown-файлы.
Главная идея: контент принадлежит вам. Конфигурация декларативная, runtime-состояние отдельно, Markdown можно читать, искать, версионировать и открывать любыми внешними инструментами.
- 🗂 Local-first inbox — база и Markdown живут локально, без серверной части.
- 📰 RSS-источники — классические фиды, статьи, блоги.
✈️ Публичные Telegram-каналы- 📝 Markdown-архив — каждый материал сохраняется в
~/.feedctl/content. - 🧾 YAML frontmatter — метаданные в Markdown, в TUI скрываются по умолчанию.
- 🔁 Версии материалов — изменение контента создаёт новую версию вместо потери истории.
- ⭐ Inbox workflow — read/unread, starred, archive, removed-source items.
- 🔎 Поиск и фильтр в TUI.
- 🎨 Nord-style TUI — полноэкранный терминальный интерфейс с Markdown preview/reader.
- 🤖 CLI-friendly output — важные команды поддерживают
--json, мутации —--dry-runи--yes.
# 1. Собрать бинарь
make build
# 2. Посмотреть эффективные пути
./feedctl config path
# 3. Добавить RSS-источник
./feedctl add rss https://example.com/feed.xml \
--id example \
--name Example \
--tags tech,blog \
--yes
# 4. Добавить публичный Telegram-канал
./feedctl add telegram @llm_under_hood \
--id tg-llm-under-hood \
--tags telegram,llm \
--max-items 50 \
--yes
# 5. Синхронизировать источники
./feedctl sync
# 6. Открыть TUI
./feedctlДля безопасной проверки без записи используйте --dry-run:
./feedctl add telegram https://t.me/llm_under_hood \
--max-items 50 \
--dry-run \
--json- Go
1.25+ - SQLite
makeдля удобных команд
Опционально доступен Nix dev shell:
nix developmake build
./feedctl --helpmake test
# или напрямую
CGO_ENABLED=0 go test ./...По умолчанию feedctl разделяет декларативную конфигурацию и runtime-состояние:
| Что | Путь |
|---|---|
| Основной config | ~/.config/feedctl/config.toml |
| Source-файлы | ~/.config/feedctl/sources.d/<source-id>.toml |
| Runtime root | ~/.feedctl |
| SQLite database | ~/.feedctl/feedctl.db |
| Markdown content | ~/.feedctl/content |
| Старые версии Markdown | ~/.feedctl/versions |
| Временные файлы | ~/.feedctl/tmp |
| Логи | ~/.feedctl/logs |
Пути можно переопределить переменными окружения:
FEEDCTL_CONFIG_DIR=/path/to/config
FEEDCTL_CONFIG_FILE=/path/to/config.toml
FEEDCTL_DATA_ROOT=/path/to/dataРедактор и браузер берутся из окружения:
EDITOR=nvim
BROWSER=xdg-open~/.config/feedctl/config.toml может выглядеть так:
[data]
root = "~/.feedctl"
database = "~/.feedctl/feedctl.db"
content_dir = "~/.feedctl/content"
versions_dir = "~/.feedctl/versions"
[sources]
dir = "~/.config/feedctl/sources.d"
[sync]
default_interval = "5m"
concurrency = 4
sync_on_startup = true
[tui]
editor = "nvim"
browser = "xdg-open"
show_removed_sources = false
[markdown]
frontmatter = true
path_template = "{source_id}/{year}/{month}/{slug}.md"Поддерживаемые токены path_template:
{source_id}{year}{month}{day}{slug}{item_id}{item_id_short}
Каждый источник описывается отдельным TOML-файлом в sources.d.
id = "example"
type = "rss"
name = "Example"
url = "https://example.com/feed.xml"
enabled = true
interval = "10m"
tags = ["tech", "blog"]id = "tg-llm-under-hood"
type = "telegram"
name = "LLM под капотом"
url = "https://t.me/s/llm_under_hood"
enabled = true
interval = "10m"
tags = ["telegram", "llm"]
max_items = 50source id должен быть безопасен для файлов и CLI: начинаться с латинской буквы или цифры и содержать только a-z, 0-9, _, -.
TOML-конфиги — только декларативные. Runtime-поля хранятся в SQLite и не должны попадать в config:
last_sync_at,last_error,etag,last_modified,cursor- read/unread state
- hashes, versions
- item counts
- disk usage
Проверить конфигурацию:
./feedctl config validate
./feedctl config validate --jsonRSS-источник добавляется командой:
./feedctl add rss https://example.com/feed.xml \
--id example \
--name Example \
--tags tech,blog \
--yesЧто происходит:
feedctlзагружает feed metadata.- Генерирует или принимает
source id. - Создаёт TOML-файл в
sources.d. - При
syncсохраняет новые материалы как Markdown.
Telegram-источник добавляется по username или URL:
./feedctl add telegram @channel --yes
./feedctl add telegram https://t.me/channel --max-items 50 --yes
./feedctl add telegram https://t.me/s/channel --dry-run --jsonПоддерживаются публичные каналы, доступные через web-view:
https://t.me/s/<channel>
Ограничения Telegram MVP:
- приватные каналы не поддерживаются;
- логин, phone auth, 2FA, MTProto и Bot API не используются;
- session-файлы и Telegram credentials не нужны;
- медиафайлы не скачиваются локально;
- Telegram HTML может измениться, поэтому parser intentionally scoped to public web pages.
Идентичность поста стабильна и строится как:
<channel>/<message_id>
Например:
llm_under_hood/831
Каждый item сохраняется как Markdown. При включённом frontmatter файл содержит служебные поля:
---
id: "..."
source_id: "tg-llm-under-hood"
source_name: "LLM под капотом"
source_type: "telegram"
title: "..."
url: "https://t.me/llm_under_hood/831"
canonical_url: "https://t.me/llm_under_hood/831"
published_at: "..."
fetched_at: "..."
content_hash: "sha256:..."
version: 1
tags: ["telegram", "llm"]
---
# Заголовок
Текст материала...Если источник меняет уже сохранённый материал, feedctl:
- считает новый content hash;
- сохраняет предыдущую версию в
~/.feedctl/versions; - обновляет текущий Markdown;
- увеличивает номер версии.
Provider metrics, например Habr views/comments/bookmarks/votes, считаются runtime metadata и не входят в Markdown hash и versioning.
Запуск:
./feedctl
# или явно
./feedctl tuiTUI открывается fullscreen, использует Nord-палитру и вертикальный marker ┃ для выбранной строки.
| Группа | Клавиши |
|---|---|
| Навигация | j/k, arrows, h/l, g/G, Ctrl+d/u, Ctrl+f/b |
| Разделы | 1 Inbox, 2 Unread, 3 Starred, 4 Sources, 5 Removed Sources, 6 All Items |
| Переключение разделов | Tab, Shift+Tab |
| Поиск | /, затем n/N для next/previous |
| Live-filter | f начать фильтр, F очистить |
| Removed-source items | A показать/скрыть |
| Открыть reader | Enter или l |
| Multi-select | v начать visual-выделение; j/k или стрелки расширяют диапазон; Esc отменяет; Space batch read/unread; u batch unread |
| Read/unread | Space, u |
| Star | s |
| Archive | a |
| Открыть URL | o |
| Редактировать Markdown | e |
| Frontmatter в preview/reader | m показать/скрыть |
| Sync | r refresh текущего, R sync all |
| Help / выход | ?, Esc, q |
Reader и preview рендерят Markdown красиво через terminal renderer. YAML frontmatter скрыт по умолчанию, чтобы не мешать чтению; включается клавишей m.
./feedctl # открыть TUI
./feedctl tui # открыть TUI явно
./feedctl status # краткий статус inbox/storage/sync
./feedctl status --json./feedctl config path # эффективные пути
./feedctl config validate # проверить config/source-файлы
./feedctl config format --yes # отформатировать существующие TOML-файлы./feedctl sources list
./feedctl sources show ID
./feedctl sources test ID
./feedctl sources test ID --json
./feedctl sources enable ID --yes
./feedctl sources disable ID --yes
./feedctl sources remove ID --dry-run --json
./feedctl sources remove ID --yesУдаление source config не удаляет уже сохранённые items и Markdown. Такие материалы можно видеть через removed-source views.
./feedctl sync # синхронизировать все enabled sources
./feedctl sync --source ID # синхронизировать один source
./feedctl sync --json./feedctl items list
./feedctl items list --unread
./feedctl items list --removed-sources
./feedctl items list --json
./feedctl items open ITEM_ID
./feedctl items markdown ITEM_ID./feedctl storage
./feedctl storage --json
./feedctl storage reconcile
./feedctl storage reconcile --jsonДля скриптов используйте --json:
./feedctl sources test tg-llm-under-hood --json
./feedctl sync --source tg-llm-under-hood --json
./feedctl items list --unread --jsonДля безопасных изменений используйте --dry-run:
./feedctl add rss https://example.com/feed.xml --dry-run --json
./feedctl add telegram @channel --max-items 50 --dry-run --json
./feedctl sources remove old-source --dry-run --jsonДля non-interactive режима используйте --yes:
./feedctl sources disable noisy-source --yes~/.config/feedctl/
├── config.toml # декларативные настройки
└── sources.d/
├── habr.toml # source definitions
└── tg-llm-under-hood.toml
~/.feedctl/
├── feedctl.db # runtime state
├── content/ # текущие Markdown-файлы
├── versions/ # старые версии Markdown
├── tmp/
└── logs/
Декларативная часть отвечает на вопрос «что подключено и как синхронизировать». Runtime часть отвечает на вопрос «что уже найдено, прочитано, изменено и где лежит».
Проект следует TDD-процессу:
- сначала regression/unit/integration тест;
- убедиться, что он падает ожидаемо;
- минимальная реализация;
- targeted tests;
- refactor;
- полный прогон.
Полная проверка:
go test ./...Форматирование:
gofmt -w cmd internalOpenSpec specs:
openspec validate --specs --strictfeedctl сейчас — local-first MVP с фокусом на терминальный workflow, RSS, публичные Telegram-каналы и Markdown-архив. Приоритеты проекта: простота, воспроизводимость, локальные данные, понятные CLI-команды и TDD для всех изменений поведения.