Пересадил Claude Code на механику

Листаю ленту, а там человек переключает модели Claude Code автомобильным рычагом. Тянет ручку, она щёлкает в паз, тахометр подскакивает, и в терминале меняется модель. Мне, конечно, тоже понадобилось. Теперь у меня есть своя:

Коробка передач: движение рычага отправляет команду смены модели в Claude CodeКоробка передач: движение рычага отправляет команду смены модели в Claude Code

Можно было набрать /model руками. Но тогда у меня не было бы коробки передач. Это ituzov-gearbox: Node.js, tmux, ванильный JavaScript и кулиса в SVG. Код открытый. Можно поставить себе, можно разобрать и сделать свою. Ниже как раз разберём.

Сначала хотел поставить готовую

Я Илья Тузов, фулстек-разработчик. Люблю разбираться, как устроены AI-инструменты, и собирать свои. Эта коробка появилась после одного ролика в ленте.

Приложение из ролика нашлось: Model Shift. На тот момент оно было платным, с закрытым кодом и только для macOS. Платить за красивую штуку нормально. Но эта штука должна печатать команды в мой терминал. Захотелось сначала посмотреть, что внутри, а посмотреть нечего.

Поэтому полез разбираться, как вообще снаружи переключить модель в работающем Claude Code. Оказалось, достаточно запустить Claude Code внутри tmux. Он умеет передавать нажатия клавиш в нужную панель. Вся связь с Клодом уместилась в одну команду. Ну тут уже захотелось собрать свою.

За каждой передачей стоит одна команда

tmux делит терминал на панели. У каждой есть свой адрес, например %3. Можно из другого терминала сказать ему: напечатай в третью панель вот это и нажми Enter:

tmux send-keys -t %3 "/model opus" Enter

В панели появится /model opus, как будто я набрал это руками. Claude Code обработает команду и переключит модель. Осталось приделать к ней рычаг. Связал всё так:

Как рычаг отправляет команду через Node.js и tmux в Claude CodeКак рычаг отправляет команду через Node.js и tmux в Claude Code

В браузере двигается ручка. Когда передача включилась, страница отправляет серверу модель и выбранную панель. Сервер дёргает tmux. За всё общение с терминалом отвечает один файл, src/tmux.js.

Передавать в терминал что угодно я не стал: сервер проверяет имя модели и формат адреса панели. tmux запускаю через execFile, аргументы передаю массивом. Но сама команда не знает, кто сидит в панели. Выбрал обычный shell вместо Клода, туда и напечатает. В интерфейсе я помечаю панели, похожие на Claude Code, по процессу и заголовку. Выбранную всё равно лучше проверить.

Коробка открывает меню и читает его с экрана

Рычаг уже умеет печатать /model opus. А какие ещё модели разложить по передачам? У всех свой список, и обновляется он чаще, чем хочется править подписи на коробке. Можно открыть меню /model и посмотреть. Коробка так и делает. У tmux для этого есть capture-pane:

tmux capture-pane -t %3 -p

Команда отдаёт текст с экрана панели. Открываю /model, жду, забираю текст и вытаскиваю названия регуляркой. Потом закрываю меню Escape. Коробка буквально читает тот же список, что и я.

Чтение списка моделей: меню, capture-pane, парсер и раскладка передачЧтение списка моделей: меню, capture-pane, парсер и раскладка передач

В коде это выглядит так, если оставить только команды:

await run(["send-keys", "-t", paneId, "/model", "Enter"]);await sleep(1200);const screen = await run(["capture-pane", "-t", paneId, "-p"]);await run(["send-keys", "-t", paneId, "Escape"]);

run здесь вызывает execFile("tmux", args, ...). Полный вариант с проверками лежит в репозитории. Только с Escape есть подвох. Если Claude сейчас отвечает, клавиша прервёт генерацию. Хотели прочитать список моделей, а остановили работу. Поэтому перед открытием меню проверяю, что происходит в панели:

const before = await run(["capture-pane", "-t", paneId, "-p"]);if (/esc to interrupt/i.test(before)) { throw new Error("панель занята — Claude сейчас отвечает");}

