The Jerusalem PostLikud braces for turnout battle, divided over Netanyahu's chances on eve of Election Day - analysisESPNAre the Lions, Seahawks, Bengals and Giants on the right track?PunchEkiti education agency graduates 76, seeks more adult learnersESPN DeportesF1 ¿Qué necesita Kimi Antonelli para ser campeón en México?Bollywood HungamaCBFC faces reported data security scare as confidential documents linked to over 500 films are allegedly exposedDaily MaverickCYBER SAFETY: SA Human Rights Commission urges stronger laws to protect children onlineStraits Times SportScotland name squad for Nations ChampionshipZDF heuteEntdecken Sie das ZDF-NachrichtenstudioIl Fatto QuotidianoSinner ancora in vetta, ma ora Zverez può superarlo. Musetti non guadagna posizioni dopo la vittoria a Lione | La nuova classifica AtpGolem.deKurioses Gefährt: Polizei stoppt Sofa auf Lime-RollernRai NewsElezioni in Israele, la sfida è tutta su Netanyahu: proteste e sondaggi indicano uno stalloDeadline‘The Lunchbox’ Director Ritesh Batra Sets Saif Ali Khan, Vidya Balan & Mélanie Laurent For ‘Long Years Ago’; Filming Begins In Mumbai
The Daily Newsstand · Free, Always
Monday, October 26, 2026

35 файлов и одна кнопка, или как вытащить проект из веб-чата нейросети

Translate

Некоторые веб-чаты вроде DeepSeek и Qwen не умеют отдавать бинарные файлы для скачивания. Я проверил это на практике и написал промпт, который генерирует единый HTML со встроенным ZIP-генератором на чистом JavaScript. Пользователь открывает файл в браузере офлайн, нажимает кнопку - и получает zip-архив со всей структурой директорий и файлов. Дальше его можно поместить под git, развернуть на машине или сделать что угодно ещё. Для нетерпеливых - ссылка на промпт.

Зачем это понадобилось

Идея не всегда приходит в удобный момент. Часто - на ходу: в очереди, на прогулке, пока ждёшь жену в машине или ребёнка с тренировки. Я привык проработку подобных идей начинать на смартфоне в DeepSeek или Qwen с помощью мобильного приложения - быстро изложить верхнеуровневую концепцию, задать ряд вопросов, сформулировать гипотезы, пока мысль не успела раствориться. Если идея вырастает во что-то большее - появляется проект, который переезжает на отдельную виртуальную машину с opencode, где уже существует свой отдельный воркфлоу. Иногда, пока opencode пережёвывает основные проекты, я возвращаюсь к DeepSeek и Qwen, чтобы параллельно обсудить гипотезы и получить обратную связь. И это опять может сгенерировать очередной набор артефактов.

Недавно занимался оптимизацией промптов и конфигурации для opencode. Структура - агенты, скиллы, темплейты, директивы - всё это вылилось в 35 файлов. Принимать участие в чемпионате по спортивной копипасте не хотелось, и тогда созрело решение.

Как это устроено

Промпт генерирует единый HTML-файл с кратким описанием проекта и кнопкой «Download ZIP». Внутри - объект FILES, где ключ - путь внутри архива, значение - полное содержимое файла. Пустые директории передаются через .gitkeep. ZIP собирается на чистом JS: local file headers, central directory, end of central directory record, method store, CRC32 через lookup-таблицу. Никаких JSZip и прочих библиотек. UTF-8, general purpose bit 11 = 1 - чтобы имена читались корректно в 7-Zip, WinRAR, macOS Archive Utility и busybox unzip.

Результат работы промпта

Результат работы промпта

А почему не сгенерировать zip сразу?

Я отправил в Qwen, DeepSeek и Perplexity один и тот же промпт с просьбой сгенерировать zip с двумя небольшими текстовыми файлами. Результаты:

Модель

Ответ

Примечание

DeepSeek

«I can’t generate actual downloadable files or ZIP archives»

