t-261009160906: анти-гонка уровней min/hour на входящих TG — привязка артефакта к сообщению (-m), guard в deliver_artifact, reply_artifact в read model, тест гонки (8 PASS) + концепт ИИШНИЦЫ

This commit is contained in:
Simbiot Harness 2026-10-09 16:16:09 +00:00
parent 37fe77f923
commit 58cc3785c6
27 changed files with 2912 additions and 22 deletions

View file

@ -40,6 +40,8 @@
.hero h1{font-size:clamp(30px,4.5vw,46px);line-height:1.1;letter-spacing:-.02em;max-width:16ch}
.hero p{margin-top:14px;max-width:48ch;color:var(--muted);font-size:17px}
.hero .btn{display:inline-block;margin-top:22px;background:var(--acc);color:#fff;padding:13px 26px;border-radius:6px;font-weight:600}
.hero .btn.ghost{background:transparent;color:var(--acc);border:1.5px solid var(--acc);margin-left:10px}
.hero .btn.ghost:hover{background:var(--acc-soft)}
.hero .btn:hover{filter:brightness(1.08)}
.rhythm{flex:1;min-width:260px;display:flex;align-items:flex-end;gap:4px;height:120px;opacity:.9}
.rhythm span{flex:1;background:var(--acc);border-radius:3px 3px 0 0;animation:grow 2.8s ease-in-out infinite}
@ -94,9 +96,25 @@
.dark input:focus{border-color:var(--acc)}
.dark button{background:var(--acc);border:none;color:#fff;padding:12px 22px;border-radius:6px;font:inherit;font-weight:600;cursor:pointer}
.plan-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-top:8px}
.plan-card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:24px}
.plan-card h3{font-size:20px}
.plan-price{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:6px 0 14px}
.plan-card ul{list-style:none;margin:0 0 18px}
.plan-card li{padding:6px 0 6px 22px;position:relative;font-size:14.5px;color:#3d3a34}
.plan-card li:before{content:"✓";position:absolute;left:0;color:var(--acc);font-weight:700}
.plan-card .btn{display:inline-block;background:var(--acc);color:#fff;padding:10px 18px;border-radius:6px;font-weight:600}
.plan-card.featured{border:2px solid var(--acc);box-shadow:0 6px 24px rgba(14,122,95,.12)}
.plan-card code{font-family:var(--mono);font-size:12px;background:var(--acc-soft);color:var(--acc);padding:1px 5px;border-radius:4px}
#reqform{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;max-width:760px}
#reqform button{margin-top:4px}
footer{padding:30px 0 40px;color:var(--muted);font-size:13.5px}
footer .wrap{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
footer .proto{font-family:var(--mono);font-size:12px}
footer .flinks{display:flex;gap:18px;font-size:13.5px}
footer .flinks a{text-decoration:underline;color:var(--muted)}
footer .flinks a:hover{color:var(--acc)}
#toast{position:fixed;left:50%;bottom:24px;transform:translate(-50%,80px);background:var(--ink);color:#fff;padding:12px 20px;border-radius:8px;font-size:14px;opacity:0;transition:.25s ease;z-index:9}
#toast.show{transform:translate(-50%,0);opacity:1}
@ -113,7 +131,7 @@
</head>
<body>
<div class="note">Прототип витрины — шаг 1 из плана Shkor beauty. Наполнение демонстрационное, оплата не подключена.</div>
<div class="note">Витрина работает: AI-ассистент подбирает уход, заявки принимаются (Lite-план). Оплата онлайн — на шаге 2.</div>
<header>
<div class="wrap">
@ -126,6 +144,7 @@
<div class="hlinks">
<a href="#catalog">Каталог</a>
<a href="#brands">Бренды</a>
<a href="#plans">Магазинам</a>
<a href="#sub">Подписка</a>
</div>
</div>
@ -143,9 +162,9 @@
<div class="hero">
<div class="wrap">
<div>
<h1>Корейский уход — с полки до двери за 1 день</h1>
<p>Каталог проверенных K-beauty брендов в Минске: очищение, тонизирование, сыворотки, SPF. Самовывоз и доставка по Беларуси.</p>
<a class="btn" href="#catalog">Смотреть каталог</a>
<h1>Корейский уход — AI подберёт, привезём за 1 день</h1>
<p>Каталог проверенных K-beauty брендов в Минске + AI-ассистент: разберёт состояние кожи, соберёт рутину и примет заявку. Доставка по Беларуси.</p>
<a class="btn" href="#catalog">Смотреть каталог</a> <a class="btn ghost" href="#request">Оставить заявку</a>
</div>
<div class="rhythm" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
@ -321,6 +340,51 @@
</div>
</section>
<section id="plans">
<div class="wrap">
<h2>Магазинам корейской косметики</h2>
<p class="sub">Shkor beauty — это AI-ассистент витрины (B2B): подбор ухода, заказы и заявки — единое API для вашего сайта и любых агентов. Оператор сервиса — Shkor Labs LLC.</p>
<div class="plan-grid">
<div class="plan-card">
<h3>Lite</h3>
<p class="plan-price">базовый план</p>
<ul>
<li>Витрина с каталогом и брендами</li>
<li>Форма заявки → общая база заказов</li>
<li>Заявки приходят с пометкой <code>plan=lite</code></li>
<li>Договор с магазином лично: телефон/мессенджер</li>
</ul>
<a class="btn" href="#request">Начать с заявки</a>
</div>
<div class="plan-card featured">
<h3>Full</h3>
<p class="plan-price">полный каталог + заказы</p>
<ul>
<li>Всё из Lite</li>
<li>Полный каталог и корзина</li>
<li>Онлайн-оплата и статусы доставки</li>
<li>Агентный API: analyze → recommend → order</li>
</ul>
<a class="btn" href="#request">Подключить Full</a>
</div>
</div>
</div>
</section>
<section class="dark" id="request">
<div class="wrap">
<h2>Оставить заявку</h2>
<p class="sub">Расскажите, что нужно — подберём уход или подключим магазин. Заявка пишется в общую базу заказов, ответим в течение дня.</p>
<form id="reqform">
<input type="text" name="name" placeholder="Имя" required>
<input type="tel" name="phone" placeholder="+375 XX XXX-XX-XX" required>
<input type="text" name="note" placeholder="Что подобрать? (сухая кожа, SPF, тонер…)">
<button type="submit">Отправить</button>
</form>
<p class="sub" id="reqstatus" style="margin-top:10px"></p>
</div>
</section>
<section class="dark" id="sub">
<div class="wrap">
<h2>Подписка на поступления</h2>
@ -334,7 +398,13 @@
<footer>
<div class="wrap">
<div>Shkor beauty — витрина корейской косметики. Прототип шага 1: каталог без платежей.</div>
<div>Shkor beauty — витрина корейской косметики + AI-ассистент. Оператор сервиса: <b>Shkor Labs LLC</b> (Wyoming, USA) · <a href="mailto:alex.shkor@gmail.com" style="text-decoration:underline">alex.shkor@gmail.com</a></div>
<div class="flinks">
<a href="/privacy.html">Privacy Policy</a>
<a href="/terms.html">Terms of Service</a>
<a href="/disclaimer.html">Disclaimer</a>
<a href="/about.html">About &amp; Contacts</a>
</div>
<div class="proto">собрано харнесом Simbiot · кооператив 42 · cil.by</div>
</div>
</footer>
@ -347,7 +417,25 @@ function toast(t){
clearTimeout(el._t);el._t=setTimeout(function(){el.classList.remove('show')},2200);
}
document.querySelectorAll('.p button').forEach(function(b){
b.addEventListener('click',function(){toast('Прототип: заказ и оплата будут на шаге 2')});
b.addEventListener('click',function(){
var card=b.closest('.pb'), h=card?card.querySelector('h3'):null;
var note=document.getElementById('reqform').note;
if(h&&note) note.value='Интересует: '+h.textContent;
document.getElementById('request').scrollIntoView({behavior:'smooth'});
toast('Добавлено в заявку — оставьте контакты ниже');
});
});
document.getElementById('reqform').addEventListener('submit',function(e){
e.preventDefault();
var f=e.target, st=document.getElementById('reqstatus');
st.textContent='Отправляем…';
fetch('/api/call',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({name:'submit_request',arguments:{name:f.name.value.trim(),phone:f.phone.value.trim(),note:f.note.value.trim()}})
}).then(function(r){return r.json()}).then(function(d){
var res=d.result||d;
if(res.order_id){st.textContent='Заявка принята: '+res.order_id+'. Свяжемся в течение дня.';f.reset();}
else{st.textContent='Не получилось: '+(res.error||JSON.stringify(res));}
}).catch(function(err){st.textContent='Сеть недоступна — напишите на alex.shkor@gmail.com';});
});
</script>

View file

@ -255,6 +255,8 @@ def place_order(args: dict) -> dict:
oid = "SB-" + uuid.uuid4().hex[:8].upper()
order = {
"order_id": oid,
"plan": args.get("plan") if args.get("plan") in ("full", "lite") else "full",
"order_id": oid,
"items": lines,
"subtotal": round(total, 2),
@ -283,9 +285,47 @@ def check_order(args: dict) -> dict:
o = _load_orders().get(oid)
if not o:
return {"error": f"заказ {oid} не найден", "known": list(_load_orders())[:10]}
return {"order_id": oid, "status": o["status"], "payment": o["payment"]["status"],
"total": o["total"], "delivery": o["delivery"]["label"], "eta": o["delivery"]["eta"],
"items": [{"title": l["title"], "qty": l["qty"]} for l in o["items"]]}
out = {"order_id": oid, "plan": o.get("plan", "full"), "status": o["status"],
"payment": (o.get("payment") or {}).get("status")}
if o.get("kind") == "request":
out.update({"kind": "request", "note": o.get("note", ""),
"products": o.get("products", []),
"customer": o.get("customer", {})})
return out
out.update({"total": o["total"], "delivery": o["delivery"]["label"], "eta": o["delivery"]["eta"],
"items": [{"title": l["title"], "qty": l["qty"]} for l in o["items"]]})
return out
def submit_request(args: dict) -> dict:
"""Заявка из витрины Lite-плана: без корзины/оплаты, пишем в ту же базу orders.json с plan=lite.
Витрина формы («Хочу это», «Подобрать рутину») → request SB-R-XXXXXXXX → менеджер/агент
договаривается лично. Когда магазин переходит на Full — та же запись дооформляется
в заказ (check_order видит статус request).
"""
contact = (args.get("phone") or args.get("email") or "").strip()
if not contact and not (args.get("name") or "").strip():
return {"error": "нужен хотя бы один контакт: phone или email (или name)"}
oid = "SB-R-" + uuid.uuid4().hex[:8].upper()
rec = {
"order_id": oid,
"plan": "lite",
"kind": "request",
"note": (args.get("note") or "").strip(),
"products": args.get("products") or [],
"customer": {"name": args.get("name") or "", "phone": args.get("phone") or "",
"email": args.get("email") or "", "address": args.get("address") or ""},
"payment": {"status": "none", "method": "none", "pay_link": None},
"total": 0.0,
"status": "request",
"created": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
orders = _load_orders()
orders[oid] = rec
_save_orders(orders)
return {"order_id": oid, "status": "request", "plan": "lite",
"next": "Магазин свяжется по контакту. Статус: tools/call check_order {\"order_id\": \"%s\"}" % oid}
def delivery_options(args: dict) -> dict:
@ -339,7 +379,8 @@ TOOLS = {
["self_report?", "symptoms?", "photo_report?", "age?", "climate?"]),
"recommend": ("Подбор продуктов: concerns+skin_type+budget_max → карточки", ["concerns?", "skin_type?", "budget_max?", "limit?"]),
"search": ("Поиск по каталогу", ["query?", "category?", "limit?"]),
"place_order": ("Оформить заказ", ["items[{id,qty}]", "delivery?", "name?", "phone?", "address?", "pay_method?"]),
"place_order": ("Оформить заказ (Full-план)", ["items[{id,qty}]", "delivery?", "name?", "phone?", "address?", "pay_method?", "plan?"]),
"submit_request": ("Заявка из витрины Lite-плана: без корзины/оплаты → orders.json plan=lite, статус request", ["phone?", "email?", "name?", "note?", "products?"]),
"check_order": ("Статус заказа и доставки", ["order_id"]),
"delivery_options": ("Варианты доставки и цены", ["cart_total?"]),
"usage_instructions": ("Инструкции по применению + порядок рутины", ["product_ids"]),
@ -365,7 +406,7 @@ def handle(request: dict) -> dict:
if name not in TOOLS:
return {"error": f"неизвестный инструмент {name!r}", "available": list(TOOLS)}
fn = {"analyze_skin": analyze_skin, "recommend": recommend, "search": search,
"place_order": place_order, "check_order": check_order,
"place_order": place_order, "submit_request": submit_request, "check_order": check_order,
"delivery_options": delivery_options, "usage_instructions": usage_instructions}[name]
try:
result = fn(arguments if isinstance(arguments, dict) else {})

View file

@ -58,12 +58,24 @@ def serve_http(addr: str) -> None:
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*") # витрина на другом домене: форма заявки → /call
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
self.wfile.write(body)
def do_OPTIONS(self) -> None: # preflight для формы витрины (fetch с другого origin)
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.send_header("Content-Length", "0")
self.end_headers()
def do_GET(self) -> None: # health / открыть в браузере
if self.path in ("/", "/health"):
self._send(200, {"ok": True, "service": "shkor-assistant", "version": 1})
self._send(200, {"ok": True, "service": "shkor-assistant", "version": 1,
"tools": "POST /tools/list", "call": "POST /call"})
else:
self._send(404, {"error": "not found", "routes": ["POST /tools/list", "POST /call"]})

View file

@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""Генератор юр-страниц Shkor beauty (t-261008154151-faa550).
Privacy Policy / Terms of Service / Cookie & Disclaimer / About & Contacts.
Единый шаблон = тот же визуальный язык, что у витрины. Оператор сервиса:
Shkor Labs LLC (Wyoming, USA), alex.shkor@gmail.com.
"""
from pathlib import Path
OUT = Path("/var/www/beauty")
OUT.mkdir(parents=True, exist_ok=True)
CSS = """
:root{--ink:#1c1a17;--paper:#faf7f2;--line:#e4ddd2;--acc:#0e7a5f;--muted:#6f6a61;
--sans:"Segoe UI",-apple-system,Arial,sans-serif;--mono:ui-monospace,Menlo,Consolas,monospace}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);line-height:1.65;font-size:16px}
.wrap{max-width:820px;margin:0 auto;padding:0 20px}
header{border-bottom:1px solid var(--line);background:#fff}
.hbar{display:flex;align-items:center;justify-content:space-between;padding:16px 0}
.logo{font-size:20px;font-weight:700}
.logo span{color:var(--acc)}
.logo small{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
nav a{margin-left:16px;font-size:14px;color:var(--muted)}
nav a:hover{color:var(--acc)}
main{padding:40px 0 60px}
h1{font-size:clamp(26px,4vw,36px);letter-spacing:-.02em;margin-bottom:6px}
.meta{font-family:var(--mono);font-size:12px;color:var(--muted);margin-bottom:28px}
h2{font-size:19px;margin:28px 0 10px}
p,li{color:#3d3a34;margin-bottom:10px}
ul{padding-left:22px}
a{color:var(--acc)}
.card{background:#fff;border:1px solid var(--line);border-radius:10px;padding:22px;margin-top:24px}
footer{padding:26px 0 40px;border-top:1px solid var(--line);color:var(--muted);font-size:13.5px}
footer .links{display:flex;gap:18px;margin-bottom:8px}
footer .links a{text-decoration:underline;color:var(--muted)}
footer .proto{font-family:var(--mono);font-size:12px;margin-top:6px}
"""
def page(slug: str, title: str, updated: str, body: str) -> None:
html = f"""<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title} — Shkor beauty</title>
<meta name="description" content="{title} сервиса Shkor beauty. Оператор: Shkor Labs LLC.">
<style>{CSS}</style>
</head>
<body>
<header>
<div class="wrap hbar">
<a class="logo" href="/">Shkor <span>beauty</span><small>корейская косметика · Минск</small></a>
<nav>
<a href="/#catalog">Каталог</a><a href="/#plans">Магазинам</a><a href="/#request">Заявка</a>
</nav>
</div>
</header>
<main class="wrap">
<h1>{title}</h1>
<div class="meta">Last updated: {updated} · v1.0 · Operator: Shkor Labs LLC (Wyoming, USA)</div>
{body}
</main>
<footer>
<div class="wrap">
<div class="links">
<a href="/privacy.html">Privacy Policy</a>
<a href="/terms.html">Terms of Service</a>
<a href="/disclaimer.html">Disclaimer</a>
<a href="/about.html">About &amp; Contacts</a>
</div>
<div>Оператор сервиса: <b>Shkor Labs LLC</b> (Wyoming, USA) · <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a></div>
<div class="proto">собрано харнесом Simbiot · кооператив 42 · cil.by</div>
</div>
</footer>
</body>
</html>"""
(OUT / f"{slug}.html").write_text(html, encoding="utf-8")
print("ok", OUT / f"{slug}.html")
D = "2026-10-08"
page("privacy", "Privacy Policy", D, """
<p>Настоящая Политика конфиденциальности объясняет, какие данные собирает сервис Shkor beauty
(далее — «Сервис») и как они используются. Оператор Сервиса — <b>Shkor Labs LLC</b>, компания,
зарегистрированная в штате Вайоминг, США (далее — «Оператор»). Контакт: <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a>.</p>
<h2>1. Какие данные мы собираем</h2>
<ul>
<li><b>Заявка (Lite-план):</b> имя, телефон и текст заявки, которые вы сами указываете в форме «Оставить заявку».</li>
<li><b>Заказ (Full-план):</b> имя, телефон, адрес доставки и состав заказа.</li>
<li><b>Обращения:</b> письма на email Оператора и переписка в мессенджерах — в объёме, необходимом для ответа.</li>
<li><b>Технические данные:</b> анонимная статистика посещений (страницы-рефереры, устройство, страна) — без персональных идентификаторов.</li>
</ul>
<h2>2. Чего мы НЕ собираем</h2>
<ul>
<li>Данные банковских карт: при онлайн-оплате (Full-план) платёж проводит независимый платёжный агент;
данные карты вводятся на его защищённой странице и Оператору не передаются.</li>
<li>Фотографии кожи без явного согласия: если вы передаёте фотоотчёт AI-ассистенту, он обрабатывается
для анализа состояния кожи и не публикуется и не передаётся третьим лицам.</li>
</ul>
<h2>3. Зачем мы используем данные</h2>
<ul>
<li>связаться с вами по заявке и довести её до результата (подбор, заказ, доставка);</li>
<li>исполнить заказ и уведомлять о его статусе;</li>
<li>улучшать каталог и подбор ассистента на обезличенной статистике.</li>
</ul>
<h2>4. Хранение и срок</h2>
<p>Записи заявок и заказов хранятся в защищённой базе Оператора не дольше, чем это необходимо для
исполнения заказа и требований бухгалтерии (по умолчанию — до 3 лет), после чего удаляются или
обезличиваются.</p>
<h2>5. Передача третьим лицам</h2>
<p>Данные передаются только: (а) службе доставки — имя, телефон и адрес вашего заказа;
(б) платёжному агенту — минимально необходимый платёжный контекст; (в) в случаях, предусмотренных законом.</p>
<h2>6. Cookie</h2>
<p>Сервис использует минимальные cookie и локальное хранилище: сессия корзины и анонимная статистика.
Подробнее — в <a href="/disclaimer.html">Disclaimer</a>.</p>
<h2>7. Ваши права</h2>
<p>Вы можете запросить доступ к своим данным, их исправление или удаление письмом на
<a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a>. Мы отвечаем в течение 30 дней.</p>
<h2>8. Изменения</h2>
<p>Актуальная версия Политики всегда доступна на этой странице; существенные изменения датируются выше.</p>
""")
page("terms", "Terms of Service", D, """
<p>Условия использования сервиса Shkor beauty (далее — «Условия»). Оператор Сервиса — <b>Shkor Labs LLC</b>
(Wyoming, USA), контакт <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a>. Используя Сервис,
вы соглашаетесь с настоящими Условиями.</p>
<h2>1. Что такое Сервис</h2>
<p>Shkor beauty — витрина корейской косметики с AI-ассистентом: подбор ухода, заявки и заказы с доставкой
по Минску и Беларуси. Сервис предлагается в двух планах:</p>
<ul>
<li><b>Lite (базовый):</b> витрина + форма заявки. Заявка фиксируется в общей базе заказов, после чего
магазин связывается с вами лично для уточнения ассортимента, цены и способа расчёта.</li>
<li><b>Full:</b> полный каталог, корзина, онлайн-оплата и статусы доставки.</li>
</ul>
<h2>2. Заявки и заказы</h2>
<ul>
<li>Заявка (Lite) не является подтверждённой сделкой: цена и наличие подтверждаются при обратной связи.</li>
<li>Заказ (Full) считается принятым после подтверждения оплаты или согласия на оплату при получении.</li>
<li>Цены указаны в белорусских рублях (BYN) и могут меняться; актуальная цена — на витрине в момент заказа.</li>
</ul>
<h2>3. Доставка</h2>
<p>Курьер по Минску, Европочта по Беларуси и самовывоз. Сроки и стоимость показываются при оформлении
заявки/заказа; бесплатная доставка — от 80 BYN по каталогу витрины.</p>
<h2>4. Возврат и обмен</h2>
<ul>
<li>Товар надлежащего качества (без следов использования, в оригинальной упаковке) — возврат/обмен
в течение 14 дней с момента получения.</li>
<li>Средства гигиены и косметика с нарушенной упаковкой возврату не подлежат.</li>
<li>Для возврата напишите на <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a> с номером заказа.</li>
</ul>
<h2>5. Интеллектуальная собственность</h2>
<p>Материалы витрины, AI-подбор и программный код ассистента принадлежат Оператору. Товарные знаки
брендов принадлежат их правообладателям.</p>
<h2>6. Ограничение ответственности</h2>
<p>Сервис предоставляется «как есть». AI-подбор средств — рекомендательный характер и не является
медицинской консультацией (см. <a href="/disclaimer.html">Disclaimer</a>). Оператор не несёт
ответственности за индивидуальные реакции кожи и решения, принятые на основе рекомендаций без
ознакомления с составами и инструкциями.</p>
<h2>7. Применимое право</h2>
<p>Настоящие Условия регулируются правом, применимым к месту регистрации Оператора (Wyoming, USA),
без ущерба для обязательных потребительских прав покупателей по законодательству места доставки.</p>
""")
page("disclaimer", "Cookie &amp; Disclaimer", D, """
<p>Страница объединяет политику cookie и важные дисклеймеры сервиса Shkor beauty. Оператор —
<b>Shkor Labs LLC</b> (Wyoming, USA), <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a>.</p>
<h2>1. Не медицинская консультация</h2>
<div class="card">
<p><b>AI-ассистент и материалы витрины НЕ являются медицинской консультацией, диагностикой или лечением.</b>
Подбор средств — эвристическая рекомендация на основе вашего самоотчёта. Она не заменяет консультацию
дерматолога или врача. При заболеваниях кожи, беременности, приёме лекарственных средств и выраженных
реакциях — проконсультируйтесь со специалистом до применения средств.</p>
</div>
<h2>2. Косметика и индивидуальные реакции</h2>
<ul>
<li>Перед первым применением сделайте тест на чувствительность (небольшой участок кожи, 24–48 часов).</li>
<li>Активы (ретинол, кислоты, витамин C) вводите постепенно; ретинол — только на ночь, SPF утром обязателен.</li>
<li>Ознакомьтесь с составом на упаковке: при аллергии на компонент средство не используйте.</li>
</ul>
<h2>3. Cookie</h2>
<ul>
<li><b>Необходимые:</b> сессия корзины и защита форм. Не отключаются.</li>
<li><b>Аналитические:</b> анонимная статистика посещений для улучшения витрины. Без персональных идентификаторов и без передачи рекламным сетям.</li>
<li><b>Рекламных и трекинговых cookie сторонних сетей — нет.</b></li>
</ul>
<p>Большинство браузеров позволяет управлять cookie в настройках; отключение необходимых cookie
может сломать корзину и отправку заявки.</p>
<h2>4. Внешние ссылки</h2>
<p>Витрина может ссылаться на сайты брендов и службы доставки — за их содержание Оператор
ответственности не несёт.</p>
<h2>5. Вопросы</h2>
<p>Любые вопросы по дисклеймерам и cookie — <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a>.</p>
""")
page("about", "About &amp; Contacts", D, """
<p><b>Shkor beauty</b> — AI-ассистент для магазинов корейской косметики: витрина, которая сама
разбирает состояние кожи клиента, собирает рутину и доводит заявку до заказа.</p>
<h2>Кто мы</h2>
<p>Оператор сервиса — <b>Shkor Labs LLC</b>, компания, зарегистрированная в штате Вайоминг, США.
Проект развивается совместно с инженерным кооперативом «42» (cil.by): витрина и ассистент собраны
ритмичным агентом Simbiot.</p>
<h2>Что мы делаем</h2>
<ul>
<li><b>Витрина K-beauty:</b> каталог проверенных брендов — Anua, Beauty of Joseon, JMsolution,
SKIN627, Biodance и другие, доставка по Минску и Беларуси.</li>
<li><b>AI beauty-ассистент (B2B):</b> агентный API analyze → recommend → order, который любой
магазин или AI-агент может подключить к своему сайту (MCP/STDIO, HTTP, CLI).</li>
<li><b>Два плана подключения:</b> <b>Lite</b> — витрина + заявки в общую базу; <b>Full</b> — полный
каталог, корзина, онлайн-оплата и статусы доставки.</li>
</ul>
<h2>Контакты</h2>
<div class="card">
<ul>
<li>Email: <a href="mailto:alex.shkor@gmail.com">alex.shkor@gmail.com</a></li>
<li>Заявка: форма на <a href="/#request">главной странице</a> — ответим в течение дня</li>
<li>Юрлицо-оператор: Shkor Labs LLC (Wyoming, USA)</li>
</ul>
</div>
<h2>Магазинам: подключение ассистента</h2>
<p>Ассистент подключается к любому сайту или AI-агенту за один шаг: JSONL поверх STDIO (MCP-стиль),
HTTP JSON API или разовый CLI-вызов. Каталог масштабируется без правок кода. Для пилота напишите
на email — подключим витрину с формой заявок (Lite) и покажем e2e-сценарий заказа.</p>
""")

158
artifacts/shkor/orders.json Normal file
View file

@ -0,0 +1,158 @@
{
"SB-R-1D8C99C8": {
"order_id": "SB-R-1D8C99C8",
"plan": "lite",
"kind": "request",
"note": "Хочу сыворотку для сухой кожи",
"products": [
"p01"
],
"customer": {
"name": "Тест Тестов",
"phone": "+375291234567",
"email": "",
"address": ""
},
"payment": {
"status": "none",
"method": "none",
"pay_link": null
},
"total": 0.0,
"status": "request",
"created": "2026-10-08T16:03:25Z"
},
"SB-55B78647": {
"order_id": "SB-55B78647",
"plan": "full",
"items": [
{
"id": "p05",
"title": "Успокаивающий тонер с сердечником Centella Toner 77%",
"qty": 1,
"price": 62.0,
"sum": 62.0
}
],
"subtotal": 62.0,
"delivery": {
"key": "minsk_courier",
"label": "Курьер по Минску",
"eta": "сегодня/завтра",
"price": 7.0,
"free_from": 80.0,
"charged": 7.0
},
"total": 69.0,
"customer": {
"name": "",
"phone": "",
"address": ""
},
"payment": {
"status": "pending",
"method": "cash_on_delivery",
"pay_link": null
},
"status": "placed",
"created": "2026-10-08T16:03:25Z"
},
"SB-8AC083A5": {
"order_id": "SB-8AC083A5",
"plan": "lite",
"items": [
{
"id": "p03",
"title": "Солнцезащитный гель Relief Sun SPF50+ PA++++",
"qty": 1,
"price": 49.0,
"sum": 49.0
}
],
"subtotal": 49.0,
"delivery": {
"key": "minsk_courier",
"label": "Курьер по Минску",
"eta": "сегодня/завтра",
"price": 7.0,
"free_from": 80.0,
"charged": 7.0
},
"total": 56.0,
"customer": {
"name": "",
"phone": "",
"address": ""
},
"payment": {
"status": "pending",
"method": "cash_on_delivery",
"pay_link": null
},
"status": "placed",
"created": "2026-10-08T16:03:25Z"
},
"SB-R-7B23D4D8": {
"order_id": "SB-R-7B23D4D8",
"plan": "lite",
"kind": "request",
"note": "Проверка формы с витрины (e2e)",
"products": [],
"customer": {
"name": "E2E Тест",
"phone": "+375290000000",
"email": "",
"address": ""
},
"payment": {
"status": "none",
"method": "none",
"pay_link": null
},
"total": 0.0,
"status": "request",
"created": "2026-10-08T16:08:02Z"
},
"SB-B352DCBF": {
"order_id": "SB-B352DCBF",
"plan": "full",
"items": [
{
"id": "p05",
"title": "Успокаивающий тонер с сердечником Centella Toner 77%",
"qty": 1,
"price": 62.0,
"sum": 62.0
},
{
"id": "p03",
"title": "Солнцезащитный гель Relief Sun SPF50+ PA++++",
"qty": 1,
"price": 49.0,
"sum": 49.0
}
],
"subtotal": 111.0,
"delivery": {
"key": "minsk_courier",
"label": "Курьер по Минску",
"eta": "сегодня/завтра",
"price": 7.0,
"free_from": 80.0,
"charged": 0.0
},
"total": 111.0,
"customer": {
"name": "Тест",
"phone": "+375XX0000000",
"address": ""
},
"payment": {
"status": "pending",
"method": "cash_on_delivery",
"pay_link": null
},
"status": "placed",
"created": "2026-10-08T16:08:11Z"
}
}

22
cli.py
View file

@ -90,6 +90,9 @@ def main():
help="ключ идемпотентности (по умолчанию file:<абсолютный путь>)")
p_dfl.add_argument("--force", action="store_true",
help="отправить даже если уже доставлен (повторная доставка вручную)")
p_dfl.add_argument("-m", "--msg", dest="msg", default=None,
help="связать доставку с входящим m-NNN (анти-гонка уровней, "
"t-261009160906): если на него уже отвечено иным артефактом — SKIP")
p_pro = sub.add_parser("proactive", help="инициативная реплика: ритм/тема/отправка (t-260930073419)")
p_pro.add_argument("--force", action="store_true",
help="отправить сейчас, игнорируя окно/интервал/тишину")
@ -361,11 +364,28 @@ def main():
print(f"FAIL: файл не найден: {args.file}")
sys.exit(1)
aid = args.key or f"file:{os.path.abspath(args.file)}"
# Анти-гонка уровней (t-261009160906): если доставка привязана к
# входящему m-NNN и на него уже отвечено ДРУГИМ артефактом —
# не отправляем второй файл на тот же запрос (гонка min/hour).
if args.msg and not args.force:
model = es.run_projections()
m = (model.get("inbox") or {}).get(args.msg)
if m is not None and m.get("replied") and m.get("reply_artifact") not in (None, "", aid):
es.append_event("harness", aid, "DeliverySkipped", {
"artifact_id": aid, "kind": "file",
"reason": "msg-already-answered-by-other-artifact",
"msg": args.msg,
"reply_artifact": m.get("reply_artifact"),
})
print(f"SKIP: на {args.msg} уже отвечено другим артефактом "
f"({m.get('reply_artifact')}) — используй --force для повтора")
sys.exit(0)
def _send():
return tg_inbox.send_document_to_chat(args.file, caption=args.caption)
ok = commands.deliver_artifact(aid, kind="file", send=_send,
owner="cli:tg-deliver-file",
caption=args.caption, force=args.force)
caption=args.caption, force=args.force,
msg=args.msg)
print(f"OK: файл доставлен ({aid})" if ok
else f"SKIP: уже доставлен ({aid}) — используй --force для повтора")
elif args.cmd == "proactive":

0
harness/*.db Normal file
View file

View file

@ -351,7 +351,7 @@ def _delivery_text(task: dict, answer: str) -> str:
def deliver_artifact(artifact_id: str, kind: str, send, owner: str = "manual",
caption: str = "", force: bool = False) -> bool:
caption: str = "", force: bool = False, msg: str = None) -> bool:
"""Идемпотентная доставка ПРОИЗВОЛЬНОГО артефакта в чат (t-260930083937).
Рут-кейс дублей: мин-тик шёл файлом «скинь сюда» curl'ом sendDocument —
@ -373,6 +373,21 @@ def deliver_artifact(artifact_id: str, kind: str, send, owner: str = "manual",
"reason": "already-delivered",
})
return False
# Анти-гонка уровней (t-261009160906): артефакт привязан к входящему
# m-NNN, на которое уже отвечено ДРУГИМ артефактом → SKIP. Два уровня
# (min/hour) на один запрос не должны слать два разных файла; dedup по
# artifact_id=file:<путь> их не различает (разные пути = разные ключи).
if msg and not force:
model = es.run_projections()
m = (model.get("inbox") or {}).get(msg)
if (m is not None and m.get("replied")
and m.get("reply_artifact") not in (None, "", artifact_id)):
es.append_event("harness", artifact_id, "DeliverySkipped", {
"artifact_id": artifact_id, "kind": kind,
"reason": "msg-already-answered-by-other-artifact",
"msg": msg, "reply_artifact": m.get("reply_artifact"),
})
return False
if force:
# ручной повтор — явное решение человека: лок недавней доставки
# не должен блокировать его на TTL (120с). Снимаем свой же захват
@ -394,10 +409,13 @@ def deliver_artifact(artifact_id: str, kind: str, send, owner: str = "manual",
# в ledger остаётся фактом; повторный force просто отправит ещё раз,
# но ledger при этом не «молодеет» (kind/ts не переписываются)
es.record_delivery(artifact_id, kind=kind)
es.append_event("harness", artifact_id, "ArtifactDelivered", {
ev = {
"artifact_id": artifact_id, "kind": kind, "tg_ok": True,
"caption": (caption or "")[:500], "owner": owner,
})
}
if msg:
ev["msg"] = msg # анти-гонка уровней (t-261009160906)
es.append_event("harness", artifact_id, "ArtifactDelivered", ev)
return True

View file

@ -548,6 +548,15 @@ def _apply_event(model: dict, ev: dict):
if m:
m["replied"] = True
m["reply_text"] = data.get("text", "")
elif etype == "ArtifactDelivered":
# Анти-гонка уровней (t-261009160906): связка «файл → входящее».
# Если доставка артефакта помечена msg=m-NNN, фиксируем её в
# инбоксе — второй артефакт на то же входящее будет SKIP'нут.
msg = (data or {}).get("msg") if isinstance(data, dict) else None
if msg:
m = model.setdefault("inbox", {}).get(msg)
if m is not None and not m.get("reply_artifact"):
m["reply_artifact"] = data.get("artifact_id")
elif etype == "MessageSeen":
# индикация взятия в работу (e-261002202857): флаг seen → reaction 👀
# ставится ровно один раз, повторные маркеры сообщений не найдут

View file

@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Simbiot — The Studio OS</title>
<meta name="description" content="A dedicated AI team — engineers, video producers, content makers — plus the OS that runs projects, treasury, decisions and meetings. White-label, flat rate.">
<meta property="og:title" content="Simbiot — The Studio OS">
<meta property="og:description" content="Hire nobody. Ship everything. An AI team for your studio plus the OS that runs it.">
<meta property="og:url" content="https://simbiot.cil.by/">
<meta property="og:type" content="website">
<meta property="og:image" content="https://simbiot.cil.by/og.png">
<meta name="twitter:card" content="summary_large_image">
<style>
/* ── fingerprint: offset rail · 660px measure · charcoal+lime · serif+mono · machined 4/10 · tinted panels · index motif · one entrance + state wash ── */
:root{
--bg:#0E100C; --panel:#151812; --panel2:#1A1E17;
--line:#272C22; --line2:#333A2C;
--ink:#E9ECE1; --dim:#ADB6A2; --faint:#7E8873;
--acc:#C9F24D; --acc-ink:#111408;
--ok:#7BD88F; --run:#E8C44A; --wait:#8A9382;
--serif:Georgia,'Iowan Old Style','Times New Roman',serif;
--mono:ui-monospace,'SF Mono','Cascadia Mono',Menlo,Consolas,'Liberation Mono',monospace;
--sans:-apple-system,system-ui,'Segoe UI',Roboto,sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased}
a{color:inherit}
:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:2px}
.wrap{max-width:1060px;margin:0 auto;padding:0 22px}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}
/* ── left rail (desktop) ── */
.layout{display:grid;grid-template-columns:168px minmax(0,1fr);gap:44px;max-width:1060px;margin:0 auto;padding:26px 22px 0}
.rail{position:sticky;top:26px;align-self:start;display:flex;flex-direction:column;gap:22px;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase}
.rail .brand{display:flex;align-items:center;gap:9px;color:var(--ink);text-decoration:none;font-weight:600}
.rail .brand .dot{width:9px;height:9px;border-radius:50%;background:var(--acc);flex:none}
.rail nav{display:flex;flex-direction:column;gap:2px}
.rail nav a{color:var(--faint);text-decoration:none;padding:9px 10px 9px 0;min-height:36px;display:flex;align-items:center;border-bottom:1px solid var(--line)}
.rail nav a:hover{color:var(--ink)}
.rail .railnote{color:var(--faint);line-height:1.9;border-top:1px solid var(--line);padding-top:14px}
.rail .railnote b{color:var(--dim);font-weight:500}
/* top bar (mobile) */
.topbar{display:none}
/* ── buttons: compact scale (26px / 11.5px) ── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:28px;padding:0 14px;border-radius:4px;border:1px solid var(--line2);background:var(--panel2);color:var(--ink);font-family:var(--mono);font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;text-decoration:none;cursor:pointer;min-height:40px}
.btn.primary{background:var(--acc);border-color:var(--acc);color:var(--acc-ink);font-weight:600}
.btn:focus-visible{outline-offset:1px}
@media(hover:hover) and (pointer:fine){.btn:hover{border-color:var(--acc)} .btn.primary:hover{filter:brightness(1.06)}}
.btn:active{transform:scale(.96)}
/* ── hero ── */
.hero{padding:30px 0 26px;border-bottom:1px solid var(--line)}
.kicker{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:18px}
.kicker .dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--ok);margin-right:8px}
h1{font-family:var(--serif);font-weight:400;font-size:clamp(38px,6vw,62px);line-height:1.04;letter-spacing:-.015em;max-width:14ch}
h1 em{font-style:italic;color:var(--acc)}
.lede{color:var(--dim);max-width:52ch;margin:18px 0 22px;font-size:15.5px}
.cta-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.cta-note{font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;color:var(--faint);text-transform:uppercase}
/* ── signature artifact: live task ladder ── */
.console{margin-top:30px;background:var(--panel);border:1px solid var(--line2);border-radius:10px;overflow:hidden}
.console .bar{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.console .bar .ticks{display:flex;gap:5px}
.console .bar .ticks i{width:8px;height:8px;border-radius:2px;background:var(--line2)}
.console .bar .live{margin-left:auto;display:flex;align-items:center;gap:7px;color:var(--dim)}
.console .bar .live i{width:7px;height:7px;border-radius:50%;background:var(--ok);animation:pulse 2.4s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
.metrics{display:grid;grid-template-columns:repeat(3,1fr);border-bottom:1px solid var(--line)}
.metric{padding:12px 14px;border-right:1px solid var(--line)}
.metric:last-child{border-right:none}
.metric .v{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:19px;color:var(--ink)}
.metric .v .u{color:var(--faint);font-size:12px}
.metric .k{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);margin-top:3px}
.ladder{display:flex;flex-direction:column}
.trow{display:grid;grid-template-columns:52px minmax(0,1fr) 96px 88px;gap:12px;align-items:center;min-height:44px;padding:8px 14px;border-bottom:1px solid var(--line);font-size:13.5px;color:var(--dim);cursor:pointer;text-align:left;background:transparent;border-left:none;border-right:none;border-top:none;width:100%;font-family:var(--sans);transition:background-color .25s,color .25s}
.trow:last-child{border-bottom:none}
.trow:hover{background:var(--panel2);color:var(--ink)}
.trow .sz{font-family:var(--mono);font-size:11px;color:var(--faint)}
.trow .name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.trow .name b{color:var(--ink);font-weight:600}
.trow .st{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--wait);display:flex;align-items:center;gap:7px}
.trow .st i{width:7px;height:7px;border-radius:50%;background:var(--wait);flex:none}
.trow[data-st="running"] .st{color:var(--run)} .trow[data-st="running"] .st i{background:var(--run)}
.trow[data-st="done"] .st{color:var(--ok)} .trow[data-st="done"] .st i{background:var(--ok)}
.trow .who{font-family:var(--mono);font-size:11px;color:var(--faint);text-align:right}
.console .foot{padding:9px 14px;font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);border-top:1px solid var(--line)}
.console .foot b{color:var(--dim);font-weight:500}
/* ── sections ── */
section{padding:44px 0;border-bottom:1px solid var(--line)}
.shead{display:flex;align-items:baseline;gap:14px;margin-bottom:22px}
.sidx{font-family:var(--mono);font-size:11px;color:var(--acc);letter-spacing:.1em}
h2{font-family:var(--serif);font-weight:400;font-size:clamp(24px,3.4vw,34px);line-height:1.12;letter-spacing:-.01em}
.snote{margin-left:auto;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.lead{color:var(--dim);max-width:56ch;margin:-8px 0 24px;font-size:15px}
/* team ledger (rows, not cards) */
.ledger{border:1px solid var(--line2);border-radius:10px;background:var(--panel);overflow:hidden}
.lrow{display:grid;grid-template-columns:44px minmax(0,1.1fr) minmax(0,1.4fr) 150px;gap:16px;padding:16px;border-bottom:1px solid var(--line);align-items:baseline}
.lrow:last-child{border-bottom:none}
.lrow .idx{font-family:var(--mono);font-size:11px;color:var(--faint)}
.lrow .nm{font-size:16px;font-weight:600}
.lrow .nm span{display:block;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--acc);margin-top:4px;font-weight:400}
.lrow .ds{color:var(--dim);font-size:13.5px}
.lrow .tags{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);text-align:right;line-height:2}
/* OS definition list */
.dl{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:0 40px}
.dl .item{display:grid;grid-template-columns:40px minmax(0,1fr);gap:12px;padding:15px 0;border-bottom:1px solid var(--line)}
.dl .item .k{font-family:var(--mono);font-size:10.5px;color:var(--acc);letter-spacing:.08em;padding-top:3px}
.dl .item .t{font-weight:600;font-size:14.5px}
.dl .item .d{color:var(--dim);font-size:13.5px;margin-top:3px}
/* included rows */
.inc{display:flex;flex-direction:column}
.inc .irow{display:grid;grid-template-columns:26px minmax(0,1fr);gap:14px;padding:13px 0;border-bottom:1px solid var(--line)}
.inc .irow:last-child{border-bottom:none}
.inc .mark{color:var(--acc);font-family:var(--mono);font-size:12px;padding-top:2px}
.inc .t{font-weight:600;font-size:14.5px}
.inc .t span{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin-left:10px;font-weight:400}
.inc .d{color:var(--dim);font-size:13.5px;margin-top:2px}
/* pricing */
.plans{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:18px}
.plan{border:1px solid var(--line2);border-radius:10px;background:var(--panel);padding:22px;display:flex;flex-direction:column;gap:14px}
.plan.hot{border-color:var(--acc)}
.plan .pt{display:flex;align-items:center;gap:10px;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
.plan.hot .pt{color:var(--acc)}
.price{font-family:var(--serif);font-size:40px;line-height:1}
.price .per{font-family:var(--mono);font-size:12px;color:var(--faint);letter-spacing:.08em}
.plan ul{list-style:none;display:flex;flex-direction:column;gap:8px;color:var(--dim);font-size:13.5px}
.plan li{display:grid;grid-template-columns:18px 1fr;gap:10px}
.plan li::before{content:"▸";color:var(--acc);font-family:var(--mono);font-size:11px}
.plan .btn{margin-top:auto}
.plan .fine{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);text-align:center}
/* footer */
footer{padding:34px 0 46px}
.fgrid{display:flex;flex-wrap:wrap;gap:14px 30px;align-items:center;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.fgrid a{color:var(--dim);text-decoration:none;border-bottom:1px solid var(--line2)}
.fgrid a:hover{color:var(--acc);border-color:var(--acc)}
.fgrid .sp{margin-left:auto}
/* entrance: one reserved-space fade-up */
.in{opacity:0;transform:translateY(8px);animation:in .5s ease forwards}
.in.d1{animation-delay:.08s}.in.d2{animation-delay:.16s}.in.d3{animation-delay:.24s}
@keyframes in{to{opacity:1;transform:none}}
/* ── mobile: single column ── */
@media(max-width:860px){
.layout{grid-template-columns:minmax(0,1fr);gap:0;padding:0 16px}
.rail{display:none}
.topbar{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase}
.topbar .brand{display:flex;align-items:center;gap:8px;color:var(--ink);text-decoration:none;font-weight:600;margin-right:auto}
.topbar .brand .dot{width:8px;height:8px;border-radius:50%;background:var(--acc)}
.topbar .btn{min-height:40px;height:28px}
.wrap{padding:0 16px}
.layout > main{padding:0}
.hero,.layout main section,.layout main footer{padding-left:0;padding-right:0}
.metrics{grid-template-columns:repeat(3,minmax(0,1fr))}
.metric .v{font-size:15px}
.trow{grid-template-columns:38px minmax(0,1fr) 84px;gap:8px;padding:10px 12px}
.trow .who{display:none}
.lrow{grid-template-columns:minmax(0,1fr);gap:6px;padding:14px 16px}
.lrow .tags{text-align:left;line-height:1.9}
.dl{grid-template-columns:minmax(0,1fr)}
.plans{grid-template-columns:minmax(0,1fr)}
h1{font-size:clamp(34px,9vw,44px)}
}
@media(prefers-reduced-motion:reduce){
*{animation:none!important;transition:none!important}
.in{opacity:1;transform:none}
html{scroll-behavior:auto}
}
</style>
</head>
<body>
<header class="topbar">
<a class="brand" href="#top"><span class="dot"></span>Simbiot</a>
<a class="btn" href="https://cil.by/cabinet">Sign in</a>
<a class="btn primary" href="https://simbiot.cil.by/#pricing">Get started</a>
</header>
<div class="layout" id="top">
<aside class="rail">
<a class="brand" href="#top"><span class="dot"></span>Simbiot</a>
<nav aria-label="Sections">
<a href="#team">Team</a>
<a href="#os">The OS</a>
<a href="#included">Included</a>
<a href="#pricing">Pricing</a>
</nav>
<div class="railnote">
<b>Coop 42</b> runtime<br>
reflex · minute<br>
hour · day<br><br>
<a class="btn" style="margin-top:10px" href="https://cil.by/cabinet">Sign in</a>
</div>
</aside>
<main>
<!-- 00 · hero + live ladder -->
<div class="hero">
<p class="kicker in"><span class="dot"></span>Studio OS · live</p>
<h1 class="in d1">Hire nobody.<br><em>Ship everything.</em></h1>
<p class="lede in d2">A dedicated AI team — engineers, video producers, content makers — plus the operating system that runs them: projects, treasury, decisions, meetings. White-label, under your brand.</p>
<div class="cta-row in d3">
<a class="btn primary" href="https://simbiot.cil.by/#pricing">Start your studio — $999/mo</a>
<a class="btn" href="https://demo.cil.by">Live demo</a>
<span class="cta-note">flat rate · cancel anytime</span>
</div>
<div class="console in d3" aria-label="Live task ladder demo">
<div class="bar">
<span class="ticks" aria-hidden="true"><i></i><i></i><i></i></span>
<span>studio ops · task ladder</span>
<span class="live"><i></i>3 agents online</span>
</div>
<div class="metrics">
<div class="metric"><div class="v" id="mDone">4<span class="u"> done</span></div><div class="k">tasks this cycle</div></div>
<div class="metric"><div class="v">4<span class="u">-cadence</span></div><div class="k">reflex→day runtime</div></div>
<div class="metric"><div class="v">S·M·L</div><div class="k">task sizes</div></div>
</div>
<div class="ladder" id="ladder"></div>
<div class="foot">hour digest 23:10 Minsk · <b>all actions signed</b> · click a row to re-dispatch</div>
</div>
</div>
<!-- 01 · team -->
<section id="team">
<div class="shead"><span class="sidx">01</span><h2>A staff of specialists, not a chatbot.</h2><span class="snote">dedicated servers</span></div>
<p class="lead">Every agent runs on its own server with private memory, tools, email and Matrix chat. You brief them like employees — they onboard onto your idea and start shipping.</p>
<div class="ledger">
<div class="lrow">
<span class="idx">/01</span>
<div class="nm">Cody<span>Engineer</span></div>
<div class="ds">Writes code, deploys services, maintains git repositories. MVPs, landing pages, bots, APIs, integrations — from brief to production.</div>
<div class="tags">full-stack · devops · git</div>
</div>
<div class="lrow">
<span class="idx">/02</span>
<div class="nm">ReelsUp<span>Video producer</span></div>
<div class="ds">Cuts long videos and podcasts into vertical clips for Reels and Shorts. Transcription, highlight detection, automated editing.</div>
<div class="tags">whisper · clips · shorts</div>
</div>
<div class="lrow">
<span class="idx">/03</span>
<div class="nm">Roy<span>Content maker</span></div>
<div class="ds">Promotional content on a conveyor: reels with 3D characters, scripts, voiceovers, visuals — brand-consistent by default.</div>
<div class="tags">3d · tts · scripts</div>
</div>
</div>
</section>
<!-- 02 · the os -->
<section id="os">
<div class="shead"><span class="sidx">02</span><h2>Agents build. The OS manages.</h2><span class="snote">your domain</span></div>
<p class="lead">Projects, money, decisions and meetings in one transparent system — under your studio's name.</p>
<div class="dl">
<div class="item"><span class="k">S.01</span><div><div class="t">Project registry</div><div class="d">A card for every venture: decisions, ownership, agents, activity feed.</div></div></div>
<div class="item"><span class="k">S.02</span><div><div class="t">Git repo per project</div><div class="d">Code and docs in a dedicated repository with a web editor. Nothing lost in chats.</div></div></div>
<div class="item"><span class="k">S.03</span><div><div class="t">Treasury &amp; wallets</div><div class="d">Revenue share flows in automatically. Every payout transparent and auditable.</div></div></div>
<div class="item"><span class="k">S.04</span><div><div class="t">Decisions &amp; voting</div><div class="d">Allocate resources by vote. Cycles, quorum, full decision history.</div></div></div>
<div class="item"><span class="k">S.05</span><div><div class="t">Meetings with AI minutes</div><div class="d">Video calls with recording, transcription, summaries — into the registry.</div></div></div>
<div class="item"><span class="k">S.06</span><div><div class="t">Time &amp; task tracking</div><div class="d">What every agent completed and where work went — a daily digest in your inbox.</div></div></div>
</div>
</section>
<!-- 03 · included -->
<section id="included">
<div class="shead"><span class="sidx">03</span><h2>Full stack. Nothing extra to buy.</h2><span class="snote">flat rate</span></div>
<div class="inc">
<div class="irow"><span class="mark">✓</span><div><div class="t">Stripe payments <span>live</span></div><div class="d">Card payments for what your agents build — subscriptions, one-offs, invoices. A working checkout on day one.</div></div></div>
<div class="irow"><span class="mark">✓</span><div><div class="t">AI inference included <span>flat</span></div><div class="d">All models run on our gateway — no API keys, no vendor bills, no metered surprises.</div></div></div>
<div class="irow"><span class="mark">✓</span><div><div class="t">White-label portal <span>your brand</span></div><div class="d">Your subdomain, your studio's name up front. Every deliverable ships under your identity.</div></div></div>
<div class="irow"><span class="mark">✓</span><div><div class="t">The agent harness <span>4-cadence</span></div><div class="d">Reflex, minute, hour, day — escalation, memory, semantic knowledge base. Coop-tested daily.</div></div></div>
<div class="irow"><span class="mark">✓</span><div><div class="t">One-click export <span>no lock-in</span></div><div class="d">Take everything with you, anytime. Ready in ~10 minutes after payment.</div></div></div>
</div>
</section>
<!-- 04 · pricing -->
<section id="pricing">
<div class="shead"><span class="sidx">04</span><h2>One plan each. Everything included.</h2><span class="snote">founding cohort</span></div>
<div class="plans">
<div class="plan hot">
<div class="pt">Personal · first come</div>
<div class="price">$99.9 <span class="per">/ month</span></div>
<ul>
<li>1 dedicated AI agent on its own server</li>
<li>Private Matrix chat — web + mobile</li>
<li>Inference, hosting, updates included</li>
<li>One-click export, no lock-in</li>
</ul>
<a class="btn primary" href="https://simbiot.cil.by/#pricing">Get my agent</a>
<div class="fine">secure checkout by Stripe · cancel anytime</div>
</div>
<div class="plan">
<div class="pt">Studio</div>
<div class="price">$999 <span class="per">/ month</span></div>
<ul>
<li>12 AI agents — engineers, producers, content</li>
<li>White-label portal on your subdomain</li>
<li>Projects, treasury, decisions, meetings — one system</li>
<li>Team members free · priority support</li>
</ul>
<a class="btn" href="https://simbiot.cil.by/#pricing">Start your studio</a>
<div class="fine">deployed the same day · self-hosted? talk to us</div>
</div>
</div>
</section>
<footer>
<div class="fgrid">
<span>© 2026 Simbiot · Cooperative 42</span>
<a href="https://demo.cil.by">Demo</a>
<a href="https://cil.by/cabinet">Cabinet</a>
<a href="mailto:portal@cil.by">portal@cil.by</a>
<span class="sp">cil.by</span>
</div>
</footer>
</main>
</div>
<script>
/* signature artifact: a miniature of the real task ladder.
Reserved geometry: rows are pre-rendered, only state cell swaps. */
(function(){
var TASKS=[
{sz:'L', name:'Redesign pricing section copy', who:'cody'},
{sz:'M', name:'Weekly digest to stakeholders', who:'os'},
{sz:'S', name:'Fix og-image on landing', who:'cody'},
{sz:'M', name:'Escalation: router burn > budget', who:'os'}
];
var ST=['queued','running','done'], LABEL={queued:'queued',running:'running',done:'done'};
var states=[1,0,2,1], doneCount=4, ladder=document.getElementById('ladder');
if(!ladder) return;
var reduced=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var rows=TASKS.map(function(t,i){
var b=document.createElement('button');
b.type='button'; b.className='trow'; b.setAttribute('data-st',LABEL[states[i]]);
b.setAttribute('aria-label',t.name+' — '+LABEL[states[i]]+'. Re-dispatch.');
b.innerHTML='<span class="sz">['+t.sz+']</span>'+
'<span class="name"><b>'+t.name+'</b></span>'+
'<span class="st"><i></i>'+LABEL[states[i]]+'</span>'+
'<span class="who">'+t.who+'</span>';
b.addEventListener('click',function(){step(i);});
ladder.appendChild(b);
return b;
});
function paint(i){
var s=LABEL[states[i]];
rows[i].setAttribute('data-st',s);
rows[i].getElementsByClassName('st')[0].innerHTML='<i></i>'+s;
}
function step(i){
if(states[i]===2){states[i]=1;paint(i);return}
if(states[i]===1){states[i]=2;doneCount++;var m=document.getElementById('mDone');
if(m)m.innerHTML=doneCount+'<span class="u"> done</span>';paint(i);return}
states[i]=1;paint(i);
}
var cur=0;
function tick(){ if(states[cur]!==2){step(cur);} cur=(cur+1)%TASKS.length; }
if(!reduced){ var t=setInterval(tick,2200); document.addEventListener('visibilitychange',function(){ document.hidden?clearInterval(t):t=setInterval(tick,2200); }); }
})();
</script>
</body>
</html>

View file

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ИИШНИЦА · Панельная сессия «Общее видение» — концепт кооператива 42</title>
<style>
:root{
--paper:#faf8f3; --ink:#15150f; --signal:#FF4F00;
--dim:#6b675e; --line:#d8d3c8; --card:#f3f0e8;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{background:var(--paper);color:var(--ink);font:16px/1.62 -apple-system,"Segoe UI",Roboto,Arial,sans-serif;-webkit-font-smoothing:antialiased}
.mono,code{font-family:ui-monospace,"JetBrains Mono","SF Mono",Menlo,Consolas,monospace}
.wrap{max-width:980px;margin:0 auto;padding:56px 24px 100px}
header{border-bottom:3px solid var(--ink);padding-bottom:26px;margin-bottom:36px}
.kicker{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.22em;color:var(--signal);font-weight:700;text-transform:uppercase}
h1{font-size:clamp(34px,6vw,58px);line-height:1.02;font-weight:800;letter-spacing:-.02em;margin:10px 0 14px;text-transform:uppercase}
.sub{font-size:17px;color:var(--dim);max-width:740px}
.badges{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}
.badge{border:1px solid var(--ink);padding:5px 12px;font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.08em;text-transform:uppercase;background:var(--paper)}
.badge.s{background:var(--ink);color:var(--paper)}
section{margin:52px 0 0}
.snum{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--signal);letter-spacing:.2em;font-weight:700}
h2{font-size:26px;font-weight:800;letter-spacing:-.01em;margin:4px 0 6px;text-transform:uppercase}
.lead{color:var(--dim);max-width:780px;margin-bottom:20px}
h3{font-size:14px;text-transform:uppercase;letter-spacing:.06em;margin:24px 0 10px;border-left:3px solid var(--signal);padding-left:10px}
p{margin:10px 0;max-width:840px}
.cardgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:14px;margin:18px 0}
.card{border:1px solid var(--ink);background:var(--card);padding:16px 16px 14px}
.card .cn{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em}
.card h4{font-size:15px;margin:6px 0;font-weight:800}
.card p{font-size:13.5px;color:var(--dim);margin:0;line-height:1.55}
.card p b{color:var(--ink)}
.timeline{margin:22px 0;border:1px solid var(--ink);background:#fff}
.trow{display:grid;grid-template-columns:120px 1fr;border-bottom:1px solid var(--line)}
.trow:last-child{border-bottom:none}
.trow .t{font-family:ui-monospace,Menlo,monospace;font-weight:700;padding:12px 14px;border-right:1px solid var(--line);display:flex;align-items:center;font-size:13px}
.trow .d{padding:12px 16px}
.trow .d b{display:block}
.trow .d span{font-size:13.5px;color:var(--dim);display:block;margin-top:2px}
.note{border:1px solid var(--ink);border-left:6px solid var(--signal);background:#fff;padding:14px 18px;margin:20px 0;max-width:860px}
.note b{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--signal)}
ul{margin:8px 0 8px 18px;font-size:14.5px}
li{margin:6px 0}
li::marker{color:var(--signal)}
.mission{border:3px double var(--ink);background:#fff;padding:26px 28px;margin:22px 0}
.mission p{font-size:19px;line-height:1.5;max-width:840px}
.mission .src{font-size:12px;color:var(--dim);font-family:ui-monospace,Menlo,monospace;margin-top:12px}
.qbox{border:1px solid var(--ink);background:var(--card);padding:16px 20px;margin:12px 0}
.qbox .q{font-size:16.5px;font-weight:600;line-height:1.45}
.qbox .m{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);letter-spacing:.15em;font-weight:700}
footer{margin-top:60px;border-top:1px solid var(--line);padding-top:18px;color:var(--dim);font-size:12.5px}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="kicker">ИИШНИЦА · Панельная сессия · Концепт</div>
<h1>Общее видение</h1>
<p class="sub">Формат панельной сессии на ИИШНИЦЕ: миссия 42 задаёт рамку, спикеры из разных индустрий описывают будущее — и панель сходится к названию общего видения.</p>
<div class="badges">
<span class="badge s">90 минут</span>
<span class="badge">5–6 спикеров</span>
<span class="badge">3 круга + зал</span>
<span class="badge">Миссия 42</span>
<span class="badge">Минск · cil.by</span>
</div>
</header>
<section id="idea">
<div class="snum">01 / ИДЕЯ</div>
<h2>Суть формата</h2>
<p class="lead">Не классическая панель с вопросами модератора, а три круга с одной целью: собрать из отдельных картин будущего — общую. Миссия 42 озвучивается в начале как рамка, объединяющая любые индустрии и любые ответы.</p>
<div class="mission">
<p><b>Миссия «42»:</b> все миссии одинаково важны. Миссия «42» объединяет абсолютно все возможные миссии. Нет неважных миссий — все люди важны, у каждого своя миссия.</p>
<div class="src">— кооператив «Друзья Коллективного Разума», Минск · cil.by</div>
</div>
<p>Почему это работает: каждый спикер говорит о <b>своей</b> индустрии — и потому не конкурирует с соседом за «правильный ответ». А третий круг, название общего видения, превращает панель из набора мнений в один артефакт, который остаётся после сессии: в заголовках, фото и цитатах.</p>
</section>
<section id="format">
<div class="snum">02 / ФОРМАТ</div>
<h2>Три круга + зал</h2>
<p class="lead">Каждый круг имеет свою функцию: раскрыть, связать, назвать. Тайминг дан для 6 спикеров; при 5 спикерах сессия ужимается до ~80 минут.</p>
<div class="timeline">
<div class="trow">
<div class="t">00:00–00:05</div>
<div class="d"><b>Открытие и миссия 42</b><span>Модератор (или Alex) озвучивает миссию 42 как рамку сессии: все миссии одинаково важны — потому каждый взгляд на будущее важен.</span></div>
</div>
<div class="trow">
<div class="t">00:05–00:29</div>
<div class="d"><b>Круг 1 — «Моё будущее» · 4 мин × 6</b><span>Каждый спикер описывает будущее своей индустрии в самом дальнем горизонте, в который он может заглянуть. Без вопросов, без перебиваний. 24 минуты.</span></div>
</div>
<div class="trow">
<div class="t">00:29–00:41</div>
<div class="d"><b>Круг 2 — «Что понравилось» · 2 мин × 6</b><span>Каждый подсвечивает, что понравилось у других спикеров. Не спорить, а связывать: где он увидел пересечение со своим будущим. 12 минут.</span></div>
</div>
<div class="trow">
<div class="t">00:41–00:55</div>
<div class="d"><b>Круг 3 — «Название общего видения»</b><span>Панель соглашается назвать общее видение — одно название для всех картин будущего. Модератор помогает сформулировать и фиксирует финальную формулировку. 14 минут.</span></div>
</div>
<div class="trow">
<div class="t">00:55–01:25</div>
<div class="d"><b>Вопросы из зала</b><span>Зал спрашивает — спикеры отвечают. Модератор держит рамку миссии: любой вопрос важен, любая индустрия равна. 30 минут.</span></div>
</div>
<div class="trow">
<div class="t">01:25–01:30</div>
<div class="d"><b>Закрытие</b><span>Модератор повторяет название общего видения и миссию 42. Короткое резюме — для фото и записи.</span></div>
</div>
</div>
<h3>Роли</h3>
<div class="cardgrid">
<div class="card">
<div class="cn">МОДЕРАТОР</div>
<h4>Держит рамку и тайминг</h4>
<p>Озвучивает миссию, следит за 4/2 минутами, ведёт панель к названию общего видения. <b>Жёсткий тайм-кипер</b>: «осталось 30 секунд».</p>
</div>
<div class="card">
<div class="cn">СПИКЕРЫ</div>
<h4>5–6 человек из разных индустрий</h4>
<p>Три задания: будущее своей индустрии (4 мин), что понравилось у других (2 мин), название общего видения. <b>Без презентаций</b> — только голос.</p>
</div>
<div class="card">
<div class="cn">ЗАЛ</div>
<h4>Четвёртый участник</h4>
<p>Вопросы после трёх кругов. В круге 3 модератор может спросить зал: какое название общего видения предложили бы вы?</p>
</div>
</div>
<div class="note">
<b>Правило тайминга</b>
<p>Карточки 60/30/10 секунд в руках тайм-кипера, звуковой сигнал на переходах между кругами. Если спикер не уложился в 4 минуты — модератор мягко забирает слово: формулировки доскажет во втором круге.</p>
</div>
</section>
<section id="why">
<div class="snum">03 / ПОЧЕМУ ЭТО СРАБОТАЕТ</div>
<h2>Что делает формат сильным</h2>
<div class="cardgrid">
<div class="card">
<div class="cn">01</div>
<h4>Круг 1 без перебиваний</h4>
<p>4 минуты каждому без вопросов — редкость на панелях. Спикеры дают глубокие ответы, а не заготовки. Глубина = запоминаемость.</p>
</div>
<div class="card">
<div class="cn">02</div>
<h4>Круг 2 связывает</h4>
<p>«Что понравилось» вместо «в чём не согласен» — позитивная рамка: панель выглядит единой, а не спорящей. Зал видит созвучие индустрий.</p>
</div>
<div class="card">
<div class="cn">03</div>
<h4>Круг 3 — артефакт</h4>
<p>Название общего видения — то, что остаётся после сессии: в заголовках, фото, цитатах. Панель производит смысл, а не обмен мнениями.</p>
</div>
<div class="card">
<div class="cn">04</div>
<h4>Миссия 42 как рамка</h4>
<p>«Все миссии одинаково важны» легитимирует любой ответ и любую индустрию — от ритейла до робототехники. Спикерам безопасно говорить смело.</p>
</div>
</div>
</section>
<section id="name">
<div class="snum">04 / НАЗВАНИЕ ОБЩЕГО ВИДЕНИЯ</div>
<h2>Заготовки для третьего круга</h2>
<p class="lead">Третий круг должен закончиться названием. Если панель не сойдётся сама, модератор предлагает шаблоны — панель выбирает или дорабатывает.</p>
<div class="qbox"><div class="m">ЗАГОТОВКА 01</div><div class="q">«Симбиоз» — будущее, где ИИ усиливает каждую миссию человека, не заменяя её.</div></div>
<div class="qbox"><div class="m">ЗАГОТОВКА 02</div><div class="q">«Коллективный разум» — каждый умножает свой вклад через общую инфраструктуру.</div></div>
<div class="qbox"><div class="m">ЗАГОТОВКА 03</div><div class="q">«42» — число, которое объединяет все миссии.</div></div>
</section>
<section id="checklist">
<div class="snum">05 / ЧЕК-ЛИСТ ОРГАНИЗАТОРА</div>
<h2>Что нужно до сессии</h2>
<ul>
<li><b>Состав панели:</b> 5–6 спикеров из максимально разных индустрий (финансы, медицина, промышленность, образование, медиа, госсектор). Разные горизонты видения — от прагматиков до футуристов.</li>
<li><b>Брифинг спикеров за 2–3 дня:</b> каждому объяснить три круга, тайминг 4/2 минуты и установку «загляни в самый дальний горизонт, в который можешь». Никаких презентаций.</li>
<li><b>Модератор:</b> умеет жёстко держать тайминг и мягко вести к названию. Репетиция открытия с миссией 42.</li>
<li><b>Тайм-кипер:</b> карточки 60/30/10 секунд, звуковой сигнал на переходах между кругами.</li>
<li><b>Зал:</b> микрофоны для вопросов, очередь или карточки для сбора вопросов.</li>
<li><b>Запись:</b> видео обязательно — название общего видения и лучшие цитаты круга 2 идут в посты и клипы после сессии.</li>
<li><b>Баннер миссии:</b> слайд или растяжка с текстом миссии 42 на сцене — рамка сессии видна на любой фотографии.</li>
</ul>
</section>
<section id="coop">
<div class="snum">06 / СВЯЗЬ С КООПЕРАТИВОМ</div>
<h2>Что это даёт 42</h2>
<p class="lead">Панель — не только контент, но и вход в экосистему кооператива для людей с миссиями.</p>
<ul>
<li><b>Поток проектов:</b> спикеры и зал видят формат работы кооператива — люди с миссиями приходят в 42, регистрируют проекты, усиливают долгосрочную AUM.</li>
<li><b>Точка входа:</b> портал cil.by упоминается в открытии и закрытии — для тех, кто захочет глубже после сессии.</li>
<li><b>Бренд:</b> миссия 42 на сцене — фирменный приём кооператива, отличающий сессию от других панелей конференции.</li>
</ul>
</section>
<footer>
Концепт панельной сессии «Общее видение» · ИИШНИЦА · кооператив «Друзья Коллективного Разума» · Минск · <span class="mono">cil.by</span><br>
Черновик для обсуждения v0.1 · <span class="mono">simbiot-harness / hour</span> · 2026-10-09
</footer>
</div>
</body>
</html>

BIN
out/leonid_meeting143.pptx Normal file

Binary file not shown.

View file

@ -0,0 +1,281 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Разговор с Леонидом — выжимка (встреча 143)</title>
<style>
html {
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 12px;
}
h1 {
font-size: 1.8em;
}
}
@media print {
html {
background-color: white;
}
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
font-size: 85%;
margin: 0;
hyphens: manual;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
</head>
<body>
<header id="title-block-header">
<h1 class="title">Разговор с Леонидом — выжимка (встреча 143)</h1>
</header>
<h1 id="разговор-алекса-с-леонидом-выжимка-и-договорённости">Разговор
Алекса с Леонидом — выжимка и договорённости</h1>
<p><em>Встреча 143 · 2026-10-08 · ~2.5 часа · Jitsi + агент-визуализатор
Simbiot</em></p>
<hr />
<h2 id="слайд-1-кто-есть-кто">Слайд 1 — Кто есть кто</h2>
<ul>
<li><strong>Леонид</strong> — давний знакомый студии, начинающий
писатель, инвестор в deep-tech (в т.ч. Anti-Aging Hero с физиком Петром
Федичевым). Владеет DGX Spark 128GB (куплен за €5000), гоняет на нём
CUDA-моделирование графов надёжности.</li>
<li><strong>Алекс</strong> — показал студийную инфраструктуру:
агент-визуализатор звонков (демо в реальном времени), собственный роутер
LM-провайдеров, RAG по всем знаниям кооператива.</li>
<li>Демо произвело впечатление: «1-й раз такое в моей практике… польза
очевидна».</li>
</ul>
<h2 id="слайд-2-демо-инфраструктуры-студии">Слайд 2 — Демо
инфраструктуры студии</h2>
<ul>
<li>Агент подключается к любому звонку, рисует слайды по ходу разговора
(задержка ~45 сек — лимит генерации).</li>
<li>Роутер сам выбирает LM под контекст (визуализация сейчас — GLM 5.3
Flash).</li>
<li>RAG: «он знает всё, что мы делаем» — доступ к общей базе знаний
кооператива.</li>
<li>Леонид получил полезный инсайт уже из демо: на его домашней
коробочке можно крутить быструю генерацию музыки/картинок/мини-видео —
трек за минуту.</li>
</ul>
<h2 id="слайд-3-главный-контакт-пилот-мультик-по-рассказу-леонида">Слайд
3 — Главный контакт: пилот «мультик по рассказу Леонида»</h2>
<ul>
<li>Леонид хочет мульт-адаптации своих рассказов (10–15 мин); готов
<strong>финансово поддержать</strong> пилот: токены — точно,
человеческий труд — обсудить. Ориентир бюджета «в пределах 1000» на
эксперимент.</li>
<li>Вход от студии: рассказ Леонида + свобода интерпретации, «никаких
пожеланий-ограничений».</li>
<li>Alex предложил: <strong>партнёр студии уже строит harness для
генерации мультиков/клипов/читов</strong> — матч идеальный. Влад
(профессиональный видеомейкер + озвучка) возьмёт видео-часть.</li>
<li>Первый шаг — аудиоспектакль (дешевле и предсказуемее), затем
видео.</li>
</ul>
<h2
id="слайд-4-творческий-метод-леонида-поучительно-для-продуктовой-линзы">Слайд
4 — Творческий метод Леонида (поучительно для продуктовой линзы)</h2>
<ul>
<li>История с хакатоном ~2018: обучали нейросеть на сольных песнях
Леннона/Маккартни и классифицировали битловские — «десяток песен
правильно заклассифицировали».</li>
<li>Пишет с ИИ (своей оценки ~80% — его творчество, ~20% ИИ), честно
маркирует «made with AI» на after.today.</li>
<li>Бэкграунд-тезис: «литературные негры» и мастерская Микеланджело —
ИИ-ассистированное творчество не ново, нова честность.</li>
</ul>
<h2 id="слайд-5-экономика-локального-инференса-спор-о-железе">Слайд 5 —
Экономика локального инференса (спор о железе)</h2>
<ul>
<li>Леонид: DGX Spark за ~1% стоимости дома — нормальная цена за
«домашний интеллект»; тренд — коробки станут достоянием домов.</li>
<li>Алекс: облачный always-on — ок для большинства задач; топовые модели
для разработки софта всё равно выигрывают у локальных.</li>
<li>Сошлись: гибрид — локально быстрая генерация (эскизы, музыка),
облако — для тяжёлых продуктовых задач. У студии в планах собственный
inference-сервер (инференс в ~6 раз быстрее).</li>
</ul>
<h2 id="слайд-6-обмен-тезисами-как-строить-ai-компанию">Слайд 6 — Обмен
тезисами: как строить AI-компанию</h2>
<ul>
<li><strong>Леонид:</strong> «Vision — говно, execution — всё» (шутка из
EPAM). Из короткого промпта не может вырасти сложная система: 10 людей с
одним промптом построят 10 одинаковых Uber’ов — вариабельность требует
доп. информации.</li>
<li><strong>Алекс:</strong> ядро студии — AI-ядро, постоянно
перестраивающее паттерны (64 сейчас) при смене контекста — «долгосрочно
самый большой актив».</li>
<li>Стратегия: строить бизнес на нейро-инфраструктуре с самого начала →
потом переводить на автопилот; незаменим только «CEO-решатель».</li>
<li>Леонид о студии: «я теперь понимаю, чем твоя студия отличается от
классической — изюминка в AI-ядре».</li>
<li>Методология Леонида для deep-tech: начинать продавать ценность сразу
(консалтинг целевого клиента), не влюбляясь в R&amp;D.</li>
</ul>
<h2 id="слайд-7-договорённости-что-фиксируем">Слайд 7 — Договорённости
(что фиксируем)</h2>
<ol type="1">
<li><strong>Пилот анимации по рассказу Леонида</strong> — Leonid
покрывает токены (ориентир до 1000), студия делает (аудио → видео, ведёт
Влад). Alex вернётся к Леониду с конкретным предложением.</li>
<li><strong>Формат разговора продолжается</strong> — Леонид открыт к
следующим сессиям.</li>
<li><strong>По голосам/долям за прошлые инвестиции — Леонид
отказался</strong> («это была шутка», «инвестирую из философских
соображений, не создавайте ожиданий»). Вопрос закрыт, давления нет.</li>
<li>Леонид ещё посмотрит презентацию студии (35-минутную) и вернётся с
фидбэком.</li>
</ol>
<h2 id="слайд-8-что-студии-делать-дальше">Слайд 8 — Что студии делать
дальше</h2>
<ul>
<li>Подготовить оффер по мульт-пилоту (объём, этап аудиоспектакля, смета
токенов) — Alex озвучит Леониду предметно.</li>
<li>Подключить партнёрский harness генерации мультиков к задаче Леонида
как первый клиентский кейс.</li>
<li>Зафиксировать кейс «агент-визуализатор на реальном внешнем звонке» —
первое демо на живом стороннем человеке, положительный фидбэк.</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,57 @@
# Разговор Алекса с Леонидом — выжимка и договорённости
*Встреча 143 · 2026-10-08 · ~2.5 часа · Jitsi + агент-визуализатор Simbiot*
---
## Слайд 1 — Кто есть кто
- **Леонид** — давний знакомый студии, начинающий писатель, инвестор в deep-tech (в т.ч. Anti-Aging Hero с физиком Петром Федичевым). Владеет DGX Spark 128GB (куплен за €5000), гоняет на нём CUDA-моделирование графов надёжности.
- **Алекс** — показал студийную инфраструктуру: агент-визуализатор звонков (демо в реальном времени), собственный роутер LM-провайдеров, RAG по всем знаниям кооператива.
- Демо произвело впечатление: «1-й раз такое в моей практике… польза очевидна».
## Слайд 2 — Демо инфраструктуры студии
- Агент подключается к любому звонку, рисует слайды по ходу разговора (задержка ~45 сек — лимит генерации).
- Роутер сам выбирает LM под контекст (визуализация сейчас — GLM 5.3 Flash).
- RAG: «он знает всё, что мы делаем» — доступ к общей базе знаний кооператива.
- Леонид получил полезный инсайт уже из демо: на его домашней коробочке можно крутить быструю генерацию музыки/картинок/мини-видео — трек за минуту.
## Слайд 3 — Главный контакт: пилот «мультик по рассказу Леонида»
- Леонид хочет мульт-адаптации своих рассказов (10–15 мин); готов **финансово поддержать** пилот: токены — точно, человеческий труд — обсудить. Ориентир бюджета «в пределах 1000» на эксперимент.
- Вход от студии: рассказ Леонида + свобода интерпретации, «никаких пожеланий-ограничений».
- Alex предложил: **партнёр студии уже строит harness для генерации мультиков/клипов/читов** — матч идеальный. Влад (профессиональный видеомейкер + озвучка) возьмёт видео-часть.
- Первый шаг — аудиоспектакль (дешевле и предсказуемее), затем видео.
## Слайд 4 — Творческий метод Леонида (поучительно для продуктовой линзы)
- История с хакатоном ~2018: обучали нейросеть на сольных песнях Леннона/Маккартни и классифицировали битловские — «десяток песен правильно заклассифицировали».
- Пишет с ИИ (своей оценки ~80% — его творчество, ~20% ИИ), честно маркирует «made with AI» на after.today.
- Бэкграунд-тезис: «литературные негры» и мастерская Микеланджело — ИИ-ассистированное творчество не ново, нова честность.
## Слайд 5 — Экономика локального инференса (спор о железе)
- Леонид: DGX Spark за ~1% стоимости дома — нормальная цена за «домашний интеллект»; тренд — коробки станут достоянием домов.
- Алекс: облачный always-on — ок для большинства задач; топовые модели для разработки софта всё равно выигрывают у локальных.
- Сошлись: гибрид — локально быстрая генерация (эскизы, музыка), облако — для тяжёлых продуктовых задач. У студии в планах собственный inference-сервер (инференс в ~6 раз быстрее).
## Слайд 6 — Обмен тезисами: как строить AI-компанию
- **Леонид:** «Vision — говно, execution — всё» (шутка из EPAM). Из короткого промпта не может вырасти сложная система: 10 людей с одним промптом построят 10 одинаковых Uber'ов — вариабельность требует доп. информации.
- **Алекс:** ядро студии — AI-ядро, постоянно перестраивающее паттерны (64 сейчас) при смене контекста — «долгосрочно самый большой актив».
- Стратегия: строить бизнес на нейро-инфраструктуре с самого начала → потом переводить на автопилот; незаменим только «CEO-решатель».
- Леонид о студии: «я теперь понимаю, чем твоя студия отличается от классической — изюминка в AI-ядре».
- Методология Леонида для deep-tech: начинать продавать ценность сразу (консалтинг целевого клиента), не влюбляясь в R&D.
## Слайд 7 — Договорённости (что фиксируем)
1. **Пилот анимации по рассказу Леонида** — Leonid покрывает токены (ориентир до 1000), студия делает (аудио → видео, ведёт Влад). Alex вернётся к Леониду с конкретным предложением.
2. **Формат разговора продолжается** — Леонид открыт к следующим сессиям.
3. **По голосам/долям за прошлые инвестиции — Леонид отказался** («это была шутка», «инвестирую из философских соображений, не создавайте ожиданий»). Вопрос закрыт, давления нет.
4. Леонид ещё посмотрит презентацию студии (35-минутную) и вернётся с фидбэком.
## Слайд 8 — Что студии делать дальше
- Подготовить оффер по мульт-пилоту (объём, этап аудиоспектакля, смета токенов) — Alex озвучит Леониду предметно.
- Подключить партнёрский harness генерации мультиков к задаче Леонида как первый клиентский кейс.
- Зафиксировать кейс «агент-визуализатор на реальном внешнем звонке» — первое демо на живом стороннем человеке, положительный фидбэк.

View file

@ -0,0 +1,436 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Встреча 143 · Алекс × Леонид — выжимка и договорённости</title>
<style>
:root{
--bg:#0a0c12;
--bg2:#0e1120;
--ink:#f0eeff;
--mut:#9a97b8;
--dim:#6f6c8f;
--vio:#8b5cf6;
--vio2:#7c6cf5;
--cyan:#5eead4;
--amber:#fbbf24;
--card:rgba(255,255,255,.035);
--line:rgba(139,92,246,.28);
--serif:Georgia,"Times New Roman",serif;
--sans:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
--mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
background:var(--bg);
color:var(--ink);
font-family:var(--sans);
overflow:hidden;
-webkit-font-smoothing:antialiased;
text-rendering:optimizeLegibility;
}
/* ---------- ambient backdrop ---------- */
.bg{position:fixed;inset:0;z-index:0;pointer-events:none}
.bg::before{
content:"";position:absolute;inset:0;
background:
radial-gradient(60vw 60vw at 12% -10%, rgba(124,108,245,.20), transparent 62%),
radial-gradient(55vw 55vw at 105% 112%, rgba(94,234,212,.09), transparent 60%),
radial-gradient(70vw 40vw at 50% 120%, rgba(139,92,246,.10), transparent 65%),
linear-gradient(180deg,var(--bg) 0%,var(--bg2) 100%);
}
.bg::after{ /* film grain */
content:"";position:absolute;inset:0;opacity:.05;mix-blend-mode:overlay;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='160' height='160' filter='url(%23n)' opacity='0.6'/></svg>");
}
.vignette{position:fixed;inset:0;z-index:1;pointer-events:none;
background:radial-gradient(120vw 90vh at 50% 50%, transparent 55%, rgba(0,0,0,.55) 100%)}
/* ---------- deck ---------- */
.deck{position:relative;z-index:2;height:100%}
.slide{
position:absolute;inset:0;
display:flex;flex-direction:column;justify-content:center;
padding:9vh 8vw 12vh;
opacity:0;transform:translateY(26px) scale(.985);
transition:opacity .55s ease,transform .55s cubic-bezier(.22,.61,.36,1);
pointer-events:none;visibility:hidden;
}
.slide.active{opacity:1;transform:none;pointer-events:auto;visibility:visible}
.slide.prev{transform:translateY(-26px) scale(.985)}
.accent{position:absolute;top:0;left:0;right:0;height:6px;
background:linear-gradient(90deg,var(--vio) 0%,#a78bfa 38%,var(--cyan) 100%);
box-shadow:0 0 24px rgba(139,92,246,.55)}
.ghost{
position:absolute;right:4vw;bottom:2vh;z-index:-1;
font-weight:900;font-size:clamp(160px,30vw,420px);line-height:1;
color:transparent;-webkit-text-stroke:1.5px rgba(139,92,246,.13);
letter-spacing:-.04em;user-select:none}
.kicker{
display:inline-flex;align-items:center;gap:.6em;
font-size:clamp(11px,1.15vw,14px);font-weight:700;letter-spacing:.24em;
text-transform:uppercase;color:var(--vio2);margin-bottom:2.4vh;
}
.kicker::before{content:"";width:34px;height:2px;background:var(--vio);box-shadow:0 0 12px var(--vio)}
h1{font-size:clamp(38px,6.6vw,86px);font-weight:900;letter-spacing:-.03em;line-height:1.04}
h1 em{font-style:normal;background:linear-gradient(92deg,#a78bfa,var(--vio2) 55%,var(--cyan));
-webkit-background-clip:text;background-clip:text;color:transparent}
h2{font-size:clamp(28px,4vw,52px);font-weight:800;letter-spacing:-.025em;line-height:1.08;margin-bottom:3.6vh}
h2 .alt{color:var(--vio2)}
.sub{font-size:clamp(15px,1.6vw,21px);color:var(--mut);max-width:34em;line-height:1.55;margin-top:3vh}
.chips{display:flex;flex-wrap:wrap;gap:.7em;margin-top:4.5vh}
.chip{
font-family:var(--mono);font-size:clamp(11px,1.05vw,13px);color:var(--mut);
padding:.55em 1.1em;border:1px solid var(--line);border-radius:999px;
background:rgba(139,92,246,.07)}
.chip b{color:var(--ink);font-weight:600}
/* ---------- content blocks ---------- */
.grid{display:grid;gap:1.5vh 1.6vw}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
@media(max-width:860px){.g2,.g3{grid-template-columns:1fr}}
.card{
background:var(--card);border:1px solid rgba(255,255,255,.07);
border-radius:18px;padding:2.6vh 1.6vw;position:relative;overflow:hidden;
backdrop-filter:blur(6px);
transition:border-color .3s,transform .3s;
}
.card:hover{border-color:var(--line);transform:translateY(-2px)}
.card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
background:linear-gradient(180deg,var(--vio),transparent 85%);opacity:.85}
.card h3{font-size:clamp(15px,1.45vw,19px);font-weight:700;margin-bottom:1vh;letter-spacing:-.01em}
.card p,.card li{font-size:clamp(13px,1.28vw,17px);color:var(--mut);line-height:1.62}
.card p b,.card li b{color:var(--ink);font-weight:600}
.tag{display:inline-block;font-family:var(--mono);font-size:clamp(10px,.95vw,12px);
color:var(--cyan);border:1px solid rgba(94,234,212,.3);border-radius:6px;
padding:.15em .6em;margin-bottom:1.2vh;letter-spacing:.06em}
.tag.v{color:#c4b5fd;border-color:rgba(139,92,246,.4)}
.tag.a{color:var(--amber);border-color:rgba(251,191,36,.35)}
ul.list{list-style:none;display:grid;gap:1.7vh}
ul.list li{
display:flex;gap:1em;align-items:flex-start;
font-size:clamp(14px,1.5vw,19px);color:var(--mut);line-height:1.6;
}
ul.list li::before{
content:"▸";color:var(--vio);font-size:1.05em;line-height:1.5;
text-shadow:0 0 14px rgba(139,92,246,.8);flex:0 0 auto}
ul.list b{color:var(--ink);font-weight:650}
ul.list .hl{color:var(--cyan)}
.quote{
margin-top:3.2vh;padding:2.4vh 2vw;border-left:3px solid var(--vio);
background:linear-gradient(90deg,rgba(139,92,246,.12),transparent 72%);
border-radius:0 14px 14px 0;
}
.quote p{font-family:var(--serif);font-style:italic;
font-size:clamp(17px,2.1vw,27px);line-height:1.45;color:#e4e0ff}
.quote cite{display:block;margin-top:1.2vh;font-style:normal;font-family:var(--mono);
font-size:clamp(10px,1vw,13px);color:var(--dim);letter-spacing:.12em;text-transform:uppercase}
.num{display:flex;gap:1.1em;align-items:flex-start;padding:1.9vh 0;
border-bottom:1px dashed rgba(255,255,255,.09)}
.num:last-child{border-bottom:none}
.num i{
font-style:normal;font-family:var(--mono);flex:0 0 auto;
width:2.1em;height:2.1em;display:grid;place-items:center;border-radius:10px;
background:rgba(139,92,246,.16);border:1px solid var(--line);
color:#c4b5fd;font-size:clamp(12px,1.15vw,15px);font-weight:700}
.num h3{font-size:clamp(15px,1.5vw,19px);font-weight:700;margin-bottom:.5vh}
.num p{font-size:clamp(13px,1.3vw,16px);color:var(--mut);line-height:1.6}
.num b{color:var(--ink);font-weight:600}
.vs{display:grid;grid-template-columns:1fr auto 1fr;gap:1.2vw;align-items:stretch}
@media(max-width:860px){.vs{grid-template-columns:1fr}.vs .vsx{display:none}}
.vs .card{height:100%}
.vs .who{font-family:var(--mono);font-size:clamp(10px,.95vw,12px);letter-spacing:.14em;
text-transform:uppercase;color:var(--vio2);margin-bottom:1.2vh}
.vsx{display:grid;place-items:center;color:var(--dim);font-size:clamp(18px,2vw,26px)}
.big{font-size:clamp(30px,4.6vw,64px);font-weight:900;letter-spacing:-.03em;line-height:1.1}
.big em{font-style:normal;color:var(--vio2)}
/* ---------- chrome ---------- */
.progress{position:fixed;left:0;bottom:0;height:3px;z-index:10;width:0;
background:linear-gradient(90deg,var(--vio),#a78bfa,var(--cyan));
box-shadow:0 0 14px rgba(139,92,246,.8);transition:width .45s ease}
.hud{position:fixed;right:2.2vw;bottom:3vh;z-index:10;display:flex;gap:.6em;align-items:center}
.hud .count{font-family:var(--mono);font-size:12px;color:var(--dim);letter-spacing:.1em;margin-right:.8em}
.hud .count b{color:var(--mut);font-weight:600}
.hud button{
width:40px;height:40px;border-radius:12px;cursor:pointer;
border:1px solid var(--line);background:rgba(139,92,246,.1);color:#c4b5fd;
font-size:16px;display:grid;place-items:center;transition:.25s}
.hud button:hover{background:rgba(139,92,246,.25);color:#fff;transform:translateY(-1px)}
.hint{position:fixed;left:2.2vw;bottom:3vh;z-index:10;
font-family:var(--mono);font-size:11px;color:var(--dim);letter-spacing:.08em;opacity:.8}
.brand{position:fixed;top:2.6vh;right:2.2vw;z-index:10;
font-family:var(--mono);font-size:11px;letter-spacing:.22em;color:var(--dim);text-transform:uppercase}
.brand b{color:var(--vio2)}
.dots{position:fixed;right:1.4vw;top:50%;transform:translateY(-50%);z-index:10;
display:flex;flex-direction:column;gap:.55em}
.dots i{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.16);cursor:pointer;transition:.3s}
.dots i.on{background:var(--vio);box-shadow:0 0 10px var(--vio);transform:scale(1.35)}
@media(max-width:700px){.dots{display:none}.hint{display:none}}
.end .sig{font-family:var(--mono);font-size:clamp(10px,1vw,13px);color:var(--dim);
letter-spacing:.18em;text-transform:uppercase;margin-top:5vh}
/* ---------- print ---------- */
@media print{
body{overflow:visible;background:#0a0c12}
.bg,.vignette,.hud,.hint,.dots,.progress,.brand{display:none!important}
.deck{height:auto}
.slide{position:relative;inset:auto;opacity:1!important;transform:none!important;
visibility:visible!important;pointer-events:auto;height:100vh;page-break-after:always}
}
</style>
</head>
<body>
<div class="bg"></div><div class="vignette"></div>
<div class="brand">Simbiot · <b>Кооператив 42</b></div>
<div class="deck" id="deck">
<!-- 01 · TITLE -->
<section class="slide">
<div class="accent"></div><div class="ghost">143</div>
<div class="kicker">Встреча 143 · 08.10.2026 · Jitsi + агент-визуализатор Simbiot</div>
<h1>Разговор<br>Алекса <em>× Леонида</em></h1>
<p class="sub">Выжимка двухсполовинойчасовой встречи: инфраструктура студии, пилот «мультик по рассказу», экономика локального инференса — и договорённости, которые фиксируем.</p>
<div class="chips">
<span class="chip"><b>~2.5</b> часа разговора</span>
<span class="chip"><b>8</b> тем</span>
<span class="chip"><b>4</b> договорённости</span>
<span class="chip">слайды рисовал агент <b>live</b></span>
</div>
</section>
<!-- 02 · WHO -->
<section class="slide">
<div class="accent"></div><div class="ghost">01</div>
<div class="kicker">Кто есть кто</div>
<h2>Два мира на одном звонке</h2>
<div class="grid g2">
<div class="card">
<div class="who tag v">Леонид</div>
<h3>Писатель · инвестор в deep-tech</h3>
<p>Давний знакомый студии. Инвестирует в deep-tech, в том числе <b>Anti-Aging Hero</b> с физиком Петром Федичевым. Дома — <b>DGX Spark 128GB</b> (€5000), на которой он гоняет CUDA-моделирование графов надёжности.</p>
</div>
<div class="card">
<div class="tag">Алекс · студия</div>
<h3>Живая AI-инфраструктура</h3>
<p>Показал, чем студия отличается от классической: <b>агент-визуализатор звонков</b> (демо в реальном времени), собственный <b>роутер LM-провайдеров</b> и <b>RAG</b> по всем знаниям кооператива.</p>
</div>
</div>
<div class="quote">
<p>«Первый раз такое в моей практике… польза очевидна»</p>
<cite>Леонид — после демо агент-визуализатора</cite>
</div>
</section>
<!-- 03 · DEMO -->
<section class="slide">
<div class="accent"></div><div class="ghost">02</div>
<div class="kicker">Демо инфраструктуры</div>
<h2>Что увидел Леонид <span class="alt">в прямом эфире</span></h2>
<ul class="list">
<li><span><b>Агент подключается к любому звонку</b> и рисует слайды по ходу разговора — задержка ~45 секунд (лимит генерации)</span></li>
<li><span><b>Роутер сам выбирает модель под контекст</b> — визуализация идёт на GLM 5.3 Flash</span></li>
<li><span><b>RAG по базе кооператива:</b> «он знает всё, что мы делаем» — агент отвечает со знанием всей истории студии</span></li>
<li><span>Инсайт Леонида из демо: <span class="hl">на его домашней коробочке можно крутить быструю генерацию</span> — музыку, картинки, мини-видео. Трек за минуту</span></li>
</ul>
</section>
<!-- 04 · PILOT -->
<section class="slide">
<div class="accent"></div><div class="ghost">03</div>
<div class="kicker">Главный контакт встречи</div>
<h2>Пилот: <span class="alt">мультик по рассказу Леонида</span></h2>
<div class="grid g2">
<div class="card">
<div class="tag a">Что хочет Леонид</div>
<p><b>Мульт-адаптация своих рассказов</b> (10–15 минут). Готов финансово поддержать пилот: <b>токены — точно</b>, человеческий труд — обсудить. Ориентир бюджета — <b>«в пределах 1000»</b> на эксперимент.</p>
</div>
<div class="card">
<div class="tag v">Что входит студия</div>
<p>Рассказ Леонида + <b>свобода интерпретации</b> («никаких пожеланий-ограничений»). Со стороны студии — партнёр уже строит <b>harness генерации мультиков</b>, видео-часть ведёт <b>Влад</b> (профессиональный видеомейкер + озвучка). Матч идеальный.</p>
</div>
</div>
<div class="quote">
<p>Первый шаг — аудиоспектакль: дешевле и предсказуемее. Затем видео.</p>
<cite>Дорожная карта пилота</cite>
</div>
</section>
<!-- 05 · METHOD -->
<section class="slide">
<div class="accent"></div><div class="ghost">04</div>
<div class="kicker">Творческий метод Леонида</div>
<h2>Поучительно для <span class="alt">продуктовой линзы</span></h2>
<ul class="list">
<li><span><b>Хакатон ~2018:</b> обучали нейросеть на сольных песнях Леннона и Маккартни, затем классифицировали битловские — «десяток песен правильно заклассифицировали»</span></li>
<li><span><b>Пишет с ИИ:</b> по собственной оценке ~80% — его творчество, ~20% — ИИ. Честно маркирует <span class="hl">«made with AI»</span> на after.today</span></li>
<li><span><b>Бэкграунд-тезис:</b> «литературные негры» и мастерская Микеланджело — ИИ-ассистированное творчество не ново. <b>Нова честность.</b></span></li>
</ul>
<div class="quote">
<p>ИИ-ассистированное творчество не ново — нова честность.</p>
<cite>Леонид</cite>
</div>
</section>
<!-- 06 · HARDWARE -->
<section class="slide">
<div class="accent"></div><div class="ghost">05</div>
<div class="kicker">Экономика локального инференса</div>
<h2>Спор о железе: <span class="alt">дом против облака</span></h2>
<div class="vs">
<div class="card">
<div class="who">Позиция Леонида</div>
<p><b>DGX Spark за ~1% стоимости дома</b> — нормальная цена за «домашний интеллект». Тренд: коробки станут достоянием домов.</p>
</div>
<div class="vsx">⚡</div>
<div class="card">
<div class="who">Позиция Алекса</div>
<p><b>Облачный always-on</b> — ок для большинства задач. Топовые модели для разработки софта всё равно выигрывают у локальных.</p>
</div>
</div>
<div class="quote">
<p>Сошлись на гибриде: локально — быстрая генерация (эскизы, музыка), облако — тяжёлые продуктовые задачи. У студии в планах собственный inference-сервер — в ~6 раз быстрее.</p>
<cite>Консенсус встречи</cite>
</div>
</section>
<!-- 07 · AI COMPANY -->
<section class="slide">
<div class="accent"></div><div class="ghost">06</div>
<div class="kicker">Обмен тезисами</div>
<h2>Как строить <span class="alt">AI-компанию</span></h2>
<div class="grid g2">
<div class="card">
<div class="tag">Тезис Леонида</div>
<p><b>«Vision — говно, execution — всё»</b> (шутка из EPAM). Из короткого промпта не вырастает сложная система: 10 людей с одним промптом построят 10 одинаковых Uber'ов — вариабельность требует дополнительной информации.</p>
</div>
<div class="card">
<div class="tag v">Тезис Алекса</div>
<p>Ядро студии — <b>AI-ядро, постоянно перестраивающее паттерны</b> (64 сейчас) при смене контекста. «Долгосрочно — самый большой актив». Строить бизнес на нейро-инфраструктуре с самого начала → затем автопилот. Незаменим только <b>«CEO-решатель»</b>.</p>
</div>
</div>
<div class="quote">
<p>«Я теперь понимаю, чем твоя студия отличается от классической — изюминка в AI-ядре»</p>
<cite>Леонид · метод deep-tech: продавать ценность сразу, не влюбляясь в R&D</cite>
</div>
</section>
<!-- 08 · DEALS -->
<section class="slide">
<div class="accent"></div><div class="ghost">07</div>
<div class="kicker">Итоги встречи</div>
<h2>Договорённости — <span class="alt">что фиксируем</span></h2>
<div class="num"><i>1</i><div>
<h3>Пилот анимации по рассказу Леонида</h3>
<p>Леонид покрывает токены (ориентир до <b>1000</b>), студия делает: аудио → видео, ведёт Влад. Алекс вернётся с <b>конкретным предложением</b>.</p>
</div></div>
<div class="num"><i>2</i><div>
<h3>Формат разговора продолжается</h3>
<p>Леонид открыт к следующим сессиям со студией.</p>
</div></div>
<div class="num"><i>3</i><div>
<h3>Голоса и доли за прошлые инвестиции — вопрос закрыт</h3>
<p>Леонид отказался: «это была шутка», «инвестирую из философских соображений, не создавайте ожиданий». Давления нет.</p>
</div></div>
<div class="num"><i>4</i><div>
<h3>Следующий шаг Леонида</h3>
<p>Посмотрит 35-минутную презентацию студии и вернётся с фидбэком.</p>
</div></div>
</section>
<!-- 09 · NEXT -->
<section class="slide">
<div class="accent"></div><div class="ghost">08</div>
<div class="kicker">Action items</div>
<h2>Что студии делать <span class="alt">дальше</span></h2>
<ul class="list">
<li><span><b>Подготовить оффер по мульт-пилоту</b> — объём, этап аудиоспектакля, смета токенов. Алекс озвучит Леониду предметно.</span></li>
<li><span><b>Подключить партнёрский harness генерации мультиков</b> к задаче Леонида — как первый клиентский кейс.</span></li>
<li><span><b>Зафиксировать кейс «агент-визуализатор на реальном внешнем звонке»</b> — первое демо на живом стороннем человеке, положительный фидбэк.</span></li>
</ul>
<div class="quote">
<p>От живого демо на звонке — к первому платному пилоту по генерации контента.</p>
<cite>Смысл встречи 143</cite>
</div>
</section>
<!-- 10 · END -->
<section class="slide end">
<div class="accent"></div><div class="ghost">42</div>
<div class="kicker">Встреча 143 · 08.10.2026</div>
<div class="big">«Изюминка — <em>в AI-ядре»</em></div>
<p class="sub">Simbiot · Кооператив 42 · агент-визуализатор, LM-роутер, RAG по знаниям студии. Презентация собрана из выжимки разговора — тем же агентом, что рисовал слайды live.</p>
<div class="sig">← → навигация · F — во весь экран · P — печать/PDF</div>
</section>
</div>
<div class="dots" id="dots"></div>
<div class="progress" id="prog"></div>
<div class="hud">
<span class="count" id="count"></span>
<button id="prev" title="Назад (←)">←</button>
<button id="next" title="Вперёд (→)">→</button>
</div>
<div class="hint">← → · свайп · F — фулскрин</div>
<script>
(function(){
var slides=[].slice.call(document.querySelectorAll('.slide'));
var dots=document.getElementById('dots'),prog=document.getElementById('prog'),cnt=document.getElementById('count');
var i=0,n=slides.length;
slides.forEach(function(_,k){var d=document.createElement('i');d.onclick=function(){go(k)};dots.appendChild(d);});
function pad(x){return (x<10?'0':'')+x}
function go(k){
k=Math.max(0,Math.min(n-1,k));
if(k===i&&slides[i].classList.contains('active'))return;
slides.forEach(function(s,j){
s.classList.toggle('active',j===k);
s.classList.toggle('prev',j<k);
});
i=k;
prog.style.width=((i+1)/n*100)+'%';
cnt.innerHTML='<b>'+pad(i+1)+'</b> / '+pad(n);
[].forEach.call(dots.children,function(d,j){d.classList.toggle('on',j===i)});
if(history.replaceState)history.replaceState(null,'','#'+(i+1));
}
document.getElementById('next').onclick=function(){go(i+1)};
document.getElementById('prev').onclick=function(){go(i-1)};
addEventListener('keydown',function(e){
if(e.key==='ArrowRight'||e.key==='PageDown'||e.key===' '){e.preventDefault();go(i+1)}
else if(e.key==='ArrowLeft'||e.key==='PageUp'){e.preventDefault();go(i-1)}
else if(e.key==='Home'){go(0)}
else if(e.key==='End'){go(n-1)}
else if(e.key==='f'||e.key==='F'||e.key==='а'||e.key==='А'){
if(document.fullscreenElement){document.exitFullscreen&&document.exitFullscreen()}
else{document.documentElement.requestFullscreen&&document.documentElement.requestFullscreen()}
}
});
var tx=null,ty=null;
addEventListener('touchstart',function(e){tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:true});
addEventListener('touchend',function(e){
if(tx===null)return;
var dx=e.changedTouches[0].clientX-tx,dy=e.changedTouches[0].clientY-ty;
if(Math.abs(dx)>60&&Math.abs(dx)>Math.abs(dy)){go(dx<0?i+1:i-1)}
tx=null;
},{passive:true});
var h=parseInt((location.hash||'').replace('#',''),10);
go(isNaN(h)?0:h-1);
})();
</script>
</body>
</html>

BIN
out/simbiot_pitch.pptx Normal file

Binary file not shown.

View file

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Simbiot — индекс статических файлов</title>
<style>
body{font-family:system-ui,-apple-system,sans-serif;background:#0e1116;color:#e6e9ef;margin:0;padding:40px 20px}
h1{font-size:26px} h2{margin-top:36px;font-size:19px;color:#8ab4ff;border-bottom:1px solid #2a3140;padding-bottom:6px}
table{width:100%;max-width:1000px;border-collapse:collapse;font-size:14px}
td{padding:8px 10px;border-bottom:1px solid #1e2430;vertical-align:top}
a{color:#7ec9ff;text-decoration:none} a:hover{text-decoration:underline}
.size{color:#8a93a5;white-space:nowrap}
.note{color:#aab4c5}
</style>
</head>
<body>
<h1>📁 Индекс статических файлов кооператива 42 / Simbiot</h1>
<p class="note">Все HTML/PNG/PDF-файлы репозитория, сгруппированные по категориям. 31 файл.</p>
<h2>🌍 Лендинги и презентации</h2>
<table>
<tr><td><a href="../docs/simbiot-landing-v2.html">docs/simbiot-landing-v2.html</a></td><td class="note">Лендинг Simbiot v2</td><td class="size">33 КБ</td></tr>
<tr><td><a href="../docs/simbiot-landing-v2-live.html">docs/simbiot-landing-v2-live.html</a></td><td class="note">Лендинг v2 (live-версия)</td><td class="size">30 КБ</td></tr>
<tr><td><a href="../docs/simbiot-landing-dashboard.html">docs/simbiot-landing-dashboard.html</a></td><td class="note">Лендинг + дашборд</td><td class="size">20 КБ</td></tr>
<tr><td><a href="../docs/simbiot_presale_landing.html">docs/simbiot_presale_landing.html</a></td><td class="note">Пресейл-лендинг</td><td class="size">17 КБ</td></tr>
<tr><td><a href="../artifacts/landing-simbiot.html">artifacts/landing-simbiot.html</a></td><td class="note">Лендинг (артefакт)</td><td class="size">11 КБ</td></tr>
<tr><td><a href="../artifacts/landing-simbiot-opencode.html">artifacts/landing-simbiot-opencode.html</a></td><td class="note">Лендинг (OpenCode)</td><td class="size">20 КБ</td></tr>
<tr><td><a href="../simbiot-presentation.html">simbiot-presentation.html</a></td><td class="note">Презентация Simbiot</td><td class="size">22 КБ</td></tr>
<tr><td><a href="../architecture.html">architecture.html</a></td><td class="note">Архитектура системы</td><td class="size">40 КБ</td></tr>
</table>
<h2>👤 Порталы и UI-клиенты</h2>
<table>
<tr><td><a href="../out/cil-by-compact-landing.html">out/cil-by-compact-landing.html</a></td><td class="note">Портал CIL by Compact</td><td class="size">23 КБ</td></tr>
<tr><td><a href="../out/ui-cil-by-compact-landing.html">out/ui-cil-by-compact-landing.html</a></td><td class="note">UI-версия портала CIL</td><td class="size">22 КБ</td></tr>
<tr><td><a href="../out/simbiot_ui_presale.html">out/simbiot_ui_presale.html</a></td><td class="note">Пресейл UI</td><td class="size">12 КБ</td></tr>
<tr><td><a href="../harness/roy.html">harness/roy.html</a></td><td class="note">Портал Roy</td><td class="size">3 КБ</td></tr>
<tr><td><a href="../harness/dashboard.html">harness/dashboard.html</a></td><td class="note">Дашборд харнеса</td><td class="size">40 КБ</td></tr>
</table>
<h2>🗓 Встречи и материалы к переговорам</h2>
<table>
<tr><td><a href="../out/meeting143_leonid_presentation.html">out/meeting143_leonid_presentation.html</a></td><td class="note">Встреча 143 — презентация для Леонида</td><td class="size">13 КБ</td></tr>
<tr><td><a href="../out/meeting143_leonid_slides.html">out/meeting143_leonid_slides.html</a></td><td class="note">Встреча 143 — слайды</td><td class="size">25 КБ</td></tr>
<tr><td><a href="../docs/yulia-call-136-proposal.html">docs/yulia-call-136-proposal.html</a></td><td class="note">Звонок 136 — предложение Юлии</td><td class="size">6 КБ</td></tr>
<tr><td><a href="../docs/yulia-call-136-recap.html">docs/yulia-call-136-recap.html</a></td><td class="note">Звонок 136 — рекап</td><td class="size">6 КБ</td></tr>
<tr><td><a href="../docs/nadezhda-call-recap.html">docs/nadezhda-call-recap.html</a></td><td class="note">Звонок с Надеждой — рекап</td><td class="size">10 КБ</td></tr>
</table>
<h2>📄 Документы и договоры</h2>
<table>
<tr><td><a href="../Studio_Participation_Agreement_2026-09.pdf">Studio_Participation_Agreement_2026-09.pdf</a></td><td class="note">Соглашение об участии в студии (PDF)</td><td class="size">17 КБ</td></tr>
<tr><td><a href="../lab42-conditions.html">lab42-conditions.html</a></td><td class="note">Условия Lab42</td><td class="size">15 КБ</td></tr>
<tr><td><a href="../artifacts/studio-conditions-2026-10.html">artifacts/studio-conditions-2026-10.html</a></td><td class="note">Условия студии 2026-10</td><td class="size">6 КБ</td></tr>
<tr><td><a href="../artifacts/rental-x-simbiot-agreement.html">artifacts/rental-x-simbiot-agreement.html</a></td><td class="note">Соглашение Rental×Simbiot</td><td class="size">16 КБ</td></tr>
<tr><td><a href="../artifacts/rental-x-simbiot-side-letter-15k.html">artifacts/rental-x-simbiot-side-letter-15k.html</a></td><td class="note">Сайд-леттер Rental×Simbiot 15k</td><td class="size">10 КБ</td></tr>
</table>
<h2>🛠 Продуктовые планы и гайды</h2>
<table>
<tr><td><a href="../docs/ai-mascots-product-plan.html">docs/ai-mascots-product-plan.html</a></td><td class="note">Продуктовый план AI-маскотов</td><td class="size">16 КБ</td></tr>
<tr><td><a href="../docs/fpga-neuralnet-guide.html">docs/fpga-neuralnet-guide.html</a></td><td class="note">Гайд: нейросети на FPGA</td><td class="size">9 КБ</td></tr>
<tr><td><a href="../docs/self-diagnostics.html">docs/self-diagnostics.html</a></td><td class="note">Самодиагностика</td><td class="size">8 КБ</td></tr>
</table>
<h2>🎨 Прототипы</h2>
<table>
<tr><td><a href="../artifacts/shkor-beauty-prototype.html">artifacts/shkor-beauty-prototype.html</a></td><td class="note">Прототип Shkor Beauty</td><td class="size">25 КБ</td></tr>
</table>
<h2>🖼 Изображения</h2>
<table>
<tr><td><a href="../artifacts/imagegen-e2e-20261003.png">artifacts/imagegen-e2e-20261003.png</a></td><td class="note">E2E-тест генерации изображений</td><td class="size">977 КБ</td></tr>
<tr><td><a href="../artifacts/dashboard-channels-t-d8112b.png">artifacts/dashboard-channels-t-d8112b.png</a></td><td class="note">Скриншот дашборда (каналы)</td><td class="size">138 КБ</td></tr>
<tr><td><a href="../artifacts/dashboard-channels-t-d8112b-full.png">artifacts/dashboard-channels-t-d8112b-full.png</a></td><td class="note">Скриншот дашборда (полный)</td><td class="size">145 КБ</td></tr>
<tr><td><a href="../out/9router-gemini-test.png">out/9router-gemini-test.png</a></td><td class="note">Тест 9router/Gemini</td><td class="size">1 КБ</td></tr>
</table>
</body>
</html>

133
scripts/make_pptx_261008.py Normal file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Генерация PPTX: (1) разговор с Леонидом, (2) питч Simbiot (Seedance)."""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
BG = RGBColor(0x0A, 0x0C, 0x12)
ACCENT = RGBColor(0x8B, 0x5C, 0xF6)
ACCENT2 = RGBColor(0x7C, 0x6C, 0xF5)
TEXT = RGBColor(0xF0, 0xEE, 0xFF)
MUT = RGBColor(0x9A, 0x97, 0xB8)
def add_slide(prs, title, bullets, kicker=None):
s = prs.slides.add_slide(prs.slide_layouts[6])
s.background.fill.solid(); s.background.fill.fore_color.rgb = BG
w, h = prs.slide_width, prs.slide_height
# accent bar
bar = s.shapes.add_shape(1, Emu(0), Emu(0), Emu(w), Inches(0.09))
bar.fill.solid(); bar.fill.fore_color.rgb = ACCENT; bar.line.fill.background()
if kicker:
tb = s.shapes.add_textbox(Inches(0.7), Inches(0.45), Inches(11.8), Inches(0.4))
tf = tb.text_frame; p = tf.paragraphs[0]; r = p.add_run(); r.text = kicker.upper()
r.font.size = Pt(12); r.font.color.rgb = ACCENT2; r.font.bold = True
tb = s.shapes.add_textbox(Inches(0.7), Inches(0.85), Inches(12.0), Inches(1.0))
tf = tb.text_frame; p = tf.paragraphs[0]; r = p.add_run(); r.text = title
r.font.size = Pt(30); r.font.color.rgb = TEXT; r.font.bold = True
tb = s.shapes.add_textbox(Inches(0.75), Inches(1.85), Inches(11.9), Inches(4.9))
tf = tb.text_frame; tf.word_wrap = True
first = True
for b in bullets:
lvl = 0
if isinstance(b, tuple): b, lvl = b
p = tf.paragraphs[0] if first else tf.add_paragraph(); first = False
r = p.add_run(); r.text = ("• " if lvl == 0 else "– ") + b
r.font.size = Pt(16 if lvl == 0 else 14); r.font.color.rgb = TEXT if lvl == 0 else MUT
p.space_after = Pt(8)
return s
prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5)
K = "Встреча 143 · 2026-10-08"
add_slide(prs, "Разговор Алекса с Леонидом", [
"Выжимка разговора и договорённости · ~2.5 часа",
"Jitsi + агент-визуализатор Simbiot (слайды рисуются в реальном времени)",
"Кооператив 42 / студия Simbiot",
], kicker=K)
add_slide(prs, "Кто есть кто", [
"Леонид — давний знакомый студии, писатель, инвестор в deep-tech (Anti-Aging Hero с физиком Петром Федичевым)",
"DGX Spark 128GB (€5000) — дома гоняет CUDA-моделирование графов надёжности",
"Алекс — инфраструктура студии: агент-визуализатор, собственный роутер LM, RAG по знаниям кооператива",
("«1-й раз такое в моей практике… польза очевидна» — Леонид о демо", 1),
], kicker=K)
add_slide(prs, "Демо инфраструктуры студии", [
"Агент подключается к любому звонку и рисует слайды по ходу разговора (~45 сек на слайд)",
"Роутер сам выбирает LM под контекст (визуализация — GLM 5.3 Flash)",
"RAG: «он знает всё, что мы делаем» — общая база знаний кооператива",
("Инсайт Леонида: домашняя коробочка → быстрая генерация музыки/картинок/мини-видео", 1),
], kicker=K)
add_slide(prs, "Пилот: мультик по рассказу Леонида", [
"Мульт-адаптация рассказов (10–15 мин); Леонид готов финансово поддержать пилот",
"Ориентир бюджета — «в пределах 1000» на эксперимент (токены — точно, труд — обсудить)",
"Вход от студии: рассказ + свобода интерпретации, без пожеланий-ограничений",
"Партнёр уже строит harness генерации мультиков — матч идеальный; видео-часть ведёт Влад",
("Первый шаг — аудиоспектакль (дешевле и предсказуемее), затем видео", 1),
], kicker=K)
add_slide(prs, "Творческий метод Леонида", [
"Хакатон ~2018: нейросеть на сольных песнях Леннона/Маккартни классифицировала битловские",
"Пишет с ИИ (~80% своё / ~20% ИИ), честно маркирует «made with AI» на after.today",
("«Литературные негры» и мастерская Микеланджело — ИИ-ассистированное творчество не ново, нова честность", 1),
], kicker=K)
add_slide(prs, "Экономика локального инференса", [
"Леонид: DGX Spark — нормальная цена за «домашний интеллект»; коробки станут достоянием домов",
"Алекс: облачный always-on — ок для большинства; топ-модели для разработки всё равно выигрывают",
("Сошлись на гибриде: локально — быстрая генерация, облако — тяжёлые продуктовые задачи", 1),
("У студии в планах свой inference-сервер (в ~6 раз быстрее)", 1),
], kicker=K)
add_slide(prs, "Как строить AI-компанию", [
"Леонид: «Vision — говно, execution — всё». Из короткого промпта не вырастет сложная система",
"Алекс: ядро студии — AI-ядро, перестраивающее паттерны (64) при смене контекста — главный долгосрочный актив",
"Стратегия: бизнес на нейро-инфраструктуре с начала → перевод на автопилот; незаменим только «CEO-решатель»",
("«Изюминка студии — в AI-ядре» — Леонид", 1),
("Методология deep-tech: продавать ценность сразу, не влюбляясь в R&D", 1),
], kicker=K)
add_slide(prs, "Договорённости", [
"1. Пилот анимации по рассказу Леонида: токены покрывает Леонид (до ~1000), ведёт Влад; Alex вернётся с конкретным предложением",
"2. Формат разговора продолжается — Леонид открыт к следующим сессиям",
"3. Голоса/доли за прошлые инвестиции — Леонид отказался: «инвестирую из философских соображений, не создавайте ожиданий»",
"4. Леонид посмотрит 35-минутную презентацию студии и вернётся с фидбэком",
], kicker=K)
add_slide(prs, "Что делать студии дальше", [
"Подготовить оффер по мульт-пилоту: объём, этап аудиоспектакля, смета токенов",
"Подключить партнёрский harness генерации мультиков — Леонид как первый клиентский кейс",
"Зафиксировать кейс «агент-визуализатор на внешнем звонке» — первое живое демо, положительный фидбэк",
], kicker=K)
prs.save("out/leonid_meeting143.pptx")
# --- Pitch deck: Simbiot / Seedance ---
prs2 = Presentation(); prs2.slide_width = Inches(13.333); prs2.slide_height = Inches(7.5)
K2 = "Simbiot · питч"
add_slide(prs2, "Simbiot — общий харнес организации", [
"Стартап за неделю получает не одного ассистента, а команду ИИ-агентов:",
"один отвечает клиентам, второй пишет код, третий следит за деньгами и метриками",
"Слой поверх Hermes, Claude Code и других — вместо зоопарка агентов и харнесов",
], kicker=K2)
add_slide(prs2, "Проблема", [
"Фаундер собирает зоопарк из агентов и харнесов вручную",
"Ушёл разработчик — ушла вся магия: знания и автоматизации не переживают людей",
"Каждый новый инструмент — ещё один остров, не связанный с остальными",
], kicker=K2)
add_slide(prs2, "Решение", [
"Simbiot координирует флот агентов и фиксирует решения компании",
"Удачные приёмы копятся как «гены» — остаются только те, что приносят прибыль",
"Компания превращается в самообучающуюся нейро-инфраструктуру",
], kicker=K2)
add_slide(prs2, "Тракшен", [
"Уже гоняется на семи собственных продуктах студии",
"AI-ядро: 64 паттерна, перестраивающихся при смене контекста",
"Агент-визуализатор, RAG по знаниям, собственный LM-роутер — в продакшене",
], kicker=K2)
add_slide(prs2, "Видео (Seedance), 3 шота", [
"Shot 1 «зоопарк в хаосе»: тёмная серверная, хаотичные голограммы и глитчи (0–10 сек)",
"Shot 2 «orchestration»: хаос сходится в фиолетовую сферу, панели выстраиваются в сетку (10–20 сек)",
"Shot 3 «финал»: фаундер у стены дашбордов с растущими revenue-графиками, отъезд в город (20–30 сек)",
("Стиль: dark cinematic, #0a0c12 + violet #7c6cf5, film grain, 4k, 16:9", 1),
], kicker=K2)
add_slide(prs2, "Строй компанию — а не зоопарк ботов", [
"Simbiot · Кооператив 42 · simbiot",
"Хронометраж питча: ~35 сек (15 сек — первый и последний абзац)",
], kicker=K2)
prs2.save("out/simbiot_pitch.pptx")
print("OK")

168
scripts/spa_fill.py Normal file
View file

@ -0,0 +1,168 @@
#!/usr/bin/env python3
# ═══════════════════════════════════════════════════════════
# SPA filler — Studio_Participation_Agreement_2026-09 → per-founder
# Задачи t-261008143050 (Siarhei Zhukovich, когорта с 16-го) и
# t-261008144131 (7 участников 42 Labs за 2026-09).
# Паттерн подстановки — как _contract_docx_bytes в /root/coop/app.py.
# ═══════════════════════════════════════════════════════════
import os, sys, copy
import docx
TEMPLATE = "/root/coop/legal/Studio_Participation_Agreement_2026-09.docx"
OUT = "/root/coop/legal"
DRAFT_LINE = "DRAFT TEMPLATE — for review by U.S. counsel before first use. Not legal advice."
# Перепривязка платежного графика на 16-е число (когорты с 16-го, t-261008143050)
SIXTEENTH = [
("no later than the 1st day of the first month of participation",
"no later than the 16th day of the first month of participation"),
("each subsequent Monthly Fee no later than the 1st day of the corresponding month of participation",
"each subsequent Monthly Fee no later than the 16th day of the corresponding month of participation"),
("If the Agreement is signed after the 1st, the Program Start is the 1st day of the next calendar month",
"If the Agreement is signed after the 16th, the Program Start is the 16th day of the next calendar month"),
("for Monthly Fee tranches, the 1st day of the participation month",
"for Monthly Fee tranches, the 16th day of the participation month"),
("takes effect on the 1st day of the next participation month",
"takes effect on the 16th day of the next participation month"),
("payable in advance by the 1st of each participation month",
"payable in advance by the 16th of each participation month"),
("the 1st day of the month of signature if signed on the 1st, otherwise the 1st day of the next calendar month",
"the 16th day of the month of signature if signed on the 16th, otherwise the 16th day of the next calendar month"),
]
def founder_replacements(f):
"""f: dict(name, passport, pid, address, email, project, one_line, rev_share,
effective, cohort, program_start, sixteenth: bool)"""
r = {
"[DATE]": f["effective"],
"[FULL LEGAL NAME]": f["name"],
"[NAME]": f["name"],
"[EMAIL]": f.get("email") or "[EMAIL]",
"cohort [MONTH YEAR]": f"cohort {f['cohort']}",
"1 [MONTH] [YEAR]": f["program_start"],
"[MONTH YEAR]": f["cohort"],
}
if f.get("rev_share"):
r["[X]"] = str(f["rev_share"])
if f.get("project"):
r["[PROJECT NAME]"] = f["project"]
if f.get("one_line"):
r["[ONE LINE]"] = f["one_line"]
# Строка Founder: имя + статус + паспорт + адрес одной подстановкой
line_old = "[FULL LEGAL NAME], an individual / [ENTITY TYPE], residing at [ADDRESS], [COUNTRY]"
line_new = (f"{f['name']}, an individual, passport of the Republic of Belarus "
f"{f.get('passport') or '[PASSPORT NO.]'} (personal identification No. "
f"{f.get('pid') or '[PERSONAL ID NO.]'}), residing at "
f"{f.get('address') or '[ADDRESS]'}, Republic of Belarus")
r[line_old] = line_new
# фолбэк, если строка разбита рант differently
r["an individual / [ENTITY TYPE]"] = "an individual"
# паспорт/ид.номер вставляются после схлопывания individual-строки
# (порядок ключей dict важен: этот ключ применяется после предыдущего)
pass_line = (f"passport of the Republic of Belarus {f.get('passport') or '[PASSPORT NO.]'}"
f" (personal identification No. {f.get('pid') or '[PERSONAL ID NO.]'}), residing at")
r["an individual, residing at"] = f"an individual, {pass_line}"
r["[ADDRESS], [COUNTRY]"] = f"{f.get('address') or '[ADDRESS]'}, Republic of Belarus"
r["[ADDRESS]"] = f.get("address") or "[ADDRESS]"
r["[COUNTRY]"] = "Republic of Belarus"
return r
def fill(f, out_docx):
doc = docx.Document(TEMPLATE)
reps = founder_replacements(f)
if f.get("sixteenth"):
reps = dict(SIXTEENTH) | reps
reps[DRAFT_LINE] = ""
for p in doc.paragraphs:
if not p.runs:
continue
full = "".join(r.text for r in p.runs)
new = full
for k, v in reps.items():
if k in new:
new = new.replace(k, v)
if new != full:
p.runs[0].text = new
for r in p.runs[1:]:
r.text = ""
doc.save(out_docx)
return out_docx
def to_pdf(docx_path):
import subprocess, tempfile
with tempfile.TemporaryDirectory() as td:
r = subprocess.run(["soffice", "--headless", "--convert-to", "pdf", "--outdir", td, docx_path],
capture_output=True, timeout=90)
pdf = os.path.join(td, os.path.basename(docx_path).replace(".docx", ".pdf"))
if os.path.exists(pdf):
dst = docx_path.replace(".docx", ".pdf")
open(dst, "wb").write(open(pdf, "rb").read())
return dst
return None
def leftover_placeholders(docx_path):
import re
d = docx.Document(docx_path)
found = set()
for p in d.paragraphs:
found.update(re.findall(r"\[[A-Z][A-Z0-9 /.,%()\[\]-]*\]", p.text))
return sorted(found)
FOUNDERS = [
# ── t-261008143050: Siarhei Zhukovich, когорта с 16-го ──
dict(file="SPA_Siarhei_Zhukovich", name="Siarhei Zhukovich",
passport="AB3393306", pid=None, # ид. номер — нет в базе, плейсхолдер
address="28 July str., 33B-42, Brest", email=None,
project="podrabotki.by (rebranding to StaffGo)",
one_line="a platform for hiring line staff: employment/contract placement and turnkey contractor paperwork (documents and payments)",
rev_share=50, effective="October 16, 2026", cohort="October 2026",
program_start="16 October 2026", sixteenth=True),
# ── t-261008144131: участники 42 Labs за 2026-09 (lab_debts) ──
dict(file="SPA_Alex_Shkor", name="Alex Shkor", passport=None, pid=None,
address=None, email="alex@shkor.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Vlad", name="Vlad", passport=None, pid=None,
address=None, email="vla2music.com@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Yura", name="Yura", passport=None, pid=None,
address=None, email="yura28440@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Stas_Dashchinskiy", name="Stas Dashchinskiy", passport=None, pid=None,
address=None, email="dashchinskiystas@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Alexander", name="Александр", passport=None, pid=None,
address=None, email="Justgbox@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Melnikau", name="Andrei Melnikau", passport="MP2848482",
pid="3120386A064PB1", address=None, email="ex3.mac@gmail.com",
project="tYESter",
one_line="a family of software tools for testing computer hardware components without the need to purchase additional equipment",
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Dmitry_Siasitski", name="Dmitry Siasitski", passport=None, pid=None,
address=None, email="sesitski.dmitry@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
]
if __name__ == "__main__":
only = sys.argv[1:] or None
for f in FOUNDERS:
if only and f["file"] not in only:
continue
docx_path = os.path.join(OUT, f["file"] + ".docx")
fill(f, docx_path)
pdf = to_pdf(docx_path)
print(f"{f['file']}: docx={'OK' if os.path.exists(docx_path) else 'FAIL'} "
f"pdf={'OK' if pdf else 'FAIL'} leftover={leftover_placeholders(docx_path)}")

View file

@ -12,6 +12,7 @@
|---|---|---|
| `meta-skill-factory` | Метаскилл: превращение повторяющейся работы в скилл репо (шаги, чеклисты, критерии качества) | Заметил, что делаю одну и ту же работу 2+ раз — прежде чем делать руками в третий |
| `presale-landing` | Производство пресейл/продуктовых лендингов: структура, честные мокапы, Stripe CTA, quality gates | Задача на лендинг/пресейл-страницу или её v2 |
| `gcal` | Google Calendar: команда из TG «сделай звонок на X» → событие с приглашениями в рабочий день (цикл 3 через 3); инструменты tools/gcal.py | Просьба спланировать звонок/встречу/событие, вопрос по графику |
## Сторонние наборы

View file

@ -0,0 +1,73 @@
---
description: Build compact premium landing pages with clear CTA hierarchy, quiet typography, and minimal visual noise. Install from Danilaa1 in one command.
---
# Compact Landing
Build a small, finished product world—not a generic marketing template. Preserve compactness and clarity; reinvent the visible design language for each product.
## Workflow
1. Inspect the product, supplied content, current stack, and existing design language. Reuse installed tools and components.
2. Ask for button size first—compact, medium, or large—unless the user already answered or asked you to choose.
3. Ask at most two adaptive questions only when their answers would materially change the result. Good subjects: brand mood, content depth, corner character, or motion level.
4. Create a design fingerprint before coding. If this skill includes scripts and Node is available, run `node scripts/pick-direction.mjs`. Otherwise choose from `references/style-dna.md` without taking the first option in every list.
5. Adapt the fingerprint to product meaning. Treat picker output as a creative constraint, not a command to make incoherent choices.
6. Identify one product-native signature artifact: a real demo, visualized output, interactive sample, comparison, miniature workflow, data specimen, or useful control. This should create identity more than decoration does.
7. Implement the smallest complete page. Prefer HTML/CSS and existing dependencies. Add JavaScript only for behavior the page actually needs.
8. Verify mobile layout, keyboard focus, reduced motion, stable dynamic states, and the real primary action.
If the user wants immediate execution, choose the button size and fingerprint yourself. Do not stop for a questionnaire.
## Design Fingerprint
Define these axes before implementation:
- **Composition:** field note, offset rail, split pamphlet, framed console, compact catalogue, stacked specimen, or another product-specific silhouette.
- **Measure:** choose a fitting compact width, usually `420-720px`; do not default to `440px` every time.
- **Palette:** warm paper, cool fog, monochrome ink, dark charcoal, product-tinted, or playful light.
- **Typography:** grotesk + mono, humanist sans + mono, editorial serif + sans, rounded sans + mono, condensed + sans, or purposeful all-mono.
- **Geometry:** sharp, machined, balanced, friendly, soft, or mixed/concentric radii.
- **Surface grammar:** whitespace, flat dividers, inset hairlines, layered rings, tinted panels, paper rules, or technical frames. Pick one dominant grammar.
- **Motif:** emoji punctuation, ASCII marks, indices, stamps, doodles, colored dots, tiny object icons, or none.
- **Motion:** choose one entrance behavior and one or two interaction behaviors.
Variation must change the first-screen silhouette, not merely colors or labels. Ensure at least four axes differ from the old canonical recipe: centered `440px` column, neutral white/zinc, Geist + Geist Mono, `6-10px` radii, inset hairline cards, Instrument order, staggered rise.
Do not randomize components independently. Choose one coherent art direction, then derive tokens from it. Use only one or two surprising details; repetition inside the page creates coherence.
Read `references/style-dna.md` for compatible ranges, direction families, motion patterns, and review checks.
## Invariants
- Keep the page compact in scope and information density, even when its canvas is wider or split.
- Put the primary action in the first viewport. Repeat it only when doing so removes friction.
- Use clear type hierarchy, concrete copy, and enough contrast.
- Use real product content. Avoid fake metrics, vague claims, filler testimonials, and decorative dashboards.
- Extend visually tiny controls to at least a `40px` hit area without making them look oversized.
- Show a visible `:focus-visible` state and preserve semantic HTML.
- Use tabular numerals for changing numbers.
- Respect `prefers-reduced-motion` and keep the reduced experience fully usable.
- Never add a dependency for a small effect CSS or the current stack can handle.
## Zero-Shift Motion
- Animate only `transform`, `opacity`, and `filter`. Never use `transition: all`.
- Reserve dimensions before animation: fixed/min heights, `aspect-ratio` for media, fixed icon slots, width-locked changing labels, and tabular numerals.
- Overlay alternate labels, icons, tabs, and panels in the same grid area or fixed box. Do not insert delayed content into document flow.
- Keep padding, borders, font size, font weight, line height, grid tracks, and flex sizing stable across hover and state changes.
- Prefer transforms for toggles, drawers, traces, and decorative motion. Absolute-position decorative particles so they never affect flow.
- Self-host fonts when practical. Otherwise use metric-compatible fallbacks and avoid hiding the page while fonts load.
- Gate hover-only effects behind `(hover: hover) and (pointer: fine)`.
## Buttons
Apply one scale consistently:
- **Compact:** `24-28px` visual height, `11-12px` label.
- **Medium:** `32-36px` visual height, `12.5-13px` label.
- **Large:** `40-44px` visual height, `13.5-14px` label.
Use `scale(0.96)` for press feedback. For wide command controls, use `0.98`. Keep label and icon slots stable during copied/loading/success states.
For `llms.txt` or assistant-copy actions, use a small mono button only when the product needs it. Reuse the project's icon system or a CSS glyph; do not embed SVG examples in skill output.
## Purposeful Personality
Emoji are allowed when they fit the product. Use `1-3` as punctuation, labels, tiny stickers, or state cues—not as a substitute for structure or iconography. Reserve their boxes so platform glyph differences do not move layout.
Color may be playful, dark, editorial, or product-tinted. Give every accent a role and repeat it. Avoid default purple-blue AI gradients, uncontrolled rainbows, and random decorative color.
Motion should explain state, focus attention, or express the product metaphor. A fixed-size trace, ambient floating mark, slot swap, clipped reveal, or state color wash can feel alive without causing reflow. Avoid perpetual motion everywhere.
## Avoid
- Reusing one section order, narrow width, palette, radius family, or component set across unrelated products.
- Generic oversized heroes, three equal feature cards, badge clouds, and feature grids by reflex.
- Copying the Cuelume waveform, cream palette, confetti trio, or exact layout unless the product genuinely calls for them.
- Making every surface a card or every control a pill.
- Decorative animation that competes with the primary action.
- Heavy animation libraries for opacity, transforms, tabs, counters, or simple reveals.
## Final Check
- Fingerprint chosen before code; at least four high-salience axes differ from the canonical recipe.
- Product-native signature artifact is visible and useful.
- First viewport has a distinct silhouette and clear primary action.
- Radius, palette, type, surface, motif, and motion form one coherent direction.
- Mobile becomes a clean single column without horizontal overflow.
- Dynamic content, media, icons, labels, and panels have reserved geometry.
- Keyboard, focus, contrast, touch targets, and reduced motion work.
- Build and the primary interaction pass using the project's existing verification path.

66
skills/gcal/SKILL.md Normal file
View file

@ -0,0 +1,66 @@
---
name: gcal
description: Use when Alex asks in TG to schedule a call/meeting ("сделай звонок на X с Владой", "запланируй созвон", "поставь встречу") — creates a Google Calendar event with guest invites in a workday slot (Alex's «3 через 3» cycle), or checks the schedule. Powered by tools/gcal.py.
metadata:
origin: task t-261008155436-a658e9, messages m-516173943/m-516173944
requires: secrets/gcal-service-account.json (service account key; until provided — dry-run/planning only)
---
# gcal: команда из TG → событие в календаре Alex
Одна команда из TG должна решаться одним прогоном этого скилла:
интеншн → валидный слот (рабочий день!) → событие с приглашением → ответ в чат.
## Шаг 1 — разобрать команду
Из сообщения извлечь:
- **что**: тема (по умолчанию «Звонок: <тема/имя гостя>»);
- **когда**: день (сегодня/завтра/дата) и время; длительность (по умолчанию 30 мин);
- **кого**: email гостей. Известные контакты: Влада — videoprodshema@gmail.com.
Email НЕ известен → спросить у Alex ДО создания (угадывать адрес нельзя);
- **время**: Alex в Минске (UTC+3). Время из TG трактовать как минское,
в инструмент передавать UTC.
## Шаг 2 — валидация слота (строго до создания)
```
./venv/bin/python tools/gcal.py next-workdays # ближайшие рабочие
./venv/bin/python tools/gcal.py create ... --dry-run # что попадёт в Google
```
Guard'ы (встроены в create, не обходить):
1. **Только рабочий день** — цикл «3 через 3» (docs/alex-schedule.md).
Якорь цикла: env GCAL_WORK_ANCHOR (по умолчанию 2026-10-02). Сдвиг цикла
Alex сообщил в TG → обновить docs/alex-schedule.md и якорь.
2. **Не позже 2ч до начала** (GCAL_MIN_LEAD_MIN=120) — анти-паттерн рекордера
(t-261008155708: встречи, созданные <2 мин до старта, не заходятся).
Выходной/поздний слот → НЕ создавать, ответить Alex с ближайшими рабочими
днями и попросить подтвердить слот.
## Шаг 3 — создать событие
```
./venv/bin/python tools/gcal.py create \
--title "Звонок: Влада" --day YYYY-MM-DD --time HH:MM \
--duration 30 --guests "videoprodshema@gmail.com" --timezone Europe/Minsk
```
`sendUpdates=all` — приглашения уходят гостям автоматически.
Ответ в чат: ссылка на событие (htmlLink), слот, гости.
## Ключ (один раз, если не настроен)
`tools/gcal.py check` печатает готовность и шаги:
Google Cloud → Calendar API → service account → JSON-ключ → дать
client_email право «Вносить изменения» на календаре Alex → положить в
`secrets/gcal-service-account.json` (0600). Ключа нет → работать dry-run'ом
и попросить Alex прислать ключ (это может сделать только владелец аккаунта).
## Критерии качества
- [ ] Слот — рабочий день цикла; иначе отказ с предложением ближайших рабочих
- [ ] До начала ≥2ч (или осознанный --force по явной просьбе Alex)
- [ ] Гости получили приглашения (sendUpdates=all), email не угадан
- [ ] В ответе Alex — время в его зоне (Минск) и ссылка на событие
- [ ] `check` зелёный; ключ лежит в secrets/ с правами 0600

View file

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
"""t-261009160906: анти-гонка уровней min/hour на входящих TG (hermetic).
Кейс 09.10: Alex попросил концепт (m-516174013). Мин-тик ответил и доставил
свой файл (16:01). Часовой тик, не видя ответа мин-уровня, сгенерировал СВОЙ
файл и доставил вторым (16:06) — Alex получил два разных файла на один запрос.
Dedup по artifact_id=file:<путь> не ловит: два разных файла = два разных ключа.
Фикс: связка «артефакт → входящее» (-m m-NNN у tg-deliver-file, msg= у
deliver_artifact, reply_artifact в read model). Если на входящее уже отвечено
иным артефактом — SKIP с событием. force обходит (ручная повторная доставка).
Запуск: ./venv/bin/python tests/test_msg_artifact_race.py
"""
import os
import subprocess
import sys
# ── изоляция: своя БД в /tmp (prod не трогаем) ──
_DST = "/tmp/simbiot-test-msg-artifact-race.db"
for p in (_DST, _DST + "-wal", _DST + "-shm"):
if os.path.exists(p):
os.unlink(p)
os.environ["SIMBIOT_DB"] = _DST
sys.path.insert(0, "/root/simbiot-harness")
from harness import es, commands
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
if not cond:
fails.append(name)
es.init_db()
# ── подготовка: входящее m-990001, на которое мин-уровень уже ответил ──
es.append_event("inbox", "m-990001", "MessageReceived", {
"update_id": 990001, "chat_id": 42, "message_id": 1, "user_id": 1,
"text": "сделай концепт в html", "kind": "text", "date": 1791560000,
})
es.append_event("inbox", "m-990001", "ReplySent", {
"to_chat": 42, "text": "готово, отправил файлом", "tg_ok": True, "channel": "tg",
})
# файлы-двойники гонки: мин-уровень и часовой
_F1 = "/tmp/simbiot-test-race-min.html"
_F2 = "/tmp/simbiot-test-race-hour.html"
for f in (_F1, _F2):
with open(f, "w") as fh:
fh.write("<html><body>r</body></html>")
sends = []
# 1) мин-уровень доставил свой файл с привязкой к сообщению
ok1 = commands.deliver_artifact(f"file:{_F1}", kind="file", send=lambda: (sends.append(_F1) or True),
owner="test:min", caption="мин", msg="m-990001")
check("min-level file delivered", ok1 and sends == [_F1], sends)
model = es.run_projections()
m = model["inbox"]["m-990001"]
check("read model binds reply_artifact", m.get("reply_artifact") == f"file:{_F1}", m.get("reply_artifact"))
# 2) часовой уровень пытается доставить СВОЙ файл на то же входящее → SKIP
ok2 = commands.deliver_artifact(f"file:{_F2}", kind="file", send=lambda: (sends.append(_F2) or True),
owner="test:hour", caption="час", msg="m-990001")
check("hour-level duplicate SKIPped", ok2 is False and sends == [_F1], sends)
db = es._conn()
skip = list(db.execute(
"SELECT data FROM events WHERE event_type='DeliverySkipped' AND data LIKE '%msg-already%'"))
check("DeliverySkipped event with reason", len(skip) == 1, len(skip))
# 3) force обходит защиту (ручная повторная доставка — явное решение человека)
ok3 = commands.deliver_artifact(f"file:{_F2}", kind="file", send=lambda: (sends.append(_F2) or True),
owner="test:hour", caption="час", force=True, msg="m-990001")
check("force bypasses guard", ok3 and sends == [_F1, _F2], sends)
# 4) доставки БЕЗ привязки к сообщению не задеваются (обратная совместимость)
ok4 = commands.deliver_artifact("file:/tmp/simbiot-test-race-unbound.html", kind="file",
send=lambda: (sends.append("unbound") or True),
owner="test", caption="x")
check("unrelated delivery unaffected", ok4 and sends[-1] == "unbound", sends)
# 5) второе входящее без артефакта: доставка с привязкой проходит свободно
es.append_event("inbox", "m-990002", "MessageReceived", {
"update_id": 990002, "chat_id": 42, "message_id": 2, "user_id": 1,
"text": "ещё один запрос", "kind": "text", "date": 1791560100,
})
es.append_event("inbox", "m-990002", "ReplySent", {
"to_chat": 42, "text": "ок", "tg_ok": True, "channel": "tg",
})
ok5 = commands.deliver_artifact("file:/tmp/simbiot-test-race-second.html", kind="file",
send=lambda: (sends.append("second") or True),
owner="test:hour", caption="y", msg="m-990002")
check("fresh msg accepts its artifact", ok5 and sends[-1] == "second", sends)
# 6) CLI: tg-deliver-file -m m-990001 с чужим артефактом → SKIP, выход 0
r = subprocess.run(
["./venv/bin/python", "cli.py", "tg-deliver-file", _F2, "-m", "m-990001",
"-c", "dup", "-k", "file:" + _F2],
capture_output=True, text=True, cwd="/root/simbiot-harness")
check("CLI SKIP on raced msg", r.returncode == 0 and "SKIP" in r.stdout,
r.stdout.strip()[:120] + r.stderr.strip()[-200:])
print("=" * 40)
print("FAILS:", len(fails), fails if fails else "— все проверки прошли")
sys.exit(1 if fails else 0)

290
tools/gcal.py Executable file
View file

@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""gcal.py — Google Calendar для харнеса: create-event / list / check (t-261008155436).
Команда из TG «сделай звонок на X с Владой» → минутный тик грузит скилл
skills/gcal/SKILL.md → зовёт этот инструмент → событие в календаре Alex
с приглашением гостям, слот только на рабочий день (цикл «3 через 3»).
Автоматизация (service account, JWT → access token, REST v3 — без google-клиентов):
1) Alex: console.cloud.google.com → новый проект → включить Calendar API →
«Service accounts» → создать → ключ JSON → прислать файл.
2) В календаре Google: Настройки → «Настройки для моего календаря» →
«Предоставить доступ» → добавить client_email ключа с ролью
«Вносить изменения» (make changes to events).
3) Положить ключ: /root/simbiot-harness/secrets/gcal-service-account.json
(или env GCAL_SERVICE_ACCOUNT_FILE). Файл 0600.
4) ./gcal.py check — готовность. Готово: скилл создаёт события без человека.
Если ключа нет — работают --dry-run, check, next-workdays (планирование слотов).
"""
from __future__ import annotations
import argparse
import base64
import json
import os
import sys
import time
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
HERE = Path(__file__).resolve().parent
REPO = HERE.parent
DEFAULT_KEY = REPO / "secrets" / "gcal-service-account.json"
TOKEN_CACHE = Path("/tmp/.gcal_access_token.json")
# ── График Alex: цикл «3 через 3» ─────────────────────────────────────────────
# docs/alex-schedule.md: 3 рабочих, 3 выходных. Якорь выведен из m-516173591
# (2026-10-03: «сегодня и завтра рабочие, далее 3 выходных» → 10-02 первый
# рабочий цикла). Проверка: 10-08 = (10-08−10-02)=6 → 6%6=0 → рабочий (совпало
# с «сегодня/завтра рабочие» от 10-08). Сдвиг цикла: env GCAL_WORK_ANCHOR=YYYY-MM-DD
ANCHOR = os.environ.get("GCAL_WORK_ANCHOR", "2026-10-02")
PERIOD = 6
WORK_DAYS = {0, 1, 2} # смещения внутри цикла: 0,1,2 — рабочие; 3,4,5 — выходные
MIN_LEAD_MIN = int(os.environ.get("GCAL_MIN_LEAD_MIN", "120")) # анти-паттерн рекордера: не позже 2ч до начала
def _anchor() -> date:
return datetime.strptime(ANCHOR, "%Y-%m-%d").date()
def is_workday(d: date) -> bool:
return ((d - _anchor()).days % PERIOD) in WORK_DAYS
def next_workdays(n: int = 6, from_day: date | None = None) -> list[date]:
d = from_day or date.today()
out: list[date] = []
while len(out) < n:
if is_workday(d):
out.append(d)
d += timedelta(days=1)
return out
# ── Креды и токен (service account → JWT → access token) ─────────────────────
def _key_path(explicit: str | None) -> Path:
p = Path(explicit or os.environ.get("GCAL_SERVICE_ACCOUNT_FILE") or DEFAULT_KEY)
return p
def load_key(explicit: str | None = None) -> dict | None:
p = _key_path(explicit)
if not p.exists():
return None
return json.loads(p.read_text())
def _b64(o: dict | bytes) -> str:
raw = json.dumps(o).encode() if isinstance(o, dict) else o
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
def get_access_token(key: dict, force: bool = False) -> str:
"""JWT (RS256) → Google OAuth2 → access token (кэш ~50 мин)."""
if not force and TOKEN_CACHE.exists():
try:
c = json.loads(TOKEN_CACHE.read_text())
if c.get("exp", 0) > time.time() + 120 and c.get("iss") == key["client_email"]:
return c["token"]
except Exception:
pass
import httpx # уже в venv харнеса
now = int(time.time())
claim = {
"iss": key["client_email"],
"scope": "https://www.googleapis.com/auth/calendar",
"aud": "https://oauth2.googleapis.com/token",
"iat": now,
"exp": now + 3600,
}
head = {"alg": "RS256", "typ": "JWT"}
signing_input = f"{_b64(head)}.{_b64(claim)}".encode()
from cryptography.hazmat.primitives import hashes, serialization
pk = serialization.load_pem_private_key(key["private_key"].encode(), password=None)
sig = pk.sign(signing_input, hashes.SHA256())
jwt = f"{signing_input.decode()}.{_b64(sig)}"
r = httpx.post(
"https://oauth2.googleapis.com/token",
data={"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": jwt},
timeout=20,
)
if r.status_code != 200:
raise RuntimeError(f"token exchange failed: {r.status_code} {r.text[:200]}")
tok = r.json()
TOKEN_CACHE.write_text(json.dumps({"token": tok["access_token"], "exp": now + tok.get("expires_in", 3600),
"iss": key["client_email"]}))
TOKEN_CACHE.chmod(0o600)
return tok["access_token"]
def _api(method: str, path: str, key: dict, body: dict | None = None, params: dict | None = None) -> dict:
import httpx
tok = get_access_token(key)
r = httpx.request(method, f"https://www.googleapis.com/calendar/v3{path}",
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
json=body, params=params or {}, timeout=25)
if r.status_code >= 400:
raise RuntimeError(f"calendar api {r.status_code}: {r.text[:300]}")
return r.json()
# ── Действия ──────────────────────────────────────────────────────────────────
def cmd_check(args) -> int:
print(f"anchor (цикл 3 через 3): {ANCHOR} (env GCAL_WORK_ANCHOR для сдвига)")
today = date.today()
print(f"сегодня {today}: {'РАБОЧИЙ' if is_workday(today) else 'выходной'}")
print("ближайшие рабочие:", ", ".join(d.isoformat() for d in next_workdays(4)))
key = load_key(args.key)
if not key:
p = _key_path(args.key)
print(f"\nключ: НЕТ ({p})")
print("шаги: Google Cloud → Calendar API → service account → JSON-ключ →")
print(" доступ к календарю Alex для client_email (make changes) →")
print(f" положить в {p} (0600). Затем: gcal.py check && gcal.py list")
return 1
print(f"\nключ: OK ({key.get('client_email', '?')})")
try:
cal = _api("GET", f"/calendars/{args.calendar}", key)
print(f"календарь: {cal.get('summary')} ({cal.get('id')}) — доступ есть")
return 0
except Exception as e:
print(f"доступ к календарю: НЕ УДАЛСЯ — {e}")
print("→ дайте client_email право «Вносить изменения» в настройках календаря")
return 2
def _guard(slot_start: datetime) -> tuple[bool, str]:
d = slot_start.date()
if not is_workday(d):
nxt = next_workdays(1, from_day=d + timedelta(days=1))
sug = ", ".join(x.isoformat() for x in nxt)
return False, f"{d.isoformat()} — выходной по циклу Alex «3 через 3». Ближайшие рабочие: {sug}"
lead = (slot_start - datetime.now(timezone.utc)).total_seconds() / 60
if lead < MIN_LEAD_MIN:
return False, (f"до начала {int(lead)} мин < {MIN_LEAD_MIN} (анти-паттерн рекордера: "
f"t-261008155708 — встречи <2 мин до старта не заходятся). Возьмите слот позже.")
return True, "ok"
def _parse_slot(day: str, hhmm: str, dur: int, tz_name: str) -> datetime:
"""Локальное время дня в tz_name → момент UTC (API Google требует offset)."""
from zoneinfo import ZoneInfo
d = date.fromisoformat(day)
hh, mm = map(int, hhmm.split(":"))
return datetime(d.year, d.month, d.day, hh, mm, tzinfo=ZoneInfo(tz_name)).astimezone(timezone.utc)
def cmd_create(args) -> int:
start = _parse_slot(args.day, args.time, args.duration, args.timezone)
ok, msg = _guard(start)
if not ok and not args.force:
print(f"ОТКАЗ: {msg}")
return 3
if not ok:
print(f"ВНИМАНИЕ (force): {msg}")
end = start + timedelta(minutes=args.duration)
guests = [g.strip() for g in (args.guests or "").split(",") if g.strip()]
event = {
"summary": args.title,
"description": args.description or "Создано харнесом Simbiot (skills/gcal, t-261008155436-a658e9)",
"start": {"dateTime": start.isoformat(), "timeZone": args.timezone},
"end": {"dateTime": end.isoformat(), "timeZone": args.timezone},
"attendees": [{"email": g} for g in guests],
"reminders": {"useDefault": False, "overrides": [
{"method": "popup", "minutes": 30}, {"method": "popup", "minutes": 10},
]},
}
if args.dry_run:
print("DRY-RUN (в Google не писали). Событие:")
print(json.dumps(event, ensure_ascii=False, indent=1))
print(f"guard: {msg}; слот рабочий: {ok}")
return 0
key = load_key(args.key)
if not key:
print("нет сервис-ключа Google — см. gcal.py check (шаги настройки)")
return 1
made = _api("POST", f"/calendars/{args.calendar}/events", key, body=event,
params={"sendUpdates": "all"})
print(f"СОЗДАНО: {made.get('htmlLink')}")
print(f" {made.get('summary')} · {start:%Y-%m-%d %H:%M} UTC ({args.timezone}) · "
f"гости: {', '.join(guests) or '—'}")
return 0
def cmd_list(args) -> int:
key = load_key(args.key)
if not key:
print("нет сервис-ключа Google — см. gcal.py check")
return 1
tmin = datetime.now(timezone.utc).isoformat()
data = _api("GET", f"/calendars/{args.calendar}/events", key,
params={"timeMin": tmin, "maxResults": args.limit, "orderBy": "startTime",
"singleEvents": "true"})
evs = data.get("items", [])
if not evs:
print("ближайших событий нет")
return 0
for e in evs:
when = (e.get("start", {}).get("dateTime") or e.get("start", {}).get("date", ""))[:16]
print(f" {when} {e.get('summary', '(без названия)')}")
return 0
def cmd_next(args) -> int:
for d in next_workdays(args.count):
off = ((d - _anchor()).days % PERIOD)
print(f" {d.isoformat()} рабочий #{off + 1}/3")
return 0
def main() -> int:
ap = argparse.ArgumentParser(description="Google Calendar для харнеса (create-event по команде из TG)")
sub = ap.add_subparsers(dest="cmd", required=True)
c = sub.add_parser("check", help="готовность: ключ, доступ, график")
c.add_argument("--key", help="путь к JSON сервис-аккаунта")
c.add_argument("--calendar", default=os.environ.get("GCAL_CALENDAR", "primary"))
c.set_defaults(fn=cmd_check)
c = sub.add_parser("create", help="создать событие (слот только рабочий день)")
c.add_argument("--title", required=True)
c.add_argument("--day", required=True, help="YYYY-MM-DD")
c.add_argument("--time", required=True, help="HH:MM (UTC)")
c.add_argument("--duration", type=int, default=30, help="минуты")
c.add_argument("--guests", default="", help="email через запятую (приглашения)")
c.add_argument("--description", default="")
c.add_argument("--timezone", default=os.environ.get("GCAL_TZ", "UTC"))
c.add_argument("--calendar", default=os.environ.get("GCAL_CALENDAR", "primary"))
c.add_argument("--key")
c.add_argument("--dry-run", action="store_true", help="показать событие, в Google не писать")
c.add_argument("--force", action="store_true", help="игнорировать guard (осознанно)")
c.set_defaults(fn=cmd_create)
c = sub.add_parser("list", help="ближайшие события")
c.add_argument("--limit", type=int, default=10)
c.add_argument("--calendar", default=os.environ.get("GCAL_CALENDAR", "primary"))
c.add_argument("--key")
c.set_defaults(fn=cmd_list)
c = sub.add_parser("next-workdays", help="ближайшие рабочие дни (цикл 3 через 3)")
c.add_argument("count", nargs="?", type=int, default=6)
c.set_defaults(fn=cmd_next)
a = ap.parse_args()
try:
return a.fn(a)
except Exception as e: # noqa: BLE001 — инструмент отвечает кодом, не трейсбеком
print(f"ОШИБКА: {type(e).__name__}: {e}")
return 4
if __name__ == "__main__":
sys.exit(main())

View file

@ -1,8 +1,14 @@
{
"permission": {
"edit": "allow",
"bash": "allow",
"webfetch": "allow"
},
"share": "disabled"
"permission": { "edit": "allow", "bash": "allow", "webfetch": "allow" },
"share": "disabled",
"provider": {
"simbiot": {
"npm": "@ai-sdk/openai-compatible",
"name": "Simbiot Router (cil.by)",
"options": { "baseURL": "https://router.cil.by/v1" },
"models": {
"gpt-5.2": {}, "claude-sonnet-4.6": {}, "gpt-4.1": {}, "o4-mini": {}
}
}
}
}

View file

@ -0,0 +1,8 @@
{
"permission": {
"edit": "allow",
"bash": "allow",
"webfetch": "allow"
},
"share": "disabled"
}

2
~/.profile Normal file
View file

@ -0,0 +1,2 @@
export OPENAI_API_KEY=sk-9ffbac9384bc446eab3bbe58a4a98dc3f4b2347d341caf51a34ce93149fd8c57f1eff2065f89025e
export OPENAI_BASE_URL=https://router.cil.by/v1