
Все уже слышали про Gemini CLI, который позволяет взаимодействовать с мощной gemini 2.5 прямо из командной строки. Это удобно, открывает массу возможностей. Но что, если мы захотим не просто использовать готовое решение, а понять, как оно работает изнутри? А если у нас вообще нету VPN для сервисов гугла? Или, что еще интереснее, создать свой собственный, пусть и экспериментальный, аналог? Чем мы хуже? Давайте сверстаем свой вариант на… GOLANG?
Да, именно так. Мы не будем писать продакшн-готовый инструмент, который заменит собой все существующие CLI. Наша цель — эксперимент, погружение в процесс, понимание того, как можно подружить Go, консоль и большую языковую модель.
Моя идея проста: создать относительно не сложного CLI-агента, который будет слушать наши запросы, обращаться к AI за советом или командой, предлагать эту команду нам для выполнения, а затем, после нашего подтверждения, выполнять её и анализировать результат. Если команда завершится ошибкой, AI попытается понять, что пошло не так, и предложит решение. Это позволит нам не только получать ответы, но и автоматизировать рутинные задачи, не выходя из терминала.
Если вам интересен процесс и вы хотите следить за дальнейшими материалами, буду признателен за подписку на мой телеграм-канал. Там я публикую полезныe материалы по разработке, разборы сложных концепций, советы как быть продуктивным и конечно же отборные мемы: https://t.me/nullPointerDotEXE.
В качестве зависимостей будем использовать github.com/fatih/color для красивого вывода. Поэтому сразу сделайте go get github.com/fatih/color
Шаг 1. Объявляем переменные, структуры, константы и инициализируемся
var ( successColor = color.New(color.FgGreen).Add(color.Bold).SprintFunc() borderColor = color.New(color.FgWhite).Add(color.Bold).SprintFunc() labelColor = color.New(color.FgHiWhite).Add(color.Bold).SprintFunc() aiColor = color.New(color.FgHiCyan).Add(color.Bold).SprintFunc() errorColor = color.New(color.FgRed).Add(color.Bold).SprintFunc() )
Начнем с переменных. Это не более, чем просто функции для подсветки текста. Мы же пишем аналог gemini, ведь так? Ну вот и текст будем делать тоже красивым.
Далее у нас идут инструкции для нейросети. Модельки я использую бесплатные, но не самые слабые. Конкретно тут я использую qwen3 на 30b параметров. Api ключ я покажу как позже получить на openrouter.
const ( systemPromptBase = "Вот системный контекст:\n" simpleChatPromptTemplate = "Ты — AI-агент. Ответь на вопрос пользователя, основываясь на информации которая есть. НИКОГДА НЕ ИСПОЛЬЗУЙ СМАЙЛИКИ. вот история сообщений: %v" commandGenPromptTemplate = "Ты — AI-агент. Твоя задача — генерировать команды для PowerShell на основе запроса пользователя.Отвечай только командой, без лишних слов, объяснений и markdown-форматирования. Только голая команда. Никогда не используй смайлики. Старайся генерить команды, которые не выводят очень длинный лог. И вот наши прошлые сообщения: %v" errorAnalysisPromptTemplate = "Проанализируй ошибку выполнения команды PowerShell и объясни простыми словами, что пошло не так. Исходный запрос: '%s'. Команда: '%s'. Ошибка: '%s'" summaryPromptTemplate = "Кратко объясни результат выполнения команды, основываясь на первоначальном запросе пользователя. Исходный запрос: '%s'. Вывод команды: '%s'" )
Ну тут сами прочитаете тексты. 1 промпт нужен для предоставлении базовой информации о пк. Это можно убрать, но если вы планируете делать такого агента под mac os или linux, то предоставление базовой информации нейросетке для генерации правильных команд необходимо. 2 промпт для режима чата. 3 для агента. 4 для анализа ошибок и 5 для отчетов.
Идем далее
type Message struct { Role string `json:"role"` Content string `json:"content"` } type App struct { APIKey string APIUrl string Model string MaxRetries int Client *http.Client Reader *bufio.Reader History []Message } type APIRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` } type APIResponse struct { Choices []Choice `json:"choices"` } type Choice struct { Message ResponseMessage `json:"message"` } type ResponseMessage struct { Content string `json:"content"` }
Структурки.
первая структура для сохранения истории. Все остальные для взаимодействия с API нейросети. А вот app для инициализации приложения. Городить DI, конфигурацию и прочий оверхед в маленьком проекте я не буду. Сейчас у нас максимально лайтовая CLI’ка на каждый день.
Структурку App необходимо проинициализировать.
func NewApp() *App { return &App{ APIKey: "апи ключ", APIUrl: "https://openrouter.ai/api/v1/chat/completions", Model: "qwen/qwen3-30b-a3b:free", MaxRetries: 5, Client: &http.Client{ Timeout: 60 * time.Second, //генерация может быть не самой быстрой }, Reader: bufio.NewReader(os.Stdin), History: []Message{ { Role: "system", Content: systemPromptBase + getSystemInfo(), }, }, } }
Но запомните: НИКОГДА НЕ ХАРДКОРДИТЕ АПИ КЛЮЧИ! ВОТ ВООБЩЕ НЕ НАДО ТАК ДЕЛАТЬ!! В данном случае мы пишем CLI чисто для эксперемента, а не в продакшн. Поэтому загружать конфиг с env я не вижу смысла тут. Но если будете расширять, то ОБЯЗАТЕЛЬНО ВЫНОСИТЕ КОНФИГУРАЦИЮ И САМОЕ ГЛАВНОЕ .ENV.
Функция getSystemInfo() возвращает базовую информацию о пк юзера.
func getSystemInfo() string { osName := runtime.GOOS currentUser, err := user.Current() username := "unknown" if err == nil { username = currentUser.Username } cwd, err := os.Getwd() if err != nil { cwd = "unknown" } return fmt.Sprintf( "System Context:\n- OS: %s\n- Shell: PowerShell\n- User: %s\n- CWD: %s\n", osName, username, cwd, ) }
Итак, давайте получим API ключ. Если у вас локальная модель или другая, то можете пропускать смело шаг 2.
Шаг 2. Получаем API ключ для нейросети на openrouter
Перейдите на сайт опенроутера и войдите любым удобным способ

После этого наведите курсор на свою иконку. Там будет вкладка «keys». Вот это вот вам туда вот.

Далее просто создаете ключ, копируете и вставляете.
Сложно? Не думаю.
Шаг 3. Создаем функцию генерации контента.
Cердце нашей утилиты, без которого оно не сможет функционировать хоть как-то. Функция максимально типичная и простая. Она делает запрос к API и возвращает ответ. Это ядро логики взаимодействия с внешним API генерации текста.
func (a *App) GenerateContent(messages []Message) (string, error) { // Формируем тело запроса с моделью и сообщениями reqBody := APIRequest{ Model: a.Model, Messages: messages, } // Сериализуем тело запроса в JSON bodyBytes, err := json.Marshal(reqBody) if err != nil { return "", fmt.Errorf("ошибка кодирования JSON: %w", err) } // Создаём HTTP POST-запрос к API req, err := http.NewRequest("POST", a.APIUrl, bytes.NewBuffer(bodyBytes)) if err != nil { return "", fmt.Errorf("ошибка создания запроса: %w", err) } // Устанавливаем заголовки: тип контента и авторизация req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+a.APIKey) // Отправляем запрос и получаем ответ resp, err := a.Client.Do(req) if err != nil { return "", fmt.Errorf("ошибка выполнения запроса: %w", err) } defer resp.Body.Close() // Читаем тело ответа respBytes, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("ошибка чтения ответа: %w", err) } // Проверяем успешность запроса по статус-коду if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("API вернул ошибку (статус %d): %s", resp.StatusCode, string(respBytes)) } // Десериализуем JSON-ответ в структуру var result APIResponse if err := json.Unmarshal(respBytes, &result); err != nil { return "", fmt.Errorf("ошибка декодирования ответа: %w", err) } // Возвращаем контент первого ответа, если он есть if len(result.Choices) > 0 && result.Choices[0].Message.Content != "" { return result.Choices[0].Message.Content, nil } // Если контент не получен — возвращаем ошибку return "", fmt.Errorf("API не вернул контент в ответе") }
Шаг 4. Выполняем команды от нейросети.
Напишем функцию, которая будет преобразовывать слова в действие.
func executeCommand(cmdStr string) (string, error) { cmd := exec.Command("powershell", "-NoProfile", "-Command", cmdStr) output, err := cmd.CombinedOutput() // Возвращаем вывод в любом случае, т.к. там может быть текст ошибки return strings.ToValidUTF8(string(output), ""), err }
Функция executeCommand выполняет указанную строку команды через PowerShell, собирает весь вывод (включая ошибки), преобразует его в корректную UTF-8 строку и возвращает вместе с ошибкой (если она возникла). Даже если команда завершилась с ошибкой, текст её вывода всё равно возвращается — это важно для отображения сообщений об ошибках пользователю. Если в выводе будет кириллица, то символы будут битые. Поэтому мы переводим все в UTF8.
В дополнение к этой команде идет функция для очистки. Зачем? Я отвечу зачем. Хоть мы в промпте явно указываем, что генерировать команду надо сразу, но от фундаментальных проблем нейросети мы не лишены. Нейросеть банально может забыть инструкцию из-за длины контекста. Поэтому мы перестраховываемся.
func cleanCommand(cmdStr string) string { cmdStr = strings.TrimPrefix(cmdStr, "```powershell") cmdStr = strings.TrimPrefix(cmdStr, "```bash") cmdStr = strings.TrimPrefix(cmdStr, "```") cmdStr = strings.TrimSuffix(cmdStr, "```") cmdStr = strings.TrimPrefix(cmdStr, "Команда: ") cmdStr = strings.TrimPrefix(cmdStr, "Command: ") // Более надежное извлечение команды из `powershell -command "..."` if strings.HasPrefix(strings.ToLower(cmdStr), "powershell -command ") { firstQuote := strings.Index(cmdStr, "\"") lastQuote := strings.LastIndex(cmdStr, "\"") if firstQuote != -1 && lastQuote > firstQuote { cmdStr = cmdStr[firstQuote+1 : lastQuote] } } return strings.TrimSpace(cmdStr) }
Функция cleanCommand очищает строку команды от лишнего форматирования и обёрток, которые могли быть добавлены нейросетью. Она удаляет префиксы вроде powershell`, bash, "Команда: ", "Command: ", а также обрезает завершающие ««.
Если строка начинается с конструкции powershell -command "...", то функция аккуратно извлекает содержимое внутри кавычек. В итоге возвращается чистая команда, готовая к выполнению.
Шаг 5. Пишем вспомогательные функции для красивого вывода
Тут я прям подробно заострять внимание не буду. В целом можно обойтись и без этих функций, но тк мы делаем свою gemini CLI, которая работает в России, то без них не обойтись.
Начнем с функции вывода огромной надписи CLI AGENT.
func printHeader() { var asciiHeader = []string{ " ███████╗██╗ ██╗ █████╗ ██████╗ █████╗ ███████╗███╗ ██╗████████╗", " ██╔════╝██║ ██║ ██╔══██╗██╔════╝ ██╔══██╗██╔════╝████╗ ██║╚══██╔══╝", " ██║ ██║ ██║ ███████║██║ ███╗███████║█████╗ ██╔██╗ ██║ ██║ ", " ██║ ██║ ██║ ██╔══██║██║ ██║██╔══██║██╔══╝ ██║╚██╗██║ ██║ ", " ███████╗███████╗██║ ██║ ██║╚██████╔╝██║ ██║███████╗██║ ╚████║ ██║ ", " ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ", " ", " (by oyminirole) ", } colors := []*color.Color{ color.New(color.FgHiCyan), color.New(color.FgCyan), color.New(color.FgHiBlue), color.New(color.FgBlue), color.New(color.FgHiMagenta), color.New(color.FgMagenta), } for _, line := range asciiHeader { lineLength := len(line) step := float64(len(colors)) / float64(lineLength) for i, char := range line { colorIndex := int(float64(i) * step) if colorIndex >= len(colors) { colorIndex = len(colors) - 1 } colors[colorIndex].Printf("%c", char) } fmt.Println() } color.New(color.FgHiCyan).Add(color.Bold).Println("\nИнформация:") fmt.Println(" • Используйте перед запросом \"!\" для получения ответов на вопросы, или просто введите запрос. Пример: !Как создать папку?") fmt.Println(" • Для режима агента просто введите запрос без !. Агент будет исполнять команды и предоставлять результаты с отчетами.") fmt.Println(" • Для выхода из программы нажмите Ctrl+C или закройте окно терминала.") fmt.Println(strings.Repeat("─", 70)) }
Функция printHeader выводит в терминал красивый ASCII-баннер с градиентной цветовой заливкой, а затем — краткую справку по использованию CLI-интерфейса.
Сначала она построчно печатает логотип с псевдонимом (oyminirole это я кста), используя плавный градиент из шести цветов. Затем выводит инструкции для пользователя: как задавать вопросы (!), как использовать режим агента и как выйти из программы. Всё оформлено в стиле интерактивной CLI-помощи.
Куда же без спинера.
func startSpinner(text string) chan bool { stop := make(chan bool) go func() { frames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇"} i := 0 for { select { case <-stop: fmt.Print("\r\033[K") //выполняет очистку текущей строки в терминале return default: fmt.Print("\r\033[K") //выполняет очистку текущей строки в терминале fmt.Print(text + " ") color.New(color.FgHiCyan).Add(color.Bold).Printf(frames[i%len(frames)]) time.Sleep(100 * time.Millisecond) i++ } } }() return stop }
Функция startSpinner запускает спиннер (анимацию загрузки) в отдельной горутине, чтобы визуально показать, что идёт фоновый процесс (например, генерация ответа).
Спиннер крутится в консоли, отображая символы из массива frames (символы типа ⠋, ⠙ и т.д.). Каждые 100 мс обновляется кадр, пока в канал stop не придёт сигнал — тогда спиннер останавливается и строка очищается.
Возвращаемый канал chan bool используется, чтобы остановить анимацию извне.
Теперь напишем функции для отображения красивых ответов
func printResultBox(commandOutput, aiSummary string, makeAnalize bool) { width := 80 fmt.Printf(" %s\n", successColor("╭─[ Результат ]"+strings.Repeat("─", width-16))) fmt.Printf(" %s %s\n", borderColor("│"), labelColor("Вывод команды:")) for _, line := range strings.Split(strings.TrimSpace(commandOutput), "\n") { fmt.Printf(" %s %s\n", borderColor("│"), line) } if makeAnalize && aiSummary != "" { fmt.Printf(" %s %s\n", borderColor("│"), borderColor(strings.Repeat("·", width-4))) fmt.Printf(" %s %s\n", borderColor("│"), aiColor("Анализ AI:")) for _, line := range strings.Split(strings.TrimSpace(aiSummary), "\n") { fmt.Printf(" %s %s\n", borderColor("│"), aiColor(line)) } } fmt.Printf(" %s\n", successColor("╰"+strings.Repeat("─", width-2))) } func printErrorBox(errorOutput, aiAnalysis string) { width := 80 fmt.Printf(" %s\n", errorColor("╭─[ ✗ Ошибка ]"+strings.Repeat("─", width-16))) fmt.Printf(" %s %s\n", borderColor("│"), labelColor("Лог ошибки:")) for _, line := range strings.Split(strings.TrimSpace(errorOutput), "\n") { fmt.Printf(" %s %s\n", borderColor("│"), errorColor(line)) } if aiAnalysis != "" { fmt.Printf(" %s %s\n", borderColor("│"), borderColor(strings.Repeat("·", width-4))) fmt.Printf(" %s %s\n", borderColor("│"), aiColor("Анализ AI:")) for _, line := range strings.Split(strings.TrimSpace(aiAnalysis), "\n") { fmt.Printf(" %s %s\n", borderColor("│"), aiColor(line)) } } fmt.Printf(" %s\n", errorColor("╰"+strings.Repeat("─", width-2))) }
Знаю, что выглядит очень страшно, но ничего страшного они не делают. Запоминать вам это не надо. Эти функции просто выводят красиво текст в консоль.
Шаг 6. Пишем основную логику
Начнем с наименее страшной функции.
// handleSimpleChat обрабатывает запросы в режиме простого чата. func (a *App) handleSimpleChat(userInput string) { spinner := startSpinner("Генерация...") prompt := []Message{ {Role: "system", Content: fmt.Sprintf(simpleChatPromptTemplate, a.History)}, {Role: "user", Content: strings.TrimPrefix(userInput, "!")}, } response, err := a.GenerateContent(prompt) if err != nil { printErrorBox(fmt.Sprintf("Ошибка генерации ответа:\n%v", err), "") return } spinner <- true printResultBox(response, "", false) }
Функция handleSimpleChat обрабатывает пользовательский ввод в режиме простого чата (вопрос-ответ) и выводит результат.
Что делает по шагам:
-
Запускает спиннер с подписью «Генерация…», чтобы показать, что идёт обработка.
-
Формирует промпт для нейросети:
-
системное сообщение с шаблоном
simpleChatPromptTemplate, в который подставляется история общения (a.History); -
пользовательское сообщение (ввод без
!в начале).
-
-
Вызывает
GenerateContentдля получения ответа от AI. -
Обрабатывает ошибку, если она возникла — выводит её в виде красной рамки.
-
Останавливает спиннер, если всё прошло успешно.
-
Печатает результат в красивой рамке (
printResultBox).
Итог: простой чат-режим, в котором мы получаем ответ на свой вопрос с анимацией ожидания и красивым выводом. Ничего сложного.
Идем дальше. Вот теперь мы подходим к самой логике агента. Но для начала напишем простенькую функцию для подтверждения команд.
func (a *App) askForConfirmation(command string) bool { fmt.Print("Подтвердить команду? [y/n]: ", aiColor(command), "\n> ") confirmInput, _ := a.Reader.ReadString('\n') confirmInput = strings.ToLower(strings.TrimSpace(confirmInput)) fmt.Print("\033[2A\033[J") return confirmInput == "y" || confirmInput == "yes" }
Функция askForConfirmation запрашивает у пользователя подтверждение на выполнение команды и возвращает true, если ответ — положительный. Так же она очищает 2 строки вверх (\033[2A) и удаляет их содержимое (\033[J), чтобы убрать следы подтверждения из терминала.
Теперь сам агент.
func (a *App) handleCommandMode(userInput string) { currentTurnHistory := make([]Message, len(a.History)) copy(currentTurnHistory, a.History) currentTurnHistory = append(currentTurnHistory, Message{Role: "user", Content: userInput}) var lastError string for i := 0; i < a.MaxRetries; i++ { spinnerGen := startSpinner("Генерация команды...") prompt := []Message{ {Role: "system", Content: fmt.Sprintf(commandGenPromptTemplate, currentTurnHistory)}, {Role: "user", Content: userInput}, } command, err := a.GenerateContent(prompt) spinnerGen <- true if err != nil { printErrorBox(fmt.Sprintf("Ошибка генерации команды:\n%v", err), "") return } command = cleanCommand(command) // 2. Подтверждение от пользователя if !a.askForConfirmation(command) { log.Println("Команда отменена пользователем.") return } currentTurnHistory = append(currentTurnHistory, Message{Role: "assistant", Content: command}) // 3. Выполнение команды spinnerExec := startSpinner("Выполнение...") output, err := executeCommand(command) spinnerExec <- true if err != nil { currentTurnHistory = append(currentTurnHistory, Message{Role: "console error", Content: output}) lastError = output time.Sleep(time.Second) continue // Переходим к следующей попытке } // 4. Успешное выполнение и подведение итогов spinnerSummary := startSpinner("Готовлю отчет...") summaryPrompt := []Message{{Role: "user", Content: fmt.Sprintf(summaryPromptTemplate, userInput, output)}} aiSummary, err := a.GenerateContent(summaryPrompt) if err != nil { log.Println("Ошибка генерации отчета: ", errorColor(err)) } spinnerSummary <- true printResultBox(output, aiSummary, true) currentTurnHistory = append(currentTurnHistory, Message{Role: "AI", Content: aiSummary}) a.History = currentTurnHistory // Сохраняем успешный диалог в основную историю return // Успешно завершили, выходим из функции } // 5. Обработка, если все попытки провалились spinnerAnalysis := startSpinner("Анализирую ошибки...") analysisPrompt := []Message{{Role: "user", Content: fmt.Sprintf(errorAnalysisPromptTemplate, userInput, "N/A", lastError)}} aiAnalysis, _ := a.GenerateContent(analysisPrompt) spinnerAnalysis <- true printErrorBox(fmt.Sprintf("Не удалось выполнить задачу после %d попыток.", a.MaxRetries), aiAnalysis) }
Согласен, пугает. Метод handleCommandMode реализует режим агента, в котором нейросеть генерирует команду по пользовательскому запросу, предлагает её на подтверждение, выполняет, а затем формирует краткий отчёт или анализирует ошибки при неудаче.
Пошаговая логика
-
Создаётся копия истории сообщений текущего чата, чтобы работать в рамках одной сессии, не влияя на глобальную историю. Это сделано чтобы не забивать контекст.
-
Запуск до
MaxRetriesпопыток:-
Генерация команды: нейросети передаётся промпт на основе истории и текущего ввода. Показывается спиннер.
-
Подтверждение: у пользователя спрашивается, можно ли выполнять команду. При отказе — функция завершает работу.
-
Выполнение команды: запускается в PowerShell, с анимацией выполнения. Если произошла ошибка — результат сохраняется, делается пауза и запускается следующая попытка.
-
-
Если команда выполнена успешно:
-
Генерируется краткий AI-отчёт по результатам выполнения.
-
Вывод команды и отчёт красиво показываются через
printResultBox. -
Вся история этого раунда сохраняется в общую историю приложения.
-
-
Если все попытки провалились:
-
Генерируется анализ ошибки (AI анализирует, почему не удалось).
-
Показывается красная рамка с числом неудачных попыток и анализом.
-
Назначение в CLI
Этот метод — ключевой элемент CLI-агента. Он превращает текстовый ввод пользователя в конкретную команду, контролирует выполнение, перехватывает ошибки и формирует осмысленные отчёты, обеспечивая высокий уровень автономности и интерактивности.
Шаг 7. Собираем все в единое целое.
Все самое страшное теперь позади. Давайте соберем наше приложение.
func isCommandPrefixed(input string) bool { return strings.HasPrefix(strings.ToLower(strings.TrimSpace(input)), "!") } func (a *App) Run() { printHeader() //да, это наш красивый заголовок for { fmt.Println("╭─" + strings.Repeat("─", 66)) color.New(color.FgHiWhite).Print("│ > Введите запрос: ") userInput, _ := a.Reader.ReadString('\n') color.New(color.FgHiWhite).Println("╰" + strings.Repeat("─", 65)) userInput = strings.TrimSpace(userInput) if userInput == "" { continue } if isCommandPrefixed(userInput) { a.handleSimpleChat(userInput) } else { a.handleCommandMode(userInput) } } } func main() { app := NewApp() defer app.Client.CloseIdleConnections() // Закрываем соединения при завершении app.Run() }
Обработка пользовательского ввода
Каждый цикл начинается с вывода декоративной рамки и приглашения ко вводу ( > Введите запрос:). Введённая строка очищается от пробелов и анализируется:
-
Если строка начинается с восклицательного знака
!— система воспринимает её как вопрос или сообщение и передаёт на обработку вhandleSimpleChat. Это режим диалога с нейросетью без выполнения команд. -
Если строка не начинается с
!— считается, что пользователь хочет выполнить команду, и запуск происходит черезhandleCommandMode. В этом режиме нейросеть сначала генерирует команду, затем спрашивает подтверждение, выполняет её и формирует краткий отчёт.
Такой подход позволяет чётко разделять безопасные текстовые запросы и потенциально опасные системные действия.
Главный цикл приложения
Функция Run запускает бесконечный цикл обработки пользовательского ввода, обеспечивая непрерывную работу CLI-интерфейса. Перед этим выводится ASCII-заголовок. Каждое сообщение анализируется и передаётся в соответствующий режим, в зависимости от его структуры.
Запуск и завершение
Точка входа программы — функция main — создаёт экземпляр App с преднастроенными параметрами (HTTP-клиент, история, ввод и т.д.) и запускает основной цикл. При завершении работы соединения HTTP-клиента закрываются корректно через defer, что особенно важно при частом использовании API.
Результат
Итогом является интуитивно понятный CLI-интерфейс, где:
-
!Как создать папку?→ возвращает ответ ИИ; -
создай мне папку на рабочем столе→ превращается в команду, исполняется, а затем поясняется.
Шаг 7. Восхищаемся результатом.
Поздравляю, мы получили максимально простую и красивую CLI’ку, которую спокойно можно переписывать как душе удобно, добавляя новый функционал. Причем с неплохими фишками. У нас нейросеть проводит саморефлексию и чинит ошибки. Что может быть лучше?

Ну и еще примерчик:

Команды можно выполнять и более сложные. Спокойно может поднять докер контейнеры, создать файлы и написать в них код, проанализировать тонну информации о вашем пк, найти всякие операции, которые жрут память и тд.
На мой взгляд приложение вышло крайне полезным и интересным.
Сори, что не выложил полный код на гитхаб. Позже добавлю, все распишу, так как ну вообще нету времени в данный момент у меня. Но поделиться интересным тоже хочется. На гитхаб уже опубликую более полноценную версию с env и конфигами, а так же чуть допилю функционал. Но в целом все основные моменты я вам показал.
По традиции жду ваших комментариев. Гудлак!
ссылка на оригинал статьи https://habr.com/ru/articles/925318/
Добавить комментарий