Предложил текст для копирования вручную

Qwen

Нет технической возможности отдать бинарный файл

Нет доступа к файловому хранилищу

Perplexity

Успех: stories.zip со ссылкой на скачивание

Сработал Perplexity Labs с песочницей и код-исполнением

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

Про консистентность

Протестировал пока только в DeepSeek. Четыре раза запустил промпт, получил четыре HTML, распаковал каждый zip в новую директорию и сравнил через diff -r. Вся структура и содержимое совпадают от директории к директории. Разница в объёме html-файлов связана с изменениями, внесёнными мной в промпт и, как следствие, в интерфейс получаемого html-файла (добавилось дерево проекта, описание файлов и их размеры).

$ ls -lah
итого 564K
drwxrwxr-x  6 evgeniy evgeniy 4,0K окт 26 13:02 .
drwxrwxr-x 22 evgeniy evgeniy 4,0K окт 26 10:59 ..
-rw-rw-r--  1 evgeniy evgeniy  83K окт 26 10:59 deepseek_html_0.html
-rw-rw-r--  1 evgeniy evgeniy  83K окт 26 12:20 deepseek_html_1.html
-rw-rw-r--  1 evgeniy evgeniy  91K окт 26 12:31 deepseek_html_2.html
-rw-rw-r--  1 evgeniy evgeniy  91K окт 26 12:56 deepseek_html_3.html
drwxrwxr-x  5 evgeniy evgeniy 4,0K окт 26 11:36 opencode-config-0
-rw-rw-r--  1 evgeniy evgeniy  42K окт 26 10:59 opencode-config-0.2.0_0.zip
-rw-rw-r--  1 evgeniy evgeniy  42K окт 26 12:21 opencode-config-0.2.0_1.zip
-rw-rw-r--  1 evgeniy evgeniy  42K окт 26 12:34 opencode-config-0.2.0_2.zip
-rw-rw-r--  1 evgeniy evgeniy  42K окт 26 12:56 opencode-config-0.2.0_3.zip
drwxrwxr-x  5 evgeniy evgeniy 4,0K окт 26 12:21 opencode-config-1
drwxrwxr-x  5 evgeniy evgeniy 4,0K окт 26 12:34 opencode-config-2
drwxrwxr-x  5 evgeniy evgeniy 4,0K окт 26 12:57 opencode-config-3
-rw-rw-r--  1 evgeniy evgeniy 2,7K окт 26 12:23 prompt-1.md
-rw-rw-r--  1 evgeniy evgeniy 2,4K окт 26 12:26 prompt-2.md
-rw-rw-r--  1 evgeniy evgeniy 1,7K окт 26 12:49 prompt-3.md
$ diff -r opencode-config-0 opencode-config-1
$ diff -r opencode-config-0 opencode-config-2
$ diff -r opencode-config-0 opencode-config-3
$ 

Предстоит ещё проверить в Qwen, Kimi и других, но текущий результат обнадёживает.

Бонусы, о которых не думал сразу

Имя zip-архива не всегда достаточно, чтобы понять, что находится внутри, особенно если имя содержит название проекта. Чтобы разобраться, нужно открыть архив, заглянуть как минимум в README.md, иногда и в код. На телефоне это ещё менее удобно, чем на компьютере. В то же время html-файл откроется практически любым браузером на любом устройстве и сразу будет содержать описание проекта, извлечённое из контекста общения с нейросетью.

Почему HTML остаётся лучшим выбором

Мой сценарий — «пользователь работает в веб-чате, модель уже знает структуру, нужно отдать всё одним архивом». В этом сценарии html-файл с встроенным zip:

  • работает там, где модель не умеет генерировать бинарные файлы (DeepSeek и Qwen на момент проверки через web-чат);

  • не заставляет переключаться на другую модель только ради архива (Perplexity может сгенерировать zip-архив, но работать с ним мне нравится меньше);

  • не требует установки расширений или доступа к файловой системе;

  • даёт пользователю полный контроль над структурой: иерархия, пустые директории, метаданные.