Так я проверяю занятость перед чтением моделей. На само переключение рычагом эту проверку не переносил, поэтому передачи меняем, когда Claude ждёт ввода. Да, это завязано на конкретный интерфейс. Перерисуют меню или поменяют подсказку, придётся чинить парсер. И 1200 мс ожидания тоже не гарантия: на медленной машине меню может открываться дольше.

Не получилось прочитать меню? Показываю ошибку и оставляю запасную раскладку. А если хочется свою, можно положить её в gearbox.config.json. Тогда коробка возьмёт передачи оттуда.

Почему ручка не проходит сквозь металл

Но ради одной команды я бы не стал рисовать коробку. Хотелось именно тянуть ручку, ловить паз и слышать щелчок. Если ручка просто ездит за курсором, это кружок поверх картинки. Какая-то грустная коробка получается.

У настоящей есть прорези. Из первой в четвёртую по диагонали не проскочишь, надо пройти через нейтраль. И если бросил ручку на полпути, она должна вернуться назад.

Путь из первой в четвёртую передачу через нейтральПуть из первой в четвёртую передачу через нейтраль

Так кулиса превратилась в граф: горизонтальная рейка нейтрали и вертикальные колонки передач. Ручке разрешено жить только на этих линиях. На нейтрали можно ехать вбок. Подошёл к нужной колонке, потянул вверх или вниз, ручка зашла в прорезь. Дальше x заперт, меняется только y, да и тот ограничен концами паза.

// Упрощённая схема движения внутри вертикальной прорези.const col = nearestColumn(x);x = col.x;y = clamp(pointer.y, col.top, col.bottom);

Чтобы выйти из колонки, нужно вернуться на нейтраль. Даже если ленишься тянуть и просто кликаешь по номеру передачи, ручка доедет по прорезям. Через металл не срезает. Дотянул до паза и отпустил: щелчок, перегазовка, команда улетела в терминал. Не дотянул, ручка возвращается в текущую передачу.

Обошёлся без физического движка. Запретил ручке проходить через стенки, добавил возврат в паз и короткий щелчок. Звук тоже сделал кодом, через Web Audio.

Окно сделал через Chrome

Ещё хотелось, чтобы коробка висела отдельным маленьким окном. Для этого хватило браузера, который уже установлен. Node.js поднимает сервер на 127.0.0.1, а Chromium открывает страницу с флагом:

--app=http://127.0.0.1:4321

Получается окно без вкладок и адресной строки, размером с коробку. Отдельный браузерный движок с собой не тащу. Правда, запускаю его с отдельным профилем. Уже работающий Chrome может забрать новое окно себе и проигнорировать размер и положение, которые я попросил. Поэтому у коробки свой профиль. Не нашёлся Chromium, откроется обычная вкладка.

Предусмотрел запуск на macOS, Linux и Windows через WSL. Без WSL на Windows не получится: вся затея держится на tmux. Сервер слушает 127.0.0.1, поэтому напрямую с другой машины к нему не подключиться. Возможность отправлять ввод в терминал у него остаётся, и эту часть стоит прочитать перед запуском. Открытый репозиторий как раз позволяет это сделать.

Как попробовать

Нужны Node.js 18+, tmux и установленный Claude Code. Сначала запускаешь Клода внутри tmux:

tmuxclaude

В другом терминале запускаешь коробку:

npx ituzov-gearbox

Или сначала забираешь исходники и запускаешь из папки:

git clone https://github.com/ituzov/ituzov-gearbox.gitcd ituzov-gearboxnpm start

Дальше выбери панель с Клодом, дождись чтения моделей и тяни ручку. Только смотри и в терминал: коробка отправляет клавиши, а принял ли Claude команду, видно уже там. Всё лежит на GitHub, MIT. За терминалом иди в src/tmux.js, за рычагом и щелчками в public/app.js.

Вот так вместо установки готовой аппки я написал свою коробку передач. Модели можно было переключать и раньше, зато теперь с перегазовкой. Ещё хочу сцепление на пробел и ручник для отмены генерации. Есть куда потратить следующий вечер.

Источник: habr.com

0 0 голоса
Рейтинг новости
1
0
Подписаться
Уведомить о
0 комментариев