Мы живем в новом дивном мире, и прототипирование стало увлекательным занятием.
Делиться

Меня очень удивило, как быстро отдельные разработчики теперь могут выпускать реальные и полезные прототипы.
Такие инструменты, как Claude Code, Google AntiGravity и растущая экосистема вокруг них, перешагнули новый порог: вы можете изучать проекты других пользователей в интернете и понимать, насколько быстро вы можете создавать свои собственные уже сегодня.
В последние недели я начал выделять от одного до двух часов в день исключительно на разработку с использованием моего стека технологий, ориентированного на искусственный интеллект:
- Google Антигравитация
- Google Gemini Pro
- Доступ к моделям Клода осуществляется через AntiGravity.
Эта система коренным образом изменила мое представление о прототипировании, скорости итераций и о том, на что способен «персональный ИИ-агент» сегодня. Что еще важнее, она вернула меня к практическому программированию и разработке, чем я лично пожертвовал после того, как моя роль в DareData сместилась с выполнения задач на управление и координацию.
Лично для меня эта революция стала настоящим благословением, ведь я всегда был обречен на руководящие должности. Она устраняет компромисс, с которым я смирился: развитие компании означало полный отказ от строительства. Мне больше не нужно выбирать между строительством и управлением, они, по сути, дополняют друг друга.
Однако здесь есть более широкие последствия для тех, кто «просто» занимается разработкой. Если ИИ-агенты будут все больше заниматься выполнением задач, то одной лишь реализации будет недостаточно. Разработчиков (хотят они этого или нет) будут подталкивать к координации, принятию решений и… управлению — тому, что отдельные специалисты ненавидят всей душой. Другими словами, управленческие навыки становятся частью технического стека, а ИИ-агенты — частью контекста, которым они управляют.
Больше всего меня удивило, насколько полезными оказались мои существующие управленческие навыки:
- Направлять агента, а не заниматься его микроменеджментом.
- Спрашивать о результатах, а не об инструкциях.
- Составление карты, определение приоритетов и выявление спорных моментов.
На практике я управляю и координирую работу виртуального сотрудника. Я могу оказывать глубокое влияние на одни аспекты его работы, оставаясь при этом практически в неведении относительно других — и это не ошибка, а большая особенность. Например, в моем личном ИИ-помощнике я могу четко понимать бэкэнд, но при этом практически ничего не знаю о фронтенде. Система по-прежнему работает, потому что моя роль заключается не в том, чтобы знать все, а в том, чтобы направлять систему в нужное русло.
Это напрямую аналогично тому, как я координирую работу людей внутри компании. По мере роста DareData мы не нанимаем копий основателей. Мы намеренно нанимаем людей, которые умеют делать то, чего мы сами не умеем, и со временем — то, чему мы никогда не научимся достаточно глубоко, чтобы делать это хорошо.

Хватит самоанализа по поводу управления. Давайте посмотрим, что я создаю, потому что именно для этого вы здесь:
- Персональный ИИ-помощник, разработанный с учетом моих реальных повседневных дел, а не универсальный шаблон повышения продуктивности. Он адаптируется к тому, как я работаю, думаю и принимаю решения.
- Мобильное приложение, которое рекомендует один музыкальный альбом в неделю, без традиционной системы рекомендаций. Отсутствие чувства комфорта помогает мне расширить свой кругозор в плане прослушивания музыки.
- Мобильная игра, в которой один персонаж проходит через многоуровневые подземелья, разработанная в первую очередь как творческая площадка, а не как коммерческий продукт.
Интересно то, что, хотя я уверенно владею кодом большей части бэкенда, разработка фронтенда — это не тот навык, которым я обладаю. И если бы меня заставили делать это самому, эти проекты затянулись бы с нескольких часов до нескольких дней, или просто никогда бы не были выпущены.
Это ограничение теперь в значительной степени неактуально. С этой новой архитектурой настоящим узким местом становится воображение. Цена «незнания» целого уровня рухнула.
Итак, в оставшейся части этого поста я расскажу о своем личном ИИ-помощнике: что он делает, как устроен и почему он мне подходит. Моя цель — сделать его открытым исходным кодом после стабилизации, чтобы другие могли адаптировать его к своим рабочим процессам. В настоящее время он очень специфичен для моей жизни, но эта специфичность преднамеренна, и сделать его более универсальным — часть эксперимента.
Познакомьтесь с Фернао
Фернан Лопес был летописцем португальской монархии. Я намеренно выбрал португальскую историческую личность — у португальцев есть привычка присваивать исторические имена почти всему. Если это звучит стереотипно по-португальски, то это сделано намеренно.
Фернао — португалец, но на самом деле говорит по-английски, можно назвать его современным летописцем XXI века.