Про ограничения

DeepSeek, например, позволяет по кнопке продолжать генерацию, что позволяет собрать единый большой html-файл в несколько итераций. Но не факт, что все аналогичные решения дадут такую возможность. В противном случае единый файл, доступный для скачивания, может не собраться. Впрочем, сомнительно, что таким образом кто-то рассчитывает генерировать мегабайты контента. В крайнем случае можно попросить разбить на несколько архивов. Россыпь небольших файлов — наиболее вероятный сценарий, который мы как раз и закрываем текущим решением. Если же проект разрастётся до чего-то тяжёлого, с ним в любом случае нужно работать с помощью предпочитаемого вами harness.

Промпт

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

Содержимое промпта
Generate a single self-contained HTML file (opencode-config-bundle.html)
that lets the user download the opencode-config archive from a phone.

Layout, top to bottom:

1. Sticky header:
   - left: title "opencode-config bundle" (single line, truncates on
     narrow screens)
   - right: download button, compact, same line as title
   - below the title, small muted line: "v0.2.0 - 35 files"
   - status line under the button ("Preparing..." / "Done."), only
     visible after click, small font

2. Section "About": what/why/who, 2 short paragraphs.

3. Section "How to use": 4 numbered steps, short.

4. Section "Directory tree": full tree with ASCII branches, one line
   per file: name, short description, size (computed from FILES at
   load). Directories show a role, not a size. Wrapped in a
   horizontally scrollable <pre>.

5. Section "Prior art": two links with one-line notes.

6. Section "Disclaimer": same text as README.md.

7. Section "Authorship": Evgeniy Shumilov + DeepSeek, contacts.

8. <details> with plain file list.

9. <script src="jszip"> + handler. On click: build zip, trigger
   download, show status. If JSZip is undefined, show a clear error.

CSS:
- Header is position: sticky; top: 0; background: #fff; border-bottom:
  1px solid #ddd; padding: 0.6rem 1rem; z-index: 10.
- Header flex: title on the left, button on the right, gap 0.5rem.
- Button: compact on desktop (padding 0.5rem 0.9rem, font 0.9rem),
  full-width on very narrow screens via media query below 420px.
- Body has padding-top so sticky header does not overlap content.
- No emojis, ASCII only in the tree.
- Sizes in the tree are computed from FILES, single source of truth.

Как это встроено в рабочий процесс

На работе и дома я использую i3wm, а в качестве лаунчера — rofi. Для небольших промптов, которые нужны регулярно, у меня есть обвязка: каждый промпт — это отдельный shell-скрипт в ~/.config/rofi/scripts/, содержащий имя, описание и текст. Например, pr-step-by-step:

#!/bin/sh
#icon diodon
#name Step by step prompt

text='Guide me step by step. One command or snippet with a set of commands from you - one feedback from me. Do not use Russian comments, UTF icons, or emojis in snippets.'

nohup sh -c "printf '%s' '$text' | /usr/bin/xclip -selection clipboard" >/dev/null 2>&1 &

Вызывается rofi, вводится пара символов, например prom..., выбирается нужный промпт, enter — текст уходит в буфер обмена, дальше можно вставлять в чат. Бывает полезно и для opencode, например, когда несмотря на все проверки и базовые промпты локальная модель пытается выйти за пределы рабочей директории.

Скрытый текст

В конфиге rofi добавлен кастомный мод scripts:

configuration {
    modi: "drun,calc:qalc,scripts:~/.config/rofi/rofiselector,ssh,window,run";
    width: 35;
    show-icons: true;
    icon-theme: "papirus";
    lines: 10;
    fake-transparency: true;
    sidebar-mode: true;	
}
@import "/usr/share/rofi/themes/mytheme.rasi"

Сам ~/.config/rofi/rofiselector — скрипт, который формирует список промптов и обрабатывает выбор.

#!/bin/bash

