Вопрос: файл лежит внутри твоей среды — как превратить его во внешнюю ссылку, которая открывается у пользователя в браузере?
Правило: ссылки внутренней среды (sandbox:/..., /mnt/data/...) у пользователя НЕ открываются. Это не доставка.
Доставка = внешний https://... URL, открывающийся без твоей среды.
| Задача | Канал |
|---|---|
| Показать файл НА ЭКРАНЕ браузера | К1 |
| Отдать большой файл на скачивание | К2 или К3 |
| Нет токена / нет прав | К3 или К4 |
| Максимальная надёжность | К1 + дубль в К3 |
Хотя бы один гейт не пройден → работа НЕ сделана. Отчёт: «НЕ ГОТОВО» + причина + UNKNOWN. Никаких «должно заработать».
{
"protocol": "SINGULYAR-SCREEN-DELIVERY", "version": "1.1",
"goal": "файл из среды ИИ -> внешний https URL в браузере пользователя",
"not_delivery": ["sandbox:/...", "/mnt/data/...", "локальный путь", "обещание без URL"],
"channels": {
"K1_github_pages": {
"repo": "rtynbirf/singulyar", "branch": "main",
"pages_base": "https://rtynbirf.github.io/singulyar/",
"raw_base": "https://raw.githubusercontent.com/rtynbirf/singulyar/main/",
"auth": {
"method": "github_device_flow", "client_id": "178c6fc778ccc68e1d6a", "scope": "repo",
"device_code": "POST https://github.com/login/device/code",
"token": "POST https://github.com/login/oauth/access_token (grant_type=...device_code)",
"user_entry": "https://github.com/login/device",
"token_rules": "в памяти сессии; никогда в файлах/коммитах/логах" },
"api": {
"get_file": "GET /repos/.../contents/{path_urlencoded}?ref=main",
"put_file_le1mb": "PUT /repos/.../contents/{path_urlencoded} {message, content:base64, branch, sha?}",
"blob": "POST /repos/.../git/blobs {content:base64, encoding:base64}",
"tree": "POST /repos/.../git/trees {base_tree, tree:[{path, mode:100644, type:blob, sha}]}",
"commit": "POST /repos/.../git/commits {message, tree, parents}",
"ref": "PATCH /repos/.../git/refs/heads/main {sha}",
"pages_latest": "GET /repos/.../pages/builds/latest" } },
"K2_release_assets": {
"create": "POST /repos/.../releases {tag_name, name}",
"upload": "POST https://uploads.github.com/repos/.../releases/{id}/assets?name={file}",
"limits": "до 2 ГБ на ассет" },
"K3_filebin": {
"post": "POST https://filebin.net/{bin}/{filename} body = байты файла -> 201",
"get": "GET https://filebin.net/{bin}/{filename} -> 302 -> 200",
"rules": "bin создаётся сам; ~7 дней; публично; не для секретов" },
"K4_native": "только настоящий https://, открывающийся без твоей песочницы" },
"gates": ["G1_выложено", "G2_финальный_URL", "G3_VERIFY_200_и_маркер", "G4_REPORT"],
"file_rules": {
"on_screen": "самодостаточный .html без внешних зависимостей",
"for_machine": ".md",
"naming": "латиница/цифры/_/-; кириллица с percent-encoding; без пробелов",
"size": "К1: <=1MB Contents API; >1MB Git Data API; >95MB — К2/К3",
"secrets": "запрещены в файлах, коммитах и публичных bin-ах" }
}
# 1) запросить код устройства
curl -s -X POST https://github.com/login/device/code \
-H "Accept: application/json" \
-d "client_id=178c6fc778ccc68e1d6a&scope=repo"
# {"device_code":"...","user_code":"ABCD-1234","verification_uri":"https://github.com/login/device","interval":5}
# 2) показать человеку ТОЛЬКО user_code:
# «Открой https://github.com/login/device и введи код ABCD-1234»
# 3) опрашивать токен с паузой interval:
curl -s -X POST https://github.com/login/oauth/access_token \
-H "Accept: application/json" \
-d "client_id=178c6fc778ccc68e1d6a&device_code=<DEVICE_CODE>&grant_type=urn:ietf:params:oauth:grant-type:device_code"
# authorization_pending — ждать; slow_down — пауза +5 c; успех — {"access_token":"ghu_..."}
TOKEN="<access_token>" # только в памяти сессии
.html (стили и скрипты внутри, без CDN, работает офлайн)..md._/-; кириллица допустима, но в URL — percent-encoding; без пробелов.Метод A — Contents API (до 1 МБ):
ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$PATH_IN_REPO")
B64=$(base64 -w0 "$FILE")
SHA=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.github.com/repos/rtynbirf/singulyar/contents/$ENC?ref=main" \
| python3 -c "import json,sys;d=json.load(sys.stdin);print(d.get('sha','') if isinstance(d,dict) else '')")
# body.json = {"message":"ADD FAYL.html","content":B64,"branch":"main"[,"sha":SHA если заменяешь]}
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" \
-d @body.json "https://api.github.com/repos/rtynbirf/singulyar/contents/$ENC"
# успех: HTTP 200/201 + "commit":{"sha":"..."} — запомнить
Метод B — Git Data API (больше 1 МБ):
BLOB_SHA = POST /git/blobs {content: base64(файл), encoding:"base64"}
BASE_SHA = GET /git/ref/heads/main → .object.sha
TREE_SHA = POST /git/trees {base_tree:BASE_SHA, tree:[{path, mode:"100644", type:"blob", sha:BLOB_SHA}]}
COMMIT = POST /git/commits {message, tree:TREE_SHA, parents:[BASE_SHA]}
= PATCH /git/refs/heads/main {sha:COMMIT, force:false}
ADD/UPDATE <имя>: <суть>.for i in $(seq 1 48); do
ST=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.github.com/repos/rtynbirf/singulyar/pages/builds/latest" \
| python3 -c "import json,sys;print(json.load(sys.stdin).get('status',''))")
[ "$ST" = "built" ] && break
sleep 5
done # максимум 240 c; errored — читать error в ответе
# К1:
URL="https://rtynbirf.github.io/singulyar/$ENC"
CODE=$(curl -sL -o out.bin -w "%{http_code}" "$URL?v=$(date +%s)") # ?v= против CDN-кэша
MARK=$(grep -c "КОНТРОЛЬНАЯ_СТРОКА" out.bin)
# гейт: CODE=200 и MARK>=1
# К3:
curl -sL "https://filebin.net/$BIN/файл" # 302 → 200 — нормально, браузер проходит сам
rtynbirf.github.io/singulyar/<путь>; машины — raw.githubusercontent.com/rtynbirf/singulyar/main/<путь>.CLAIM: <что должно открыться и по какому URL>
CODE: <файл, канал, путь/commit sha, размер>
TEST: HTTP <код>; маркер: да/нет; сборка: <время>
RESULT: ГОТОВО → <финальный URL> | НЕ ГОТОВО → причина
UNKNOWN: <что не проверено>
REL=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" \
-d '{"tag_name":"files-20261002","name":"Файлы"}' \
"https://api.github.com/repos/rtynbirf/singulyar/releases" | python3 -c "import json,sys;print(json.load(sys.stdin)['id'])")
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/octet-stream" \
--data-binary @"БОЛЬШОЙ_ФАЙЛ" \
"https://uploads.github.com/repos/rtynbirf/singulyar/releases/$REL/assets?name=%D0%A4%D0%90%D0%99%D0%9B"
# VERIFY browser_download_url → 200. Проверено: ассет 42 483 085 Б в v1.12 отдаётся анонимно 200.
BIN="sv$(date +%s)" # bin создаётся автоматически
curl -s -o /dev/null -w "%{http_code}" -X POST --data-binary @"ФАЙЛ" \
-H "Content-Type: application/octet-stream" "https://filebin.net/$BIN/ФАЙЛ" # ждать 201
curl -sL "https://filebin.net/$BIN/ФАЙЛ" # 302 → 200
function deliver(file, path):
if no_token_or_size_gt_95MB: # канал К3
bin = "sv" + now
assert POST filebin.net/{bin}/{name} == 201
body = GET filebin.net/{bin}/{name} (follow redirect)
assert status == 200 and MARKER in body
return report(RESULT="ГОТОВО", url)
token = device_flow("178c6fc778ccc68e1d6a", scope="repo")
sha = GET /contents/{quote(path)}?ref=main → .sha | None
r = PUT /contents/{quote(path)} {message, base64(file), branch, sha?}
assert r in (200,201); commit = r.commit.sha
wait ≤240s: GET /pages/builds/latest → status == "built"
body = GET pages_url + "?v=" + now
assert 200 and MARKER in body
return report(CLAIM, CODE(commit), TEST, RESULT="ГОТОВО", url)
VERIFIED (проект СИНГУЛЯР): Device Flow · Contents API · Git Data API · Pages · Release-ассеты (42 МБ) · filebin.net (POST 201 / GET 302→200). Состояние: Pages отвечает 200; в корне репо СИНГУЛЯР_01…22.
Выполняй TEST сам в своей среде. Непроверенное помечай UNKNOWN.
СИНГУЛЯР · ПРОТОКОЛ ВЫВОДА НА ЭКРАН · v1.1 · машина-исполнителю: читай JSON-контракт из §4 первым.
Вся документация дома: ДОКУМЕНТАЦИЯ.html