Вы закрываете ноутбук в 23:00 и понимаете, что за день сделали... ну, что-то делали. В голове каша. Час долбились с багом в CI/CD, полтора часа совещание, еще полчаса пролистывали тикеты безрезультатно. А в ежедневном стендапе надо выдать внятный список.
Я перепробовал все: от ручного заполнения блокнота до pomodoro-трекеров. Но реально работающий метод нашелся только когда я заставил машину саму собирать за меня все цифры. На 18 июня 2026 года лучшая связка — ActivityWatch + пара сторонних апишек + собственный скрипт на Python 3.13. И сейчас я покажу, как собрать такую систему за один вечер.
Дисклеймер: я не люблю длинные теоретические вступления. Если вы хотите просто скопировать готовый код и забыть о ручных отчетах — листайте вниз, там лежит полный конфиг. Но если не прочитаете про «почему» — потом будете проклинать меня за баги в 3 часа ночи.
Проблема: вы не помните, что сделали
Каждое утро я просыпался и смотрел на пустой экран daily-отчета. «Вчера работал над деплоем» — это не отчет, это плевок в лицо здравому смыслу. К вечеру мозг перетирает 80% деталей. Вы помните, что фикс поставили, но сколько времени ушло на ту самую доску в Jira? Фиг.
А если вы фрилансер или работаете на фиксированный час — без точного трекинга вы просто дарите свои деньги заказчику. Потому что на стендапе вы скажете «потратил 2 часа на задачу», а на самом деле 3,5. И никто не узнает, кроме вас.
Ручные трекеры вроде Toggl или Tmetric работают, но требуют дисциплины. Я — DevOps, у меня дисциплина кончилась еще в час ночи, когда я патчил продакшен под Red Bull. Поэтому я хочу, чтобы система собирала данные сама, а я только один раз настроил фильтры.
И да, я знаю про RescueTime, он собирает топ аппов. Но его данные — черный ящик. Ты не знаешь, почему он посчитал Slack как «продуктивность», а терминал — как «нейтральное». Поэтому лучшая база — ActivityWatch с открытым исходным кодом и собственной логикой.
Решение: три источника + один скрипт
Нам нужно собрать данные из трех слоев:
- Активность за компьютером — ActivityWatch (окна, приложения, заголовки). Из него мы вытащим таймлайны событий.
- Целенаправленные трекеры — RescueTime API или Toggl API (я использую RescueTime для классификации «продуктивно/нет»).
- Контекстуальные сервисы — Google Calendar (встречи) и GitHub/GitLab (коммиты и PR).
Скрипт на Python забирает данные за прошедший день, собирает в единый JSON, генерирует Markdown и, если нужно, экспортирует в PDF. В конце — шлет в Telegram через бота. Весь код я выложу под спойлером.
Важно: все API ключи храните в .env. Никогда не коммитьте их в репозиторий, даже если репо приватный. Один случайный push в публичный форк — и ваш RescueTime аккаунт может уйти в спам.
В этой статье я не буду разбирать установку ActivityWatch — с ней справится даже стажер. Если у вас его нет, скачайте с официального сайта (последняя версия 0.13 на июнь 2026). Там же есть watcher для Linux, macOS, Windows. Я работаю на macOS, но скрипт платформенно-независимый.
Пошаговый план за 40 минут
1 Настройка ActivityWatch и экспорт данных через REST API
ActivityWatch поднимает локальный сервер на порту 5600. API — это радость. Заберите данные за период:
import requests
from datetime import datetime, timedelta
aw_url = "http://localhost:5600"
# Получаем список bucket'ов (категорий событий)
fetch_buckets = requests.get(f"{aw_url}/api/0/buckets").json()
# Нас интересует bucket с активными окнами
window_bucket = [b for b in fetch_buckets if 'window' in b][0]
# Получаем события за последние 24 часа
events = requests.get(f"{aw_url}/api/0/buckets/{window_bucket}/events?start={datetime.utcnow()-timedelta(days=1)}&end={datetime.utcnow()}").json()Обратите внимание: я использую UTC, а не локальное время. Потом скрипт сделает конвертацию. Иначе на границе дня будут сдвиги.
Вытаскиваем из событий: app имя, title окна, длительность. У ActivityWatch есть интересная фича — он считает не просто время между start и end, а вычитает периоды, когда вы отошли (экран заблокировался). Это ключевое отличие от сырых логов.
2 Забор данных из RescueTime и Google Calendar
RescueTime — платный, но у него есть бесплатный Daily Summary API. Выдает агрегированные данные по продуктивности (время на очень продуктивные, продуктивные, нейтральные, отвлекающие и очень отвлекающие категории).
import os
rt_key = os.getenv('RESCUETIME_API_KEY')
rt_url = f"https://www.rescuetime.com/anapi/daily_summary_feed?key={rt_key}&format=json"
rt_data = requests.get(rt_url).json()
# последний элемент — сегодняшний отчет (если день еще не закончился)Но есть нюанс: RescueTime обновляется с задержкой до 2 часов. Если вы генерируете сводку в 23:00, данные могут быть не полными. Я ставлю cron на 7 утра следующего дня.
Календарь Google — через Google Calendar API (нужен OAuth). Но для простоты можно использовать iCal ссылку и парсить через icalendar:
import icalendar, requests
ics = requests.get("https://calendar.google.com/calendar/ical/.../basic.ics").text
cal = icalendar.Calendar.from_ical(ics)Заберите события за вчера, отфильтруйте по времени. Отличный источник для заголовка «Встречи» в отчете.
Git-активность — через GitHub API. Получаем коммиты пользователя за день. Я обычно вытаскиваю первые строки сообщений коммитов и количество измененных файлов.
3 Генерация Markdown-отчета и превращение в PDF
Собираем все в одну структуру:
report = {
"date": yesterday.isoformat(),
"total_computer_time_hours": sum(events_durations),
"top_apps": [
{"name": "", "hours": 0}
],
"productive_vs_distracting": {
"productive": 0,
"distracting": 0
},
"meetings": [
{"title": "", "start": "", "end": ""}
],
"git_commits": [
{"repo": "", "message": ""}
]
}Формируем Markdown. Вот мой любимый шаблон:
# Сводка дня: {date}
## Общее время за компьютером
{total_hours} часов
## Топ приложений
{apps_table}
## Продуктивность (RescueTime)
Продуктивно: {productive} ч
Отвлекающе: {distracting} ч
## Встречи
{meetings_list}
## Коммиты
{commits_list}
PDF удобно генерировать через weasyprint или md2pdf. Я использую weasyprint — он берет HTML, а не Markdown, поэтому сначала конвертирую Markdown в HTML через markdown2.
from weasyprint import HTML
HTML(string=html_report).write_pdf('summary.pdf')Вуаля. Теперь у вас есть файл, который можно отправить себе в Telegram через бота (токен бота и chat_id берем из .env).
4 Запуск по крону и отправка в Telegram
Создаем Python-скрипт daily_summary.py. Прописываем cron (или launchd на macOS) на запуск каждый день в 7:00 утра:
# m h dom mon dow command
0 7 * * * cd /path/to/project && /usr/bin/python3 daily_summary.py >> /var/log/daily_summary.log 2>&1Telegram-отправка через python-telegram-bot (v20.x):
import asyncio
from telegram import Bot
async def send():
bot = Bot(token=os.environ['TELEGRAM_BOT_TOKEN'])
with open('summary.pdf', 'rb') as f:
await bot.send_document(chat_id=os.environ['TELEGRAM_CHAT_ID'], document=f)
asyncio.run(send())Нюансы и типичные ошибки
1. ActivityWatch не видит все окна. Если вы работаете в полноэкранных приложениях (например, игре), watcher может не зафиксировать активность. Решение: поставьте дополнительный watcher на заголовки окон — ''aw-watcher-window'' на Linux, он цепляет лучше. Или используйте ''aw-watcher-afk'' для отслеживания бездействия.
2. RescueTime API имеет лимит — 200 запросов в день. Для одного человека хватит, но не злоупотребляйте. Кешируйте ответы хотя бы на час.
3. Разница часовых поясов. Если ваш сервер живет по UTC, а вы работаете в MSK (UTC+3), то запрос за «вчера» даст данные не за ваше вчера, а за смещенное. Я решаю это передачей параметра timezone_offset_hours в скрипт из переменной окружения.
4. PDF может не сформироваться из-за шрифтов. WeasyPrint требует шрифты, установленные в системе. У меня была проблема на macOS — пришлось доустанавливать fontconfig через brew и указывать путь к шрифтам.
5. Отсутствие асинхронности в cron. Не вызывайте asyncio.run() внутри cron скрипта, если в системе уже крутится event loop. Лучше сделать отдельный Python файл без async для простоты. Или используйте requests вместо httpx.
Кстати, если вам интересно, как я автоматизирую обход блокировок DNS для доступа к AI-сервисам без VPN — почитайте статью «Обход блокировок ChatGPT: автоматическое обновление DNS-правил через GitHub Actions без VPN». Там простой GitHub Actions, который вписывается в нашу философию «не делай руками то, что может сделать код».
Как не превратить систему в очередной трекер-заброшка
Самое трудное — не начать все это ненавидеть. Если вы каждый день будете получать отчет, который сообщает, что 4 часа вы сидели в Slack, а не писали код, — мотивация упадет к нулю. Я прошел через это.
Совет: сделайте отчет __полезным__. Добавьте в него не только «плохие» цифры (время в Minecraft), но и «хорошие» — количество закрытых задач, коммитов, мерджей. Пусть бот говорит: «Молодец, сегодня на 20% больше коммитов, чем в среднем за неделю». Я для этого добавляю вызов OpenAI API — прошу одним предложением оценить продуктивность дня. Это добавляет человеческий фидбек, который скрашивает сухие данные.
Кстати, если хотите собрать что-то похожее на персонального ассистента, обратите внимание на статью «Собираем локального Jarvis за вечер: ваш первый персональный ИИ-ассистент». Там описана архитектура, которая позволит вашей сводке не просто лежать в PDF, а инициировать действия — например, ставить задачи в Todoist по итогам дня.
Еще один нюанс: если вы используете несколько AI-инструментов для кода (Cursor, Windsurf), их история тоже может быть источником. Как разрулить конфликты между ними в одном репозитории — читайте в статье «AI-инструменты дерутся за ваш код: как остановить войну Cursor и Windsurf в одном репозитории». Там хитрый трюк с .gitignore и правилами для каждого assist.
Полный конфиг файла .env
# ActivityWatch (локальный, ключ не нужен)
AW_HOST=http://localhost:5600
# RescueTime
RESCUETIME_API_KEY=your_key_here
# Google Calendar iCal URL (публичная ссылка)
ICAL_URL=https://calendar.google.com/calendar/ical/.../basic.ics
# GitHub Token (Personal Access Token)
GITHUB_TOKEN=ghp_...
GITHUB_USER=yourname
# Telegram
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
TELEGRAM_CHAT_ID=123456789
# Таймзона (смещение от UTC в часах)
TIMEZONE_OFFSET=3
# OpenRouter или любой LLM для комментария
OPENROUTER_API_KEY=sk-or-v1-...
Скрипт daily_summary.py целиком я не привожу, чтобы статья не стала километровой. Но его легко собрать из кусочков выше. Если нужна моя версия с обработкой ошибок, таймаутами и красивыми таблицами — пишите в комментариях, могу выложить.
Бонус: интеграция с onWatch
У меня есть еще один инструмент — onWatch, Go-бинарник, который следит за квотами AI-провайдеров (Anthropic, Synthetic, Z.ai) и присылает уведомления, когда лимит исчерпан. Я добавил его данные в сводку дня, чтобы видеть, сколько токенов я потратил на ассистентов. Подробнее — в статье «onWatch: Go-бинарник, который следит за квотами Anthropic, Synthetic и Z.ai, пока вы спите».
Туда же можно засунуть и данные из Context7 — если вы используете MCP для доступа к документации, запишите, к каким страницам обращались. Это даст контекст: «Весь день читал документацию по новому API». Статья «Context7 MCP: как подключить к Claude для работы с актуальной документацией (пошаговый гайд)» поможет интегрировать этот источник.
В итоге за месяц у вас накопится база данных о собственной работе. Вы сможете делать аналитику: в какие дни вы максимально продуктивны, сколько времени уходит на встречи, какие приложения едят ваше время. И главное — перестанете врать себе про «я работал весь день».
А теперь — давайте перейдем к полному конфигурационному файлу, который я обещал.
Конфигурация скрипта (config.yaml)
# config.yaml
calendar:
ical_url: "${ICAL_URL}"
max_events: 10
activity_watch:
host: "${AW_HOST}"
bucket: "aw-watcher-window"
timezone_offset: ${TIMEZONE_OFFSET}
rescuetime:
api_key: "${RESCUETIME_API_KEY}"
enable: true
github:
token: "${GITHUB_TOKEN}"
user: "${GITHUB_USER}"
repos: ["my-repo", "work-repo"]
telegram:
bot_token: "${TELEGRAM_BOT_TOKEN}"
chat_id: "${TELEGRAM_CHAT_ID}"
output:
format: "pdf" # или "markdown"
directory: "./reports"
Скрипт читает этот YAML, подставляет переменные окружения и генерирует отчет. Никаких жестко закодированных путей. Если хотите адаптировать под себя — просто меняйте YAML.
На этом все. Надеюсь, мой опыт поможет вам перестать гадать, что вы сделали за день. Если остались вопросы — пишите. Я отвечаю даже на глупые.