ROOT_DIR="$HOME/.config/rofi/scripts"
STATE_FILE="/tmp/rofi_scripts_current_dir"

save_current_dir() {
    echo "$1" > "$STATE_FILE"
}

load_current_dir() {
    if [ -f "$STATE_FILE" ]; then
        local saved_dir=$(cat "$STATE_FILE")
        if [[ "$saved_dir" == "$ROOT_DIR"* ]]; then
            echo "$saved_dir"
            return
        fi
    fi
    echo "$ROOT_DIR"
}

get_metadata() {
    local file_path="$1"
    local metadata_type="$2"
    
    local line=$(grep -m 1 "^[[:space:]]*#$metadata_type" "$file_path" 2>/dev/null)
    
    if [ -n "$line" ]; then
        echo "$line" | sed "s/^[[:space:]]*#$metadata_type[[:space:]]*//" | xargs
    else
        echo ""
    fi
}

get_dir_icon() {
    local dir_path="$1"
    local icon_file="$dir_path/.icon"
    
    if [ -f "$icon_file" ]; then
        local icon=$(cat "$icon_file" | head -1 | xargs)
        if [ -n "$icon" ]; then
            echo "$icon"
            return
        fi
    fi
    
    echo "folder"
}

print_items() {
    local current_dir="$1"
    
    if [ "$current_dir" != "$ROOT_DIR" ]; then
        echo -en "..\0icon\x1fgo-up\n"
    fi
    
    for item in "$current_dir"/*; do
        [ -e "$item" ] || continue
        
        local name=$(basename "$item")
        
        if [ -d "$item" ]; then
            local icon=$(get_dir_icon "$item")
            echo -en "$name\0icon\x1f$icon\n"
        fi
    done
    
    for item in "$current_dir"/*; do
        [ -e "$item" ] || continue
        
        local name=$(basename "$item")
        
        if [ -x "$item" ] && [ -f "$item" ]; then
            local custom_name=$(get_metadata "$item" "name")
            local icon=$(get_metadata "$item" "icon")
            
            if [ -n "$custom_name" ]; then
                local display_name="$custom_name"
            else
                display_name="${name%.*}"
            fi
            
            if [ -n "$icon" ]; then
                echo -en "$display_name\0icon\x1f$icon\n"
            else
                echo -en "$display_name\0icon\x1fterminal\n"
            fi
        fi
    done
}

run_item() {
    local current_dir="$1"
    local selected="$2"
    
    if [ "$selected" = ".." ]; then
        local parent_dir=$(dirname "$current_dir")
        save_current_dir "$parent_dir"
        print_items "$parent_dir"
        return
    fi
    
    for item in "$current_dir"/*; do
        [ -e "$item" ] || continue
        
        local name=$(basename "$item")
        
        if [ -d "$item" ] && [ "$name" = "$selected" ]; then
            local target_dir="$current_dir/$name"
            if [ -d "$target_dir" ]; then
                save_current_dir "$target_dir"
                print_items "$target_dir"
            fi
            return
        fi
    done
    
    for script in "$current_dir"/*; do
        if [ -x "$script" ] && [ -f "$script" ]; then
            local script_name=$(basename "$script")
            local custom_name=$(get_metadata "$script" "name")
            local file_base="${script_name%.*}"
            
            if [ "$selected" = "$custom_name" ] || [ "$selected" = "$file_base" ]; then
                rm -f "$STATE_FILE"
                exec "$script"
                exit 0
            fi
        fi
    done
}

if [ -z "$1" ]; then
    CURRENT_DIR=$(load_current_dir)
    print_items "$CURRENT_DIR"
else
    CURRENT_DIR=$(load_current_dir)
    run_item "$CURRENT_DIR" "$1"
fi

Итог

Вставил промпт → скачал HTML → открыл → нажал кнопку → получил zip → хоть заноси под git, хоть отправляй в бэкапы. Множество файлов, одна кнопка, ноль копипасты.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.