Иногда полезная инженерная работа начинается не с кода, а с разговора.
Например, вы обсуждаете архитектуру, ограничения, варианты реализации или странный баг в ChatGPT. Через какое-то время направление становится понятным, и дальше хочется продолжить уже локально: в репозитории, терминале, с Codex CLI.
Но контекст остался в ChatGPT.
Можно вручную скопировать summary, можно вставить отдельные фрагменты, можно начать новую сессию в Codex и заново объяснить задачу. Всё это работает, но теряет часть деталей: почему было принято решение, какие варианты отбросили, какие ограничения уже проговорили.
Мне захотелось сделать маленький мост:
ChatGPT share URL -> локальная сессия Codex CLI
Так появился chatgpt2codex.
npx chatgpt2codex https://chatgpt.com/share/<id>
Репозиторий: https://github.com/vv-bogdanov/chatgpt2codex
npm: https://www.npmjs.com/package/chatgpt2codex
Что делает утилита
chatgpt2codex принимает публичную ссылку на расшаренный ChatGPT-диалог и импортирует его как локальную сессию Codex CLI.
По умолчанию сессия привязывается к текущей директории:
npx chatgpt2codex https://chatgpt.com/share/<id>
Можно явно указать проект:
npx chatgpt2codex https://chatgpt.com/share/<id> -C /path/to/project
Можно сначала посмотреть, что будет импортировано, ничего не записывая:
npx chatgpt2codex https://chatgpt.com/share/<id> --dry-run
Если для директории уже есть сессия, утилита по умолчанию завершится с ошибкой. Для осознанной замены есть флаг:
npx chatgpt2codex https://chatgpt.com/share/<id> --force
Почему это не просто “записать файл”
Первая версия казалась очевидной: распарсить ChatGPT share page, нормализовать сообщения и записать JSONL-файл в директорию сессий Codex.
У Codex CLI локальные сессии лежат примерно здесь:
~/.codex/sessions/YYYY/MM/DD/
Файл называется в духе:
rollout-2026-06-30T11-50-01-<session-id>.jsonl
Внутри это JSONL: строка с метаданными сессии, дальше события диалога.
Но после первого импорта Codex не всегда подхватывал сессию в resume flow.
Оказалось, что в современных сборках одного JSONL недостаточно. Codex использует ещё и локальную SQLite-базу:
~/.codex/state_5.sqlite
Именно там хранится индекс тредов: cwd, title, preview, source, archived state, путь до rollout-файла и другие поля, которые нужны для отображения сессии в списке продолжения.
То есть рабочий импорт должен писать не только файл, но и согласованную запись в локальный state.
Формат на практике
Минимально полезный импорт делает несколько вещей.
Сначала создаёт rollout JSONL с метаданными сессии. Важно, чтобы сессия выглядела для Codex как нормальная CLI-сессия:
{ "type": "session_meta", "payload": { "source": "cli", "thread_source": "user", "cwd": "/path/to/project" }}
В ранней версии я использовал кастомный source, и это оказалось плохой идеей: Codex не воспринимал такую сессию как обычную CLI-сессию и мог фильтровать её из resume picker.
Затем утилита пишет строку в state_5.sqlite, если база существует. Для отображения важны, в частности:
-
id -
rollout_path -
cwd -
source -
title -
preview -
first_user_message -
has_user_event -
archived
После этого Codex уже видит импортированную сессию как локальный тред, который можно продолжить.
Обработка дублей
Я не хотел, чтобы импорт случайно перетирал существующий рабочий контекст.
Поэтому поведение по умолчанию такое:
если для cwd уже есть сессия -> выйти с ошибкой
Проверка смотрит сначала в SQLite-индекс Codex, потом fallback-ом по JSONL-файлам. Если пользователь действительно хочет заменить существующий импорт, он явно передаёт --force.
При --force утилита удаляет старую строку из SQLite, удаляет старый rollout-файл и создаёт новую сессию.
Парсинг ChatGPT share
Отдельная часть — достать саму переписку из share URL.
ChatGPT share page не является публичным API импорта. Поэтому парсер сделан прагматично: он извлекает данные из страницы, нормализует сообщения и приводит их к небольшой внутренней модели:
interface NormalizedMessage { role: "user" | "assistant"; text: string; createdAt?: string;}
Этого достаточно, чтобы перенести полезный контекст в Codex-сессию.
Тесты
Поскольку и ChatGPT share pages, и локальный формат Codex не являются стабильными публичными API, тесты здесь важнее, чем может показаться для маленькой CLI-утилиты.
Я добавил проверки на:
-
парсинг share-страницы;
-
запись rollout JSONL;
-
корректный
source=cli; -
запись в SQLite-индекс;
-
обнаружение существующей сессии для
cwd; -
поведение
--force.
И отдельно проверил сценарий через локальный npm tarball, чтобы убедиться, что пакет работает не только из исходников.
Ограничения
Это не официальный импорт ChatGPT -> Codex.
Утилита опирается на текущие форматы ChatGPT share pages и локального состояния Codex CLI. Они могут измениться. Поэтому я держу реализацию небольшой, а поведение — максимально прямолинейным.
Ещё одно практическое ограничение: нужен Node.js 22.13.0 или новее, потому что для записи SQLite-индекса используется node:sqlite.
Зачем это всё
Главная идея простая: не терять контекст при переходе от обсуждения к реализации.
Если разговор в ChatGPT уже содержит ограничения, решения и аргументы, полезно уметь перенести его в локальный coding-agent workflow без ручного пересказа.
chatgpt2codex закрывает ровно этот небольшой зазор:
обсудили в ChatGPT -> расшарили URL -> продолжили в Codex CLI
Код открыт, пакет опубликован в npm:
ссылка на оригинал статьи https://habr.com/ru/articles/1053822/