KodikDevKit: как я перенес ИИ-аудит и рутину бэкендера в консоль (и обманул веб-хостинг)

KodikDevKit: как я перенес ИИ-аудит и рутину бэкендера в консоль (и обманул веб-хостинг)

Каждый бэкенд-разработчик ежедневно сталкивается с кучей рутины: написать структуру проекта, набросать юнит-тесты, правильно оформить коммит по стандарту или проверить код на уязвимости. Обычно мы идем по одному и тому же пути: открываем браузер, переходим в веб-интерфейс условного ChatGPT, копируем туда код (надеясь, что служба безопасности компании об этом не узнает) и ждем ответ.

Мне этот процесс окончательно надоел. Во-первых, это банально долго и выбивает из состояния потока. Во-вторых, отправлять коммерческий код на внешние сервера — так себе идея, если вы работаете под NDA.

Я решил написать простую, но эффективную интерактивную консольную утилиту KodikDevKit, которая делает всё то же самое, но на 100% локально, оффлайн и прямо в терминале. В этой статье я расскажу, как под капотом связать Node.js и локальную Ollama, покажу реальный код стриминга ответов, поделюсь философией vibe-coding и расскажу, как мне пришлось костылями обманывать систему хелсчеков облачного хостинга, чтобы задеплоить туда CLI-приложение.

Философия Vibe-Coding: зачем мне вообще свой CLI?

Я развиваю собственную студию разработки MevsusLab и постоянно прыгаю между совершенно разными задачами. Сегодня нужно поднять модульный REST API на FastAPI, завтра — написать Telegram-бота на aiogram, послезавтра — настроить окружение.

В какой-то момент я поймал себя на том, что постоянное переключение между редактором кода, терминалом и вкладкой браузера с нейросетью просто убивает состояние потока. Это ломает весь vibe-coding — подход, при котором ты быстро прототипируешь идеи с помощью ИИ, не отвлекаясь на рутину.

Плюс встал вопрос безопасности данных. Так появился концепт инструмента, который будет жить прямо там, где я работаю (в консоли), и думать исключительно на моем локальном железе.

Архитектура решения

Под капотом всё устроено максимально просто и надежно. Нам не нужны сторонние облачные API, тяжелые SDK и постоянное подключение к интернету:

[Консоль / CLI] ---> [Node.js скрипт] ---> [Локальный ,API, Ollama (localhost:11434)] | (Чтение файлов) | [Ваш исходный код]

В качестве ИИ-движка выступает Ollama — она позволяет запускать современные LLM (я гоняю на llama3 или специализированных кодерских моделях вроде qwen2.5-coder) прямо на домашней машине. Скрипт на Node.js считывает файлы из рабочей директории, упаковывает их в правильный системный промпт и общается с локальным API Ollama.

И также вы конечно можете работать через облачные API — OpenAI и Antropic. Механика та же: скрипт сам собирает содержимое файлов, пакует в промпт и отправляет на выбранный эндпоинт, будь то gpt-4o или claude-3-5-sonnet. Достаточно указать API-ключ и название модели — и движок переключается мгновенно. Никакой отдельной обвязки писать не нужно, внутренний слой адаптирует запросы под формат каждого провайдера. Удобно, когда локальное железо не вывозит, а нужно быстрое и мощное облачное решение без заморочек.

Ну и конечно, офлайн-режим остаётся базой — вся обработка идёт локально через Ollama, никакие файлы наружу не утекают. Просто ставишь модель, запускаешь — и работаешь в полном вакууме, даже без интернета. Разбор полетов: пишем локальный ИИ-аудитор

Разбор полетов: пишем локальный ИИ-аудитор

Давайте посмотрим на ядро нашей утилиты. Вместо тяжелых внешних библиотек мы будем использовать стандартный fetch, который доступен в Node.js «из коробки» начиная с 18-й версии.

Чтобы пользователю не пришлось секунд 30 смотреть в пустой экран в ожидании, пока локальная модель сгенерирует весь ответ целиком, мы обязательно включаем режим стриминга (stream: true). Ответ от Ollama в таком режиме приходит потоком в формате NDJSON (строки JSON, разделенные переносом строки), которые мы парсим на лету и сразу выводим в консоль.

Вот функция, которая отвечает за чтение файла, подготовку промпта и стриминг аудита:

import fs from 'fs'; import path from 'path'; // Системный промпт, который настраивает модель на строгий технический аудит const SYSTEM_PROMPT = `Ты — опытный senior-разработчик и специалист по информационной безопасности. Твоя задача — провести аудит предоставленного кода. Найди уязвимости, хардкод-ключи, проблемы с производительностью и предложи варианты оптимизации. Отвечай строго по делу, без лишней "воды" и долгих вступлений. Используй markdown для форматирования кода.`; async function runLocalAudit(filePath) { // Проверяем, существует ли файл const absolutePath = path.resolve(filePath); if (!fs.existsSync(absolutePath)) { console.error(`[Ошибка] Файл не найден: ${filePath}`); return; } const codeContent = fs.readFileSync(absolutePath, 'utf-8'); console.log(`[Инфо] Запуск локального аудита для: ${path.basename(filePath)}...`); try { const response = await fetch('http://localhost:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'llama3', // Можно заменить на qwen2.5-coder:7b для лучшего понимания кода prompt: `Проведи аудит следующего кода:\n\n\`\`\`\n${codeContent}\n\`\`\``, system: SYSTEM_PROMPT, stream: true // Включаем стриминг, чтобы видеть ответ по мере генерации }) }); if (!response.ok) { throw new Error(`Ошибка Ollama API: ${response.statusText}`); } // Читаем поток данных const reader = response.body.getReader(); const decoder = new TextDecoder(); let finished = false; while (!finished) { const { value, done } = await reader.read(); if (done) { finished = true; break; } const chunk = decoder.decode(value, { stream: true }); // Ответ от Ollama приходит в виде NDJSON (строки JSON, разделенные переносом строки) const lines = chunk.split('\n'); for (const line of lines) { if (line.trim() !== '') { const parsed = JSON.parse(line); process.stdout.write(parsed.response); // Выводим символы в реальном времени } } } console.log('\n\n[Успешно] Аудит завершен.'); } catch (error) { console.error(`\n[Ошибка выполнения]: ${error.message}`); } }

Какие еще фичи мы упаковали в CLI?

KodikDevKit — это не только аудит кода. Чтобы закрыть всю ежедневную бэкенд-рутину, мы добавили еще несколько автоматизаций:

  1. Генерация архитектуры: Вместо того чтобы руками создавать папки и файлы для нового проекта, вы просто вводите в CLI: "Разверни модульный бэкенд на FastAPI с SQLite". Утилита мгновенно генерирует и раскладывает по полочкам готовое дерево проекта с преднастроенными конфигами и базой данных.
  2. Умные коммиты: Скрипт заглядывает в текущие изменения через git diff, отправляет их локальной модели и генерирует осмысленное сообщение для Git строго по стандарту Conventional Commits. Никаких безликих "fix" и "update".
  3. Автогенерация тестов: Создание базовых юнит-тестов и конфигураций CI/CD пайплайнов в один клик.

Главный технический факап: деплой CLI туда, где его не ждали

Проект создавался в рамках хакатона Kodik Launchpad. По условиям платформы, приложение нужно было развернуть на их внутреннем облачном веб-хостинге, чтобы активировать карточку проекта в общем маркетплейсе.

И вот тут начался ад. Логи сборки Docker-контейнера раз за разом падали с одной и той же ошибкой: Healthcheck timed out after 45 attempts. Connection refused.

В чем была проблема?

Платформа хостинга была жестко заточена под классические веб-сайты и REST API. Она запускала контейнер, видела открытый порт и начинала долбиться туда HTTP-запросами (хелсчеками), чтобы убедиться, что приложение успешно запустилось и работает.

Но KodikDevKit — это интерактивная консольная утилита! Когда Docker-контейнер запускался на удаленном сервере (в non-TTY окружении, где нет реального ввода с клавиатуры), библиотеки интерактивного меню пугались отсутствия stdin и мгновенно завершали процесс Node.js с ошибкой. Контейнер падал, порт закрывался, а хостинг 45 раз стучался в закрытую дверь, после чего гасил деплой.

Как мы это починили?

Чтобы обмануть систему валидации облачного хостинга и заставить консольный CLI-инструмент жить в веб-среде, пришлось изолировать логику запуска. Мы внедрили проверку окружения на самом раннем этапе инициализации Node.js.

Если приложение видит, что запущено на сервере (например, существует переменная process.env.PORT или process.stdin.isTTY === false), оно понимает: «Так, клавиатуры нет, я на хостинге».

В этом случае код полностью блокирует запуск консольного меню и поднимает микроскопический, чистейший веб-сервер на нативном модуле http:

const http = require('http'); if (process.env.PORT) { // Поднимаем пустую заглушку для прохождения хелсчеков хостинга http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('KodikDevKit CLI is running and live!'); }).listen(process.env.PORT); // КРИТИЧЕСКИ ВАЖНО: Останавливаем выполнение остального CLI-кода return; } else { // Тут запускается наше стандартное интерактивное меню для локального использования на ПК runInteractiveMenu(); }

Результат: На сервере Node.js крутит микро-заглушку, которая успешно отвечает хостингу 200 OK на все запросы доступности. Хелсчек пройден, статус деплоя загорелся зеленым, проект в маркетплейсе активирован! При этом локально у пользователя на компьютере утилита по-прежнему запускается как полноценный интерактивный терминальный инструмент.

Заключение и Roadmap

Этот кейс в очередной раз доказал мне, что концепция Privacy-First и локальные LLM — это не просто хайп, а реально работающий инструмент, который делает разработку быстрее, безопаснее и полностью бесплатной.

В ближайших планах нашей команды MevsusLab:

  • Расширить шаблоны автогенерации архитектуры для бэкенда.
  • Добавить поддержку других локальных движков (например, LM Studio).
  • Сделать систему интеграции с Telegram-ботами для быстрой отправки алертов безопасности.

Буду рад вашему фидбеку, предложениям и пулл-реквестам!

1