На данном этапе я делаю две вещи, которых обычно избегаю: антропоморфизирую ИИ и поддаюсь очень португальскому инстинкту именования. Считайте это безобидным признаком моего старения.
Впрочем, что же на самом деле дает мне Фернао? Лучше всего начать с его главной страницы.

Фернао — симпатичный парень, который в данный момент выполняет пять задач:
- Расписание на день : я планирую свой день, собирая воедино календари, списки дел и цели, а затем преобразую их в нечто, чему я могу следовать.
- Помощник по написанию текстов : помогает мне проверять и дорабатывать черновики постов в блоге и других текстов.
- Portfolio Helper : предлагает компании или ETF для добавления в портфель в зависимости от потребностей в ребалансировке и макроэкономической ситуации (без попыток предсказать будущее).
- Финансовый организатор : извлекает данные о расходах из моих банковских выписок и загружает все в приложение Cashew, избавляя меня от еще одной задачи, которая отнимала у меня около 3-4 часов в месяц.
- Подписки и скидки : отслеживает все мои подписки и показывает скидки или преимущества, которые у меня, вероятно, есть, но которыми я никогда не помню воспользоваться.
В этом посте я сосредоточусь на приложении «Расписание на день».
В настоящий момент расписание Фернао включает в себя три простых пункта:
- Получает мой календарь, включая все запланированные встречи.
- Извлекает мои задачи из Microsoft To Do.
- Извлекает мои личные ключевые результаты из Notion.
Всё это связано через API. Идея проста: каждый день Фернао анализирует мои ограничения, приоритеты и обязательства, а затем генерирует для меня оптимальное расписание.

Создать расписание на фронтенде довольно просто (весь фронтенд написан на Vibe). Вот кнопка для создания расписания:

Как только я нажимаю кнопку «Создать расписание», Фернао начинает готовить в фоновом режиме:

Далее, по порядку, выполняются следующие шаги: получение данных из моего календаря, задач и Notion.
Следующий момент также является тем местом, где базовые навыки программирования начинают действительно иметь значение, не потому что весь приведенный ниже код не работает, а потому что вам нужно понимать, что происходит и где в конечном итоге могут возникнуть ошибки или потребуется улучшение.
Начнём с получения календаря. В данный момент это обрабатывается одной гигантской функцией, созданной Клодом, которая совершенно не оптимизирована .
def get_events_for_date(target_date=None): «»» Получает события на определенную дату из Google Календаря через ICS-каналы. Аргументы: target_date: объект datetime.date для целевого дня. Если None, используется сегодняшний день. Возвращает список словарей событий. «»» # Жестко заданные URL-адреса календаря (не используется переменная окружения во избежание проблем с заполнителями) CALENDAR_URLS = [ 'cal1url', 'call2url' ] LOCAL_TZ = os.getenv('TIMEZONE', 'Europe/Lisbon') # Получаем часовой пояс local = tz.gettz(LOCAL_TZ) # Если целевая дата не указана, используем сегодняшний день if target_date is None: target_date = datetime.now(local).date() # Создаем дату и время для границ целевого дня day_start = datetime.combine(target_date, datetime.min.time()).replace(tzinfo=local) day_end = day_start + timedelta(days=1) # Отладка: вывод диапазона проверяемых дат print(f»n[Отладка] Проверка календарей на дату: {target_date.strftime('%Y-%m-%d')}») print(f» Начало: {day_start.strftime('%Y-%m-%d %H:%M %Z')}») print(f» Конец: {day_end.strftime('%Y-%m-%d %H:%M %Z')}») print(f» Часовой пояс: {LOCAL_TZ}») all_events = [] # Получение данных из каждого календаря for idx, cal_url in enumerate(CALENDAR_URLS, 1): calendar_name = f»Календарь {idx}» print(f»n[Отладка] Получение {calendar_name}…») try: # Загрузка календаря с URL-адреса ICS с достаточным таймаутом r = requests.get(cal_url, timeout=30) r.raise_for_status() cal = Calendar(r.text) events_found_this_cal = 0 total_events_in_cal = len(list(cal.events)) print(f» Всего событий в {calendar_name}: {total_events_in_cal}») # Используйте временную шкалу для эффективной фильтрации событий в диапазоне дат целевого дня # Преобразуйте местное время в UTC для фильтрации по временной шкале day_start_utc = day_start.astimezone(timezone.utc) day_end_utc = day_end.astimezone(timezone.utc) # Получите события в диапазоне целевого дня, используя временную шкалу days_timeline = cal.timeline.overlapping(day_start_utc, day_end_utc) for e in days_timeline: if not e.begin: continue # Получите время начала события start = e.begin.datetime if start.tzinfo is None: start = start.replace(tzinfo=timezone.utc) # Преобразование в местный часовой пояс start_local = start.astimezone(local) # Отладка: вывод первых нескольких событий для просмотра дат if events_found_this_cal < 3: print(f"Событие: '{e.name}' в {start_local.strftime('%Y-%m-%d %H:%M')}") # Получение времени окончания end = e.end.datetime if e.end else None end_local = end.astimezone(local) if end else None all_events.append({ "title": e.name, "start": start_local.strftime("%H:%M"), "end": end_local.strftime("%H:%M") if end_local else None, "location": e.location or "", "description": e.description or "" }) events_found_this_cal += 1 print(f"[OK] Найдено {events_found_this_cal} событий для целевого дня в {calendar_name}") except requests.exceptions.RequestException as e: print(f" [X] Сетевая ошибка при получении {calendar_name}: {str(e)}") continue except Exception as e: print(f" [X] Ошибка обработки {calendar_name}: {type(e).__name__}: {str(e)}") continue # Сортировка по времени начала all_events.sort(key=lambda x: x["start"]) # Вывод всех событий подробно if all_events: print(f"n[Google Calendar] Найдено {len(all_events)} событий для {target_date.strftime('%Y-%m-%d')}:") print("-" * 60) for event in all_events: time_str = f"{event['start']}-{event['end']}" if event['end'] else event['start'] location_str = f" @ {event['location']}" if event['location'] else "" print(f" {time_str} | {event['title']}{location_str}") print("-" * 60) else: print(f"n[Google Calendar] No events for {target_date.strftime('%Y-%m-%d')}") return all_events
Как разработчик на Python, я испытываю отвращение ко всем операторам print. Но это проблема для следующего этапа работы Фернао: рефакторинга и оптимизации кода после того, как логика продукта будет доработана.
Именно здесь я вижу, как по-настоящему работает динамика взаимодействия человека и ИИ. Я сразу вижу несколько способов улучшить эту функцию (уменьшить многословие, сократить ненужные задержки, оптимизировать поток), но для качественного выполнения этой задачи все равно требуется время, рассудительность и целенаправленность. ИИ помогает мне действовать быстро; он не заменяет необходимости понимать, что такое нечто выдающееся.
Пока что я не тратил много времени на его оптимизацию, и это осознанный выбор. Несмотря на некоторые шероховатости, функция делает именно то, что от неё требуется: она извлекает данные из моих календарей и передаёт информацию о встречах в Fernão, обеспечивая всё дальнейшее развитие событий.
Далее Фернао загружает мои задачи из Microsoft To Do. Здесь хранятся мои ежедневные списки дел (небольшие, конкретные задачи, которые необходимо выполнить и которые структурируют конкретный день). Все это настраивается непосредственно в приложении Microsoft To Do, которое является ключевой частью моего ежедневного рабочего процесса.
Если вам интересно узнать о более широкой системе повышения производительности, лежащей в основе этого, я написал об этом в соответствующей статье на родственном мне блоге (Wait a Day), ссылка на которую приведена ниже.
Итак, давайте рассмотрим еще одну подробно описанную функцию:
def get_tasks(target_date=None): «»» Получает задачи из Microsoft To Do на определенную дату (задачи, срок выполнения которых истекает в этот день или раньше). Аргументы: target_date: объект datetime.date для целевого дня. Если None, используется сегодняшний день. Возвращает список словарей задач. «»» CLIENT_ID = os.getenv('MS_CLIENT_ID', 'CLIENT_ID_KEY') AUTHORITY = «https://login.microsoftonline.com/consumers» SCOPES = ['Tasks.ReadWrite', 'User.Read'] # Настройка постоянного кэша токенов cache = SerializableTokenCache() cache_file = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), '.token_cache.bin') if os.path.exists(cache_file): with open(cache_file, 'r') as f: cache.deserialize(f.read()) # Аутентификация с постоянным кэшем app = PublicClientApplication(CLIENT_ID, authority=AUTHORITY, token_cache=cache) accounts = app.get_accounts() result = None if accounts: result = app.acquire_token_silent(SCOPES, account=accounts[0]) if not result or «access_token» not in result: for account in accounts: app.remove_account(account) result = None if not result: flow = app.initiate_device_flow(scopes=SCOPES) if «user_code» not in flow: print(f»[MS To Do] Failed to create device flow: {flow.get('error_description', 'Unknown error')}») return [] if «message» in flow: print(flow['message']) result = app.acquire_token_by_device_flow(flow) if not result or «access_token» not in result: print(f»[MS To Do] Authentication failed: {result.get('error_description', 'No access token') if result else 'No result'}») return [] headers = {«Authorization»: f»Bearer {result['access_token']}»} # Получение границ даты для целевой даты # Если целевая дата не указана, используйте сегодняшнюю дату if target_date is None: from datetime import date target_date = date.today() # Преобразование даты в дату и время в формате UTC для сравнения now = datetime.now(timezone.utc) target_day_end = datetime.combine(target_date, datetime.max.time()).replace(tzinfo=timezone.utc) # Получение всех списков lists_r = requests.get(«https://graph.microsoft.com/v1.0/me/todo/lists», headers=headers) if lists_r.status_code != 200: return [] lists_res = lists_r.json().get(«value», []) all_tasks = [] # Получение задач из всех списков с фильтрацией на стороне сервера for task_list in lists_res: list_id = task_list[«id»] list_name = task_list[«displayName»] # Простой фильтр — получаем только незавершенные задачи params = {«$filter»: «status ne 'completed'»} tasks_r = requests.get( f»https://graph.microsoft.com/v1.0/me/todo/lists/{list_id}/tasks», headers=headers, params=params ) if tasks_r.status_code != 200: continue tasks = tasks_r.json().get(«value», []) # Фильтрация и преобразование задач for task in tasks: due_date_obj = task.get(«dueDateTime») if not due_date_obj: continue due_date_str = due_date_obj.get(«dateTime») if not due_date_str: continue try: due_date = datetime.fromisoformat(due_date_str.split('.')[0]) if due_date.tzinfo is None: due_date = due_date.replace(tzinfo=timezone.utc) # Включить все задачи, срок выполнения которых истекает в целевую дату или раньше if due_date <= target_day_end: # Определение статуса на основе целевой даты target_day_start = datetime.combine(target_date, datetime.min.time()).replace(tzinfo=timezone.utc) if due_date < target_day_start: status = "OVERDUE" elif due_date <= target_day_end: status = f"DUE {target_date.strftime('%Y-%m-%d')}" else: status = "FUTURE" all_tasks.append({ "list": list_name, "title": task["title"], "due": due_date.strftime("%Y-%m-%d"), "importance": task.get("importance", "normal"), "status": status }) except Exception as e: continue # Вывод сводки задач if all_tasks: print(f"[MS To Do] {len(all_tasks)} задач(-ов) на {target_date.strftime('%Y-%m-%d')} или просрочены") else: print(f"[MS To Do] Нет задач, подлежащих выполнению на {target_date.strftime('%Y-%m-%d')} или просроченных") # Сохранение кэша токенов для следующего запуска if cache.has_state_changed: with open(cache_file, 'w') as f: f.write(cache.serialize()) return all_tasks
Эта функция получает полный список моих задач на текущий день (а также просроченных задач за предыдущие дни) — вот пример того, как они выглядят в списке дел:

После извлечения задач из списка дел я также хочу, чтобы Фернао понимал, к чему я стремлюсь, а не просто что у меня в списке на сегодня. Для этого он получает мои цели непосредственно со страницы Notion, где указаны ключевые результаты за год.
Вот пример того, как структурированы эти цели:
- В первом столбце показан мой исходный уровень на начало года.
- В последнем столбце указана цель, которую я хочу достичь.
- А в столбце справа отображается мой текущий прогресс.
В приведенном ниже примере указано, сколько постов в блоге я хочу написать для вас в этом году, а также общее количество книг, которые я планирую продать по трем своим наименованиям.
Это даст Фернао более широкое представление о том, какие задачи следует приоритезировать, как вы увидите в подсказке для создания расписания.

Кстати, пока я писал этот пост, я добавил в приложение небольшой виджет. Если уж мы создаём персонального помощника, то почему бы ему не обладать индивидуальностью?
Поэтому я спросил у Близнецов:
«Можно добавить забавную анимацию, пока приложение получает события календаря, проверяет списки дел и загружает данные из Notion? Например, небольшой виджет с Фернао, помешивающим что-то в кастрюле, с подписью «Фернао готовит»».

После завершения этого раунда задачи и события календаря успешно собираются и отображаются на интерфейсе Фернао (пример задач и встреч за мой день показан ниже).

А теперь самое интересное: имея под рукой календари, задачи и цели, Фернао составляет для меня единый, волшебный план на весь день:
07:30-09:30 | Спортзал 09:30-09:40 | Проверка табелей учета рабочего времени в Odoo (Срок сдачи: 04.02.2026) 09:40-09:55 | Просмотр задач в списке дел — [Задача по организации] (Срок сдачи: 04.02.2026) 09:55-10:15 | Чтение новостей Feedly — [Обзор новостей] (Срок сдачи: 04.02.2026) 10:15-10:30 | Написание документа о корпоративной культуре, источник вдохновения: https://pt.slideshare.net/slideshow/culture-1798664/1798664 (Срок сдачи: 04.02.2026) 10:30-10:45 | Ответ на вопросы LinkedIn — [Задание для организации] (Срок выполнения: 04.02.2026) 10:45-11:00 | Проверка Looker (Срок выполнения: 04.02.2026) 11:00-11:30 | Публикация «Искусственный интеллект на этой неделе» (Срок выполнения: 04.02.2026) 11:30-12:00 | Подготовка к декодированию подкастов с помощью ИИ 12:00-13:00 | Декодирование подкастов с помощью ИИ — Иво Бернардо, DareData (Мероприятие) 13:00-14:00 | Перерыв на обед 14:00-14:30 | Кандидат 1 (имя скрыто) и Иво Бернардо на Google Meet (Мероприятие) 14:30-18:00 | Подготовка отчета DareData о состоянии дел 18:00-18:30 | Кандидат 2 (имя скрыто) и Иво Бернардо на Google Meet (Мероприятие) 18:30-19:00 | Кандидат 3 (имя скрыто) и Иво Бернардо на Google Meet (Мероприятие) 19:00-19:30 | Кандидат 4 (имя скрыто) и Иво Бернардо на Google Meet (Мероприятие) 19:30-20:00 | Кандидат 5 (имя скрыто) и Иво Бернардо на Google Meet (Мероприятие) 20:00-20:15 | Проверьте сигналы инсайдерской торговли для получения идей по акциям https://finviz.com/insidertrading?tc=1 (Просрочено) 20:15-20:30 | График маркетинга (Просрочено) 20:30-21:00 | Перерыв на ужин 21:00-22:00 | Занятие 22:00-22:15 | Завершение дня — подведение итогов и подготовка к завтрашнему дню.
Это один из тех моментов, которые действительно кажутся немного волшебными. Не потому, что технология непрозрачна, а потому, что результат получается настолько чистым. Беспорядочная смесь встреч, задач и долгосрочных целей превращается в день, который я действительно могу выполнить.
Что делает это еще интереснее, так это простота заключительного этапа. После того, как вся основная работа выполнена в фоновом режиме (события календаря, задачи, цели), я не организую сложный конвейер или цепочку подсказок. Я использую одну-единственную подсказку.
Этот единственный запрос берет все, что Фернао знает о моих ограничениях и приоритетах, и превращает это в тот день, который вы вот-вот увидите.
name: daily_schedule description: Генерирует ежедневное расписание на основе событий и задач календаря model: gemini-2.5-flash-lite temperature: 0.3 max_tokens: 8192 variables: — date_context — events_str — tasks_str — context — todo_context — auto_context — currently_reading — notion_context — is_today template: | Вы мой личный ИИ-помощник по планированию. Помогите мне спланировать мой день! **ЦЕЛЕВАЯ ДАТА:** Планирование на {date_context} **СОБЫТИЯ (фиксированные):** {events_str} **ЗАДАЧИ ДЛЯ ПЛАНИРОВАНИЯ:** {tasks_str} **МОЙ КОНТЕКСТ:** {context} **КОНТЕКСТ ЗАДАЧИ (как долго обычно занимают задачи):** {todo_context} **АВТОМАТИЧЕСКИ ОБУЧЕННЫЙ КОНТЕКСТ:** {auto_context} **ЧИТАЮ В НАСТОЯЩЕЕ ВРЕМЯ:** {currently_reading} **РЕЗУЛЬТАТЫ И ЦЕЛИ (из Notion):** {notion_context} **ПРАВИЛА ПЛАНИРОВАНИЯ:** 1. **Нельзя планировать задачи во время событий календаря** — события фиксированы. 2. **Обязательные перерывы:** — Обед: 12:30-13:30 (зарезервируйте время по возможности) — Ужин: 20:30-21:00 — Игра с кошкой: 45-60 минут в любом удобном месте примерно в течение дня 3. **Подбор задач:** Разумно распределяйте задачи между событиями, исходя из доступного времени. 4. **Оценка времени:** Используйте контекст задачи, чтобы оценить, сколько времени займет каждая задача. 5. **Рабочее время:** с 09:30 до 22:00. 6. **Начало расписания:** {сегодня} **ВАША ЗАДАЧА:** 1. Составьте полное почасовое расписание на {дата_контекст}. 2. Распределите все задачи между событиями и перерывами. 3. **Расставьте приоритеты задач на основе моих результатов и целей из Notion** — сосредоточьтесь на самом важном. 4. Будьте дружелюбны — если вам нужна дополнительная информация о задаче, спросите меня! 5. **Сохраняйте полученные знания:** Если я даю вам контекст о задачах, подтвердите это и скажите, что вы это запомните. **ФОРМАТ:** Начните с дружелюбного приветствия, затем предоставьте расписание в следующем формате: «` 09:30-10:00 | Задача/Событие 10:00-11:00 | Задача/Событие … «` После составления расписания, пожалуйста, сообщите, если вам потребуется внести какие-либо корректировки или уточнить какие-либо задачи. Составьте расписание на день прямо сейчас, спасибо!
И в этом плане Фернао определенно преуспевает:

Создание этой системы доставило мне настоящее удовольствие. Я буду продолжать развивать Фернао, давать ему новые обязанности, ломать вещи, чинить их и делиться здесь тем, чему научусь.
Со временем я также планирую написать практические руководства о том, как самостоятельно создавать и развертывать подобные приложения. Пока что Fernão существует только на моем компьютере, и, вероятно, так и останется. Тем не менее, я намерен сделать его открытым исходным кодом. Не потому, что он универсально полезен в своем нынешнем виде (он глубоко адаптирован к моей жизни), а потому, что лежащие в его основе идеи могут быть таковыми.
Для этого мне потребуется абстрагировать инструменты, модульно структурировать функциональность и позволить включать и выключать функции, чтобы другие могли настраивать помощника под свои собственные рабочие процессы, а не под мои.
Я мог бы создать нечто подобное, используя только Claude Code. Но я этого не сделал. Мне нужен был полный контроль: свобода менять модели, смешивать поставщиков и, в конечном итоге, запускать Fernão на локальном LLM вместо того, чтобы полагаться на внешние API. В данном случае для меня важнее право собственности и гибкость, чем удобство.
Если бы вы создавали персонального ИИ-помощника, какие задачи вы бы ему поручили? Мне бы очень хотелось услышать ваши идеи и попытаться воплотить их в Fernão. Оставьте комментарий, поскольку этот проект всё ещё находится в стадии разработки, и взгляд со стороны часто является самым быстрым способом его улучшить.
Источник: towardsdatascience.com




















