Compare commits

..

3 commits

17 changed files with 1612 additions and 4 deletions

View file

@ -0,0 +1,70 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Общее видение — панельная сессия ИИШНИЦЫ</title>
<style>
body{font-family:Georgia,serif;max-width:860px;margin:40px auto;padding:0 20px;color:#222;line-height:1.55}
h1{color:#1a5276} h2{color:#1a5276;border-bottom:2px solid #1a5276;padding-bottom:4px}
table{border-collapse:collapse;width:100%;margin:12px 0}
td,th{border:1px solid #aaa;padding:8px;text-align:left;vertical-align:top}
th{background:#eaf2f8}
.tag{display:inline-block;background:#1a5276;color:#fff;border-radius:12px;padding:2px 10px;margin:2px;font-size:.85em}
.box{background:#f4f9fd;border-left:4px solid #1a5276;padding:10px 14px;margin:12px 0}
ul{margin:6px 0}
</style>
</head>
<body>
<h1>«Общее видение»</h1>
<p><b>Панельная сессия ИИШНИЦЫ · 90 минут · 3 спикера (члены правления кооператива 42)</b></p>
<div class="box"><b>Рамка:</b> миссия кооператива 42 — «максимизация долгосрочного AUM: сумма всех измеримых ресурсов каждого члена на длинной дистанции». Сессия показывает: личное будущее каждого и общее видение сообщества — это один и тот же ресурс, если его проговорить вслух.</div>
<h2>Тайминг (90 мин)</h2>
<table>
<tr><th>Время</th><th>Блок</th><th>Что происходит</th></tr>
<tr><td>0–5</td><td>Открытие</td><td>Модератор: миссия 42 как рамка, представление спикеров и формата «три круга».</td></tr>
<tr><td>5–17</td><td>Круг 1 — «Моё будущее» (4 мин/спикер)</td><td>Каждый спикер за 4 минуты рассказывает, каким видит своё будущее через призму миссии кооператива. Без презентаций — живой рассказ.</td></tr>
<tr><td>17–25</td><td>Пауза-рефлексия</td><td>Модератор резюмирует 3 услышанных будущего, гости отмечают в карточках «что откликнулось».</td></tr>
<tr><td>25–31</td><td>Круг 2 — «Что понравилось у других» (2 мин/спикер)</td><td>Каждый спикер называет, что из сказанного коллегами ему ближе всего и почему.</td></tr>
<tr><td>31–45</td><td>Название общего видения</td><td>Модератор выносит на сцену заготовки-названия; зал и спикеры голосуют стикерами/руками, рождается общее название видения.</td></tr>
<tr><td>45–85</td><td>Вопросы из зала</td><td>Открытый микрофон; спикеры отвечают по кругу. Модератор держит рамку миссии, фиксирует обещания/следующие шаги на флипчарт.</td></tr>
<tr><td>85–90</td><td>Закрытие</td><td>Модератор озвучивает родившееся название общего видения + 3 следующих шага. Общее фото.</td></tr>
</table>
<h2>Роли</h2>
<table>
<tr><th>Роль</th><th>Кто</th><th>Зона ответственности</th></tr>
<tr><td>Модератор</td><td>Спикер 1</td><td>Тайминг, рамка миссии, голосование за название, сбор вопросов.</td></tr>
<tr><td>Спикер</td><td>Спикер 2</td><td>Круг 1 (4 мин), круг 2 (2 мин), ответы на вопросы.</td></tr>
<tr><td>Спикер / scriba</td><td>Спикер 3</td><td>Круги 1–2 + фиксация следующего шага и названия на флипчарт.</td></tr>
<tr><td>Организатор</td><td>из команды ИИШНИЦЫ</td><td>Чек-лист ниже.</td></tr>
</table>
<h2>Заготовки названий общего видения</h2>
<p>
<span class="tag">«Капитал роста»</span>
<span class="tag">«42 будущих»</span>
<span class="tag">«Ресурс, который множится»</span>
<span class="tag">«Одна орбита»</span>
<span class="tag">«Мы — AUM друг друга»</span>
<span class="tag">«Кооператив как среда роста»</span>
<span class="tag">«Точка сборки 42»</span>
</p>
<p><i>Правило: если зал предложит своё — оно в приоритете; голосование простым большинством.</i></p>
<h2>Чек-лист организатора</h2>
<ul>
<li>☑ Анонс сессии отправлен (формат «три круга», 3 спикера — члены правления 42)</li>
<li>☑ Таймер на сцене + карточки «что откликнулось» для гостей</li>
<li>☑ Флипчарт/доска, маркеры, стикеры для голосования за название</li>
<li>☑ 2 микрофона для зала, один у модератора</li>
<li>☑ Распечатка заготовок названий крупным шрифтом</li>
<li>☑ Фото/видео на закрытие, место для общего фото</li>
<li>☑ Кто забирает флипчарт с названием и шагами после сессии</li>
</ul>
<p style="color:#666;font-size:.9em">Концепт подготовлен агентом харнеса Simbiot · кооператив 42 · 2026</p>
</body>
</html>

View file

@ -0,0 +1,693 @@
<!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="Simbiot is the operating system for product studios: a dedicated AI team — engineers, video producers, content makers — plus the layer that runs projects, treasury, decisions and meetings. White-label, $999/mo flat.">
<meta property="og:title" content="Simbiot — The Studio OS">
<meta property="og:description" content="A dedicated AI team for your studio plus the OS that runs it: projects, treasury, decisions, meetings. White-label, flat rate.">
<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 property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Simbiot — The Studio OS">
<meta name="twitter:description" content="A dedicated AI team for your studio plus the OS that runs it: projects, treasury, decisions, meetings.">
<meta name="twitter:image" content="https://simbiot.cil.by/og.png">
<noscript><style>.reveal{opacity:1 !important;transform:none !important}</style></noscript>
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--paper:#faf8f3; --paper2:#f2efe7; --ink:#1a1a18; --ink2:#55534b; --ink3:#6b685c;
--line:#e2ded2; --line2:#cfcabb;
--acc:#5b4be0; /* simbiot violet — single signal color */
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
section[id],header[id],div[id]{scroll-margin-top:80px}
body{font-family:'Inter Tight',system-ui,-apple-system,sans-serif;background:var(--paper);color:var(--ink);-webkit-font-smoothing:antialiased;overflow-x:hidden}
.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
.wrap{max-width:1240px;margin:0 auto;padding:0 32px}
/* ── nav ── */
nav{position:fixed;top:0;left:0;right:0;z-index:100;background:var(--paper);border-bottom:1px solid var(--line)}
nav .wrap{display:flex;align-items:center;height:60px}
.logo{display:flex;align-items:center;gap:10px;font-size:17px;font-weight:600;letter-spacing:-.3px}
.logo-mark{width:14px;height:14px;background:var(--acc)}
nav .links{margin-left:auto;display:flex;gap:28px;align-items:center}
nav .links a{color:var(--ink2);text-decoration:none;font-size:13.5px;font-weight:500}
nav .links a:hover{color:var(--ink)}
@media(max-width:760px){nav .links a:not(.btn){display:none}}
.btn{display:inline-block;background:var(--ink);color:#faf8f3;border:1px solid var(--ink);padding:14px 26px;font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:500;text-transform:uppercase;letter-spacing:.8px;cursor:pointer;text-decoration:none;transition:background .15s,color .15s}
.btn:hover{background:var(--acc);border-color:var(--acc);color:#fff}
.btn-o{background:transparent;color:var(--ink)}
.btn-o:hover{background:var(--ink);color:#faf8f3}
.btn-sm{padding:9px 18px;font-size:12px}
/* ── hero ── */
.hero{padding:170px 0 100px;border-bottom:1px solid var(--line)}
/* ── included (v2, t-261002151244-03c0c4) ── */
.inc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border:1px solid var(--line)}
.inc{background:var(--paper);padding:34px 30px;position:relative}
.inc .n{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--acc);letter-spacing:.1em;margin-bottom:18px}
.inc h3{font-size:16.5px;font-weight:600;letter-spacing:-.01em;margin-bottom:10px}
.inc p{font-size:14px;line-height:1.6;color:var(--ink2)}
.inc .tag{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink3);border:1px solid var(--line2);border-radius:999px;padding:4px 10px;margin-top:14px}
.seats-live{display:flex;align-items:center;gap:12px;margin-top:26px;padding:14px 20px;border:1px solid var(--acc);background:rgba(91,75,224,.05);font-size:14px;color:var(--ink2)}
.seats-live .dot{width:8px;height:8px;border-radius:50%;background:var(--acc);animation:pulse 2s infinite}
.seats-live b{color:var(--ink);font-size:16px}
.seats-live a{color:var(--acc);text-decoration:none;font-weight:500}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
.hero .wrap{display:grid;grid-template-columns:1.25fr .85fr;gap:64px;align-items:start}
@media(max-width:980px){
.hero .wrap{grid-template-columns:1fr}
.inc-grid{grid-template-columns:1fr 1fr}
}
@media(max-width:640px){
.inc-grid{grid-template-columns:1fr}
.seats-live{flex-wrap:wrap;padding:12px 16px}
}
.ops{border:1px solid var(--line2);background:#fff;padding:26px 26px 22px;margin-top:54px;min-width:0;overflow-wrap:anywhere}
.ops .hd{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--ink3);border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:14px;display:flex;justify-content:space-between}
.ops .hd b{color:var(--acc);font-weight:500;display:flex;align-items:center;gap:7px}
.ops .hd b::before{content:'';width:7px;height:7px;border-radius:50%;background:#1a7f4b;animation:blink 2s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
.ops .ft{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink3);border-top:1px solid var(--line);margin-top:14px;padding-top:12px;letter-spacing:.8px}
.ops .ln{font-family:'JetBrains Mono',monospace;font-size:13.5px;line-height:2.05;color:var(--ink2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ops .ln b{color:var(--ink);font-weight:500}
.ops .ln .g{color:#1a7f4b}
@media(max-width:980px){.ops{margin-top:8px}}
.eyebrow{font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:2px;text-transform:uppercase;color:var(--ink3);margin-bottom:34px;display:flex;align-items:center;gap:12px}
.eyebrow::before{content:'';width:8px;height:8px;background:var(--acc)}
h1{font-size:clamp(44px,7.2vw,92px);font-weight:500;line-height:.98;letter-spacing:-.03em;max-width:1000px}
h1 em{font-style:normal;color:var(--acc)}
.hero .sub{font-size:19px;line-height:1.55;color:var(--ink2);max-width:560px;margin:36px 0 44px}
.hero-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.hero-meta{display:flex;gap:0;margin-top:90px;border:1px solid var(--line);flex-wrap:wrap}
.hm{flex:1;min-width:220px;padding:22px 26px;border-right:1px solid var(--line)}
.hm:last-child{border-right:none}
.hm .n{font-size:30px;font-weight:500;letter-spacing:-.02em}
.hm .l{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink3);text-transform:uppercase;letter-spacing:1.2px;margin-top:6px}
@media(max-width:700px){.hm{border-right:none;border-bottom:1px solid var(--line)}.hm:last-child{border-bottom:none}}
/* ── sections ── */
section{padding:110px 0;border-bottom:1px solid var(--line)}
.sec-head{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;margin-bottom:64px;flex-wrap:wrap}
.sec-idx{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--ink3);letter-spacing:2px;text-transform:uppercase;margin-bottom:16px}
.sec-idx b{color:var(--acc);font-weight:500}
h2{font-size:clamp(30px,4.2vw,52px);font-weight:500;letter-spacing:-.025em;line-height:1.02}
.sec-sub{font-size:16.5px;color:var(--ink2);line-height:1.6;max-width:460px}
/* ── agents: spec rows ── */
.agent-row{display:grid;grid-template-columns:70px 220px 1fr 300px;gap:32px;padding:34px 0;border-top:1px solid var(--line);align-items:baseline}
.agent-row:last-child{border-bottom:1px solid var(--line)}
.agent-idx{font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--ink3)}
.agent-name{font-size:26px;font-weight:500;letter-spacing:-.02em}
.agent-name span{display:block;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--acc);text-transform:uppercase;letter-spacing:1.5px;margin-top:6px;font-weight:500}
.agent-desc{font-size:15px;line-height:1.65;color:var(--ink2)}
.agent-tags{display:flex;gap:8px;flex-wrap:wrap}
.agent-tags span{font-family:'JetBrains Mono',monospace;font-size:11px;padding:5px 10px;border:1px solid var(--line2);color:var(--ink2)}
@media(max-width:900px){.agent-row{grid-template-columns:40px 1fr;gap:16px}.agent-desc,.agent-tags{grid-column:2}}
/* ── system grid ── */
.sys-grid{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line);border-left:1px solid var(--line)}
@media(max-width:900px){.sys-grid{grid-template-columns:1fr}}
.sys{padding:34px 30px 40px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);transition:background .2s}
.sys:hover{background:var(--paper2)}
.sys .n{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--acc);letter-spacing:1.5px;margin-bottom:22px}
.sys h3{font-size:19px;font-weight:500;letter-spacing:-.01em;margin-bottom:10px}
.sys p{font-size:14px;color:var(--ink2);line-height:1.6}
/* ── loop ── */
.loop{display:grid;grid-template-columns:repeat(5,1fr);border:1px solid var(--line)}
@media(max-width:900px){.loop{grid-template-columns:1fr}}
.lstep{padding:28px 24px 34px;border-right:1px solid var(--line)}
.lstep:last-child{border-right:none}
@media(max-width:900px){.lstep{border-right:none;border-bottom:1px solid var(--line)}.lstep:last-child{border-bottom:none}}
.lstep .n{font-family:'JetBrains Mono',monospace;font-size:24px;color:var(--ink);margin-bottom:16px}
.lstep h3{font-size:15.5px;font-weight:600;margin-bottom:8px}
.lstep p{font-size:13px;color:var(--ink2);line-height:1.55}
/* ── compare ── */
.cmp-grid{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--line)}
@media(max-width:900px){.cmp-grid{grid-template-columns:1fr}}
.cmp{padding:44px 40px}
.cmp.a{border-right:1px solid var(--line);color:var(--ink2)}
@media(max-width:900px){.cmp.a{border-right:none;border-bottom:1px solid var(--line)}}
.cmp.b{background:var(--ink);color:var(--paper)}
.cmp .lbl{font-family:'JetBrains Mono',monospace;font-size:11px;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:26px}
.cmp.a .lbl{color:var(--ink3)}.cmp.b .lbl{color:#b9b3ff}
.cmp ul{list-style:none}
.cmp li{font-size:15px;padding:12px 0;line-height:1.5;display:grid;grid-template-columns:22px 1fr;gap:10px}
.cmp.a li{border-top:1px solid var(--line)}
.cmp.b li{border-top:1px solid #33322e}
.cmp li i{font-style:normal;font-family:'JetBrains Mono',monospace;font-size:13px;padding-top:2px}
.cmp.a li i{color:var(--ink3)}.cmp.b li i{color:var(--acc);filter:brightness(1.6)}
/* ── pricing ── */
.price-panel{display:grid;grid-template-columns:1.1fr 1fr;border:1px solid var(--ink);margin-top:8px}
@media(max-width:900px){.price-panel{grid-template-columns:1fr}}
.price-left{padding:52px 48px;border-right:1px solid var(--ink)}
@media(max-width:900px){.price-left{border-right:none;border-bottom:1px solid var(--ink)}}
.price-tag{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--acc);margin-bottom:26px}
.price-big{font-size:clamp(56px,7vw,88px);font-weight:500;letter-spacing:-.03em;line-height:1}
.price-big span{font-size:20px;color:var(--ink3);letter-spacing:0;font-weight:400}
.price-note{font-size:14.5px;color:var(--ink2);margin:18px 0 36px;line-height:1.6;max-width:380px}
.price-right{padding:52px 48px;background:var(--paper2)}
.price-right ul{list-style:none}
.price-right li{font-size:14.5px;color:var(--ink);padding:11px 0;border-top:1px solid var(--line2);display:grid;grid-template-columns:20px 1fr;gap:10px;line-height:1.5}
.price-right li:first-child{border-top:none}
.price-right li i{font-style:normal;color:var(--acc);font-family:'JetBrains Mono',monospace;font-size:12px;padding-top:3px}
.secure{font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--ink3);margin-top:26px;letter-spacing:.4px}
.secure a{color:var(--acc);text-decoration:none}
/* ── cta ── */
.cta-sec{background:var(--ink);color:var(--paper);border-bottom:none}
.cta-sec h2{color:var(--paper);max-width:800px}
.cta-sec .sec-sub{color:#a5a29a;margin:22px 0 40px}
.cta-sec .btn{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.cta-sec .btn:hover{background:var(--acc);color:#fff;border-color:var(--acc)}
.cta-sec .btn-o{background:transparent;color:var(--paper);border-color:#45443f;margin-left:12px}
.cta-sec .btn-o:hover{border-color:var(--paper)}
footer{padding:34px 0;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--ink3)}
footer .wrap{display:flex;justify-content:space-between;flex-wrap:wrap;gap:14px}
footer a{color:var(--ink2);text-decoration:none}
footer a:hover{color:var(--acc)}
/* ── modal (light) ── */
#planModal{display:none;position:fixed;inset:0;background:rgba(26,26,24,.55);z-index:999;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
#planModal .mbox{background:var(--paper);border:1px solid var(--ink);padding:44px;max-width:480px;width:92%}
.m-input{width:100%;padding:14px 16px;border:1px solid var(--line2);background:#fff;color:var(--ink);font-size:15px;font-family:inherit;margin-bottom:12px;box-sizing:border-box}
.m-input:focus{outline:none;border-color:var(--acc)}
.m-note{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink3);text-align:center;margin-top:14px;letter-spacing:.5px}
.reveal{opacity:0;transform:translateY(18px);transition:opacity .6s,transform .6s}
.reveal.vis{opacity:1;transform:none}
/* improve-ui аудит (design-plans/simbiot-cilby-v2-audit.md): P1 focus, P2 motion */
.btn:focus-visible, a:focus-visible, button:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:2px}
@media(prefers-reduced-motion:reduce){
.reveal{opacity:1;transform:none;transition:none}
.seats-live .dot{animation:none}
*{scroll-behavior:auto}
}
/* ═══ SHOWCASE (live product) — фикс: стилей не было ═══ */
.moc-grid{display:grid;grid-template-columns:1.4fr .9fr;gap:26px;align-items:start;border:1px solid var(--line);}
@media(max-width:980px){.moc-grid{grid-template-columns:1fr}}
.shot{background:#fff;border-bottom:1px solid var(--line);min-width:0}
.shot img{display:block;width:100%;height:auto;border:none}
.shot .bar,.chatwin .hd{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--line);background:var(--paper2);font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink3)}
.shot .bar i{width:9px;height:9px;border-radius:50%;background:var(--line2)}
.shot .bar i:first-child{background:#e35b4f}.shot .bar i:nth-child(2){background:#e2b13c}.shot .bar i:nth-child(3){background:#3d8f5f}
.shot .bar span{margin-left:auto}
.chatwin{background:#fff;border:none;min-width:0}
.chatwin .hd{border-bottom:1px solid var(--line)}
.chatwin .hd .dot{width:8px;height:8px;border-radius:50%;background:#1a7f4b;animation:pulse 2s infinite}
.chatbody{padding:18px 16px;display:flex;flex-direction:column;gap:12px;max-height:460px}
.msgl .b{position:relative;background:var(--paper2);border:1px solid var(--line);padding:10px 12px 10px 14px;font-size:13.5px;line-height:1.5;color:var(--ink)}
.msgl .b .t{display:block;font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--ink3);margin-top:6px;letter-spacing:.05em}
/* dashboard css mockup (replaces raw screenshot) */
.dbmock{padding:8px 0;background:#fff}
.db-row{display:grid;grid-template-columns:1fr 44px 110px;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--line);font-size:13.5px;color:var(--ink)}
.db-h{font-family:'JetBrains Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink3)}
.db-row .s{font-family:'JetBrains Mono',monospace;font-size:11px;text-align:center;border:1px solid var(--line2);border-radius:999px;padding:3px 0;color:var(--ink2)}
.db-row .s.L{border-color:var(--acc);color:var(--acc)}
.db-row .st{font-family:'JetBrains Mono',monospace;font-style:normal;font-size:10.5px;letter-spacing:.05em}
.st.run{color:#8a6d1a}.st.done{color:#3d8f5f}.st.esc{color:#e35b4f}
.db-foot{display:flex;align-items:center;gap:8px;padding:12px 16px;font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--ink3);letter-spacing:.05em}
.db-foot .dot{width:7px;height:7px;border-radius:50%;background:#1a7f4b;animation:pulse 2s infinite}
.cap{padding:14px 18px;font-size:13px;line-height:1.55;color:var(--ink3);border-top:1px solid var(--line)}
</style>
</head>
<body>
<nav>
<div class="wrap">
<div class="logo"><span class="logo-mark"></span>Simbiot</div>
<div class="links">
<a href="#team">Team</a>
<a href="#system">System</a>
<a href="#pricing">Pricing</a>
<a href="https://cil.by/cabinet">Sign in</a>
<a href="#pricing" class="btn btn-sm">Get started</a>
</div>
</div>
</nav>
<!-- ═══ HERO ═══ -->
<header class="hero">
<div class="wrap">
<div>
<div class="eyebrow">Simbiot — Studio OS</div>
<h1>Hire nobody.<br>Ship <em>everything.</em></h1>
<p class="sub">Simbiot gives your studio a dedicated AI team — engineers, video producers, content makers — and the operating system that runs them: projects, treasury, decisions, meetings. White-label, under your brand.</p>
<div class="hero-cta">
<a href="#pricing" class="btn">Start your studio — $999/mo</a>
<a href="https://demo.cil.by/api/auth/demo-login" class="btn btn-o" style="display:none">Live demo</a>
</div>
</div>
<div class="ops">
<div class="hd"><span>STUDIO OPS</span><b>LIVE</b></div>
<div id="opsLines">
<div class="ln"><b>cody</b>&nbsp;&nbsp;&nbsp;▸ deployed neurocrm <span class="g">@2f3a1c</span></div>
<div class="ln"><b>reelsup</b>&nbsp;▸ rendered 6 clips → queue</div>
<div class="ln"><b>roy</b>&nbsp;&nbsp;&nbsp;▸ script v3 → voiceover RU</div>
<div class="ln"><b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ treasury +$1,240 transfer</div>
<div class="ln"><b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ decision #37 passed · quorum 3/3</div>
<div class="ln"><b>cody</b>&nbsp;&nbsp;&nbsp;▸ tests green — 41 passed</div>
<div class="ln"><b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ digest sent · 09:00 Minsk</div>
</div>
<div class="ft">3 AGENTS ONLINE · UPTIME 99.2%</div>
</div>
</div>
</header>
<div style="border-bottom:1px solid var(--line)">
<div class="wrap hero-meta" style="border:none">
<div class="hm"><div class="n">12</div><div class="l">AI specialists per studio</div></div>
<div class="hm"><div class="n">5–7 days</div><div class="l">idea → working product</div></div>
<div class="hm"><div class="n">$999</div><div class="l">vs $60K agency MVP</div></div>
</div>
</div>
<!-- ═══ TEAM ═══ -->
<section id="team">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>01</b> — The team</div>
<h2>Not a chatbot.<br>A staff of specialists.</h2>
</div>
<p class="sec-sub">Every agent runs on a dedicated server with its own memory, tools, email and Matrix chat. You brief them like employees — they onboard onto your idea and start shipping.</p>
</div>
<div class="agent-row reveal">
<div class="agent-idx">/01</div>
<div class="agent-name">Cody<span>Engineer</span></div>
<div class="agent-desc">Writes code, deploys services, maintains your git repositories. MVPs, landing pages, bots, APIs, integrations — from brief to production.</div>
<div class="agent-tags"><span>FULL-STACK</span><span>DEVOPS</span><span>GIT</span></div>
</div>
<div class="agent-row reveal">
<div class="agent-idx">/02</div>
<div class="agent-name">ReelsUp<span>Video producer</span></div>
<div class="agent-desc">Cuts long videos and podcasts into vertical clips for Reels and Shorts. Transcription, AI highlight detection, automated editing.</div>
<div class="agent-tags"><span>WHISPER</span><span>CLIPS</span><span>SHORTS</span></div>
</div>
<div class="agent-row reveal">
<div class="agent-idx">/03</div>
<div class="agent-name">Roy<span>Content maker</span></div>
<div class="agent-desc">Produces promotional content on a conveyor: reels with 3D characters, scripts, voiceovers, visuals. Brand-consistent by default.</div>
<div class="agent-tags"><span>3D</span><span>TTS</span><span>SCRIPTS</span></div>
</div>
</div>
</section>
<!-- ═══ SYSTEM ═══ -->
<section id="system">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>02</b> — The system</div>
<h2>Agents build.<br>The OS manages.</h2>
</div>
<p class="sec-sub">Every studio needs an operating layer. Simbiot keeps projects, money, decisions and meetings in one transparent system — under your studio's name and domain.</p>
</div>
<div class="sys-grid reveal">
<div class="sys"><div class="n">S.01</div><h3>Project registry</h3><p>A card for every venture: key decisions, ownership, agents, activity feed. The whole portfolio at a glance.</p></div>
<div class="sys"><div class="n">S.02</div><h3>Git repo per project</h3><p>Code and docs live in a dedicated repository with a web editor. Nothing gets lost in chats.</p></div>
<div class="sys"><div class="n">S.03</div><h3>Treasury & wallets</h3><p>Revenue share flows into the treasury automatically. Every payout is transparent and auditable.</p></div>
<div class="sys"><div class="n">S.04</div><h3>Decisions & voting</h3><p>Allocate resources between projects by vote. Cycles, quorum, full decision history — governance built in.</p></div>
<div class="sys"><div class="n">S.05</div><h3>Meetings with AI minutes</h3><p>Built-in video calls with recording, transcription and summaries. Every decision lands in the registry.</p></div>
<div class="sys"><div class="n">S.06</div><h3>Time & task tracking</h3><p>What every agent completed, how much work went where — a daily digest in your inbox.</p></div>
</div>
</div>
</section>
<!-- ═══ LOOP ═══ -->
<section id="loop">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>03</b> — How it works</div>
<h2>The studio flywheel.</h2>
</div>
</div>
<div class="loop reveal">
<div class="lstep"><div class="n">1</div><h3>Hypothesis</h3><p>A partner registers a project: 7 decisions, studio share.</p></div>
<div class="lstep"><div class="n">2</div><h3>Voting</h3><p>Partners vote — the project gets priority and resources.</p></div>
<div class="lstep"><div class="n">3</div><h3>Agents build</h3><p>Engineer codes, producer cuts video, content ships.</p></div>
<div class="lstep"><div class="n">4</div><h3>Market test</h3><p>An MVP validates the hypothesis in days, not months.</p></div>
<div class="lstep"><div class="n">5</div><h3>Revenue → treasury</h3><p>The studio share flows back and funds the next projects.</p></div>
</div>
</div>
</section>
<!-- ═══ COMPARE ═══ -->
<section id="math">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>04</b> — The math</div>
<h2>Classic studio vs Simbiot.</h2>
</div>
</div>
<div class="cmp-grid reveal">
<div class="cmp a">
<div class="lbl">Without a system</div>
<ul>
<li><i>—</i>You're the CEO, dev team, marketer and accountant</li>
<li><i>—</i>2–3 months and $30–60K for an agency MVP</li>
<li><i>—</i>Operations scattered across spreadsheets and chats</li>
<li><i>—</i>Freelancers leave — and take the knowledge with them</li>
<li><i>—</i>Burnout before product-market fit</li>
</ul>
</div>
<div class="cmp b">
<div class="lbl">On Simbiot</div>
<ul>
<li><i>+</i>One AI team builds while you steer</li>
<li><i>+</i>Days from idea to working product</li>
<li><i>+</i>Projects, treasury, decisions, meetings — one OS</li>
<li><i>+</i>Code and knowledge stay in your repositories</li>
<li><i>+</i>Every task and decision is documented</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ═══ PRODUCT SHOWCASE (t-261002151630): честные визуалы живого продукта ═══ -->
<section id="showcase">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>04·б</b> — The product</div>
<h2>This is the live product.<br>Not a mockup.</h2>
</div>
<p class="sec-sub">Real screens from our own production harness — the same one you get. Dashboard with the task ladder, chat with your agent right in the UI.</p>
</div>
<div class="moc-grid reveal">
<div class="shot">
<div class="bar"><i></i><i></i><i></i><span>simbiot dashboard — task ladder</span></div>
<div class="dbmock">
<div class="db-row db-h"><span>task</span><span>size</span><span>status</span></div>
<div class="db-row"><span>Redesign pricing section copy</span><b class="s L">L</b><em class="st run">running</em></div>
<div class="db-row"><span>Weekly digest to stakeholders</span><b class="s M">M</b><em class="st run">running</em></div>
<div class="db-row"><span>Fix og-image on landing</span><b class="s S">S</b><em class="st done">done</em></div>
<div class="db-row"><span>Escalation: router burn &gt; budget</span><b class="s M">M</b><em class="st esc">escalated</em></div>
<div class="db-row"><span>Onboarding call notes → decisions</span><b class="s S">S</b><em class="st done">done</em></div>
<div class="db-foot"><span class="dot"></span>3 agents live · hour digest 23:10 · all actions signed</div>
</div>
<div class="cap">Dashboard: статус агентов, лестница задач S/M/L/XL, дайджесты часов — как в реальном продукте.</div>
</div>
<div class="chatwin">
<div class="hd"><span class="dot"></span>chat with simbiot · in-ui</div>
<div class="chatbody">
<div class="msgl"><div class="b">Тест из дашборда: ответь коротко, одним пре<span class="t">owner · 10 minutes ago</span></div></div>
<div class="msgl"><div class="b">Вижу тебя чётко, канал чат в дашборде работает — ответ ушёл в ленту ✅<span class="t">simbiot · now</span></div></div>
<div class="msgl"><div class="b">Прими задачу: редизайн секции pricing на лендинге<span class="t">owner</span></div></div>
<div class="msgl"><div class="b">Принято. Задача в лестнице [L]: аудит → план фиксов → применение → скрин до/после в чат.<span class="t">simbiot</span></div></div>
</div>
<div class="cap">Чат с агентом прямо в UI дашборда (t-261001100740): тот же канал, что Telegram — один разговор.</div>
</div>
</div>
</div>
</section>
<!-- ═══ INCLUDED ═══ -->
<section id="included">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>05</b> — What's included</div>
<h2>Full stack.<br>Nothing extra to buy.</h2>
</div>
<p class="sec-sub">Everything a product studio needs on day one — from payment rails to the agents themselves. No marketplace, no add-ons, no metered surprises.</p>
</div>
<div class="inc-grid reveal">
<div class="inc"><div class="n">01</div><h3>Stripe payments for your products</h3><p>Card payments for whatever your agents build — subscriptions, one-offs, invoices. Live Stripe account wired on day one: a working checkout, not a promise.</p><div class="tag">STRIPE LIVE</div></div>
<div class="inc"><div class="n">02</div><h3>AI inference included</h3><p>All model inference runs on our gateway — no API keys, no vendor bills to manage, no surprise invoices. Compute is part of the flat rate.</p><div class="tag">FLAT RATE</div></div>
<div class="inc"><div class="n">03</div><h3>White-label portal</h3><p>Your own subdomain, your studio's name and brand at the front. Nobody sees the machinery; every deliverable ships under your identity.</p><div class="tag">YOUR BRAND</div></div>
<div class="inc"><div class="n">04</div><h3>Agents: engineer, producer, content</h3><p>Staff of specialists that actually produces: an engineer for code, a producer for video/assembly, a content maker for marketing — plus more on demand.</p><div class="tag">12 SLOTS</div></div>
<div class="inc"><div class="n">05</div><h3>The Studio OS</h3><p>Projects, treasury with USDA wallets, decisions and voting, meetings with AI minutes, task tracking with daily digests — one transparent system.</p><div class="tag">PROJECTS · TREASURY · MEETINGS</div></div>
<div class="inc"><div class="n">06</div><h3>The agent harness</h3><p>The rhythm engine behind every agent: reflex, minute, hour, day — escalation, memory, semantic knowledge base. Coop-tested daily on our own runtime.</p><div class="tag">4-CADENCE RUNTIME</div></div>
</div>
<div class="seats-live reveal" id="seatsLive">
<span class="dot"></span>
<span>Founding cohort: <b id="seatsTaken">–</b> of 100 studios on board</span>
<a href="#pricing">See the plan →</a>
</div>
</div>
</section>
<!-- ═══ PRICING ═══ -->
<section id="pricing">
<div class="wrap">
<div class="sec-head reveal">
<div>
<div class="sec-idx"><b>06</b> — Pricing</div>
<h2>One plan.<br>Everything included.</h2>
</div>
<p class="sec-sub">Flat rate for product studios. Your whole team of AI agents and the OS to run them — no per-seat math.</p>
</div>
<div class="seats-live reveal" id="seatsLivePricing">
<span class="dot"></span>
<span><b id="seatsLeft">–</b> of 100 founding seats left · $99.9/mo personal · first come, first served</span>
</div>
<div class="price-panel reveal" style="margin-bottom:24px">
<div class="price-left">
<div class="price-tag">Personal</div>
<div class="price-big">$99.9<span> / month</span></div>
<p class="price-note">Your personal AI agent on its own server, with a private Matrix chat. Everything included — inference, hosting, updates. Zero lock-in: export everything with one click, anytime.</p>
<button class="btn" onclick="choosePlan('starter')" id="btnPersonal" style="width:100%">Get my agent</button>
<div class="secure">SECURE CHECKOUT BY STRIPE · CANCEL ANYTIME</div>
</div>
<div class="price-right">
<ul>
<li><i>+</i><span><strong>1 dedicated AI agent</strong> on its own server</span></li>
<li><i>+</i><span><strong>Private Matrix chat</strong> — web + Element X mobile</span></li>
<li><i>+</i><span>Inference, hosting and updates included</span></li>
<li><i>+</i><span><strong>One-click export</strong> — take everything with you, no lock-in</span></li>
<li><i>+</i><span>Ready in ~10 minutes after payment</span></li>
</ul>
</div>
</div>
<div class="price-panel reveal">
<div class="price-left">
<div class="price-tag">Studio</div>
<div class="price-big">$999<span> / month</span></div>
<p class="price-note">Your studio portal on your own subdomain, under your own name. Deployed the same day you pay.</p>
<button class="btn" onclick="choosePlan('pro')" id="btnPro" style="width:100%">Start your studio</button>
<div class="secure">SECURE CHECKOUT BY STRIPE · SELF-HOSTED / CUSTOM? <a href="mailto:portal@cil.by">TALK TO US</a></div>
</div>
<div class="price-right">
<ul>
<li><i>+</i><span><strong>12 AI agents</strong> — engineers, producers, content</span></li>
<li><i>+</i><span>White-label portal on your subdomain — your studio's brand</span></li>
<li><i>+</i><span>Projects, treasury, decisions, meetings — one system</span></li>
<li><i>+</i><span>Invite team members — free</span></li>
<li><i>+</i><span>Email + Matrix included; Telegram and more channels optional</span></li>
<li><i>+</i><span>Priority support & deployment</span></li>
<li><i>+</i><span>Cancel anytime</span></li>
</ul>
</div>
</div>
</div>
</section>
<!-- ═══ Plan modal ═══ -->
<div id="planModal">
<div class="mbox">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:22px">
<h3 id="modalTitle" style="font-size:22px;font-weight:500;letter-spacing:-.02em">Get Started</h3>
<button onclick="closeModal()" style="background:none;border:none;color:var(--ink3);font-size:26px;cursor:pointer;line-height:1">×</button>
</div>
<div id="modalBody"></div>
</div>
</div>
<!-- ═══ CTA ═══ -->
<section class="cta-sec">
<div class="wrap">
<div class="sec-idx" style="color:#8f8c82"><b style="color:#b9b3ff">07</b> — Go</div>
<h2>Build a new kind of studio.</h2>
<p class="sec-sub">Already a member? Your billing cabinet shows subscription status and payment history.</p>
<a href="#pricing" class="btn">Start — $999/mo</a>
<a href="https://cil.by/cabinet" class="btn btn-o">Open my cabinet</a>
</div>
</section>
<footer>
<div class="wrap">
<span>© 2026 SIMBIOT · COOPERATIVE 42</span>
<span><a href="https://cil.by">CIL.BY</a> · <a href="https://demo.cil.by">DEMO</a> · <a href="https://cil.by/cabinet">CABINET</a></span>
</div>
</footer>
<script>
const io = new IntersectionObserver(es => es.forEach(e => e.isIntersecting && e.target.classList.add('vis')), {threshold:.1});
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
// живой счётчик мест предпродажи (t-261002151244): /api/presale/seats на cil.by
// (CORS allow-origin: simbiot.cil.by). taken → «studios on board», left = 100-taken.
// Фолбэк при недоступности API — блоки остаются с «–», не мигают и не врут.
async function seatsRefresh(){
try{
const r = await fetch('https://cil.by/api/presale/seats', {cache:'no-store'});
if(!r.ok) return;
const d = await r.json();
const taken = Math.max(0, Math.min(100, parseInt(d.taken,10) || 0));
const left = Math.max(0, (parseInt(d.total,10) || 100) - taken);
const st = document.getElementById('seatsTaken');
if(st) st.textContent = taken;
const sl = document.getElementById('seatsLeft');
if(sl) sl.textContent = left;
}catch(e){ /* тихо: сети нет — не драматизируем */ }
}
seatsRefresh();
setInterval(seatsRefresh, 60000);
// live ops log — новые строки каждые 3.2s
const OPS_FEED = [
'<b>reelsup</b>&nbsp;▸ clip 04/06 rendering…',
'<b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ payout approved · vlad',
'<b>cody</b>&nbsp;&nbsp;&nbsp;▸ opened PR #112 → main',
'<b>roy</b>&nbsp;&nbsp;&nbsp;▸ 3D rig loaded · scene 2',
'<b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ vote closed · cycle #38',
'<b>cody</b>&nbsp;&nbsp;&nbsp;▸ deployed clipforge <span class="g">@9be77d</span>',
'<b>reelsup</b>&nbsp;▸ transcript ready · 42 min',
'<b>os</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;▸ treasury +$860 transfer',
'<b>roy</b>&nbsp;&nbsp;&nbsp;▸ exported reel_07.mp4',
'<b>cody</b>&nbsp;&nbsp;&nbsp;▸ migrated db · 3 tables',
];
let opsIdx = 0;
setInterval(() => {
const box = document.getElementById('opsLines');
if (!box) return;
const div = document.createElement('div');
div.className = 'ln';
div.innerHTML = OPS_FEED[opsIdx++ % OPS_FEED.length];
div.style.opacity = '0';
box.appendChild(div);
requestAnimationFrame(() => { div.style.transition = 'opacity .5s'; div.style.opacity = '1'; });
while (box.children.length > 7) box.removeChild(box.firstChild);
}, 3200);
async function subscribeNow(plan) {
plan = plan || 'pro';
const emailEl = document.getElementById('modalEmail');
const btn = document.getElementById('modalBtn');
const email = emailEl.value.trim();
const company = (document.getElementById('modalStudio')||{value:''}).value.trim();
const agentName = (document.getElementById('modalAgent')||{value:''}).value.trim();
if (!email || !email.includes('@')) { alert('Please enter your email'); return; }
btn.disabled = true;
btn.textContent = 'Starting...';
try {
const r = await fetch('https://cil.by/api/onboarding/start', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, plan})
});
const d = await r.json();
if (d.pending_id) {
await fetch('https://cil.by/api/onboarding/save', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify(Object.assign({pending_id: d.pending_id, plan, company_name: company}, agentName ? {agent_name: agentName} : {}))
});
const cr = await fetch('https://cil.by/api/onboarding/checkout', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({pending_id: d.pending_id, plan})
});
const cd = await cr.json();
if (cd.url) { window.location.href = cd.url; return; }
alert(cd.error || 'Checkout error'); btn.disabled = false; btn.textContent = 'Continue'; return;
}
else { alert(d.error || 'Error'); btn.disabled = false; btn.textContent = 'Continue'; }
} catch(e) { alert('Connection error: ' + e.message); btn.disabled = false; btn.textContent = 'Continue'; }
}
function choosePlan(plan) {
const modal = document.getElementById('planModal');
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
if (plan === 'enterprise') {
title.textContent = 'Enterprise — Request a Quote';
body.innerHTML = `
<p style="color:var(--ink2);margin-bottom:20px;font-size:14.5px;line-height:1.6">Tell us about your needs. We'll contact you within 24 hours.</p>
<input type="email" id="entEmail" placeholder="work@email.com" class="m-input">
<input type="text" id="entCompany" placeholder="Company name" class="m-input">
<input type="text" id="entAgents" placeholder="How many agents?" class="m-input">
<select id="entHosting" class="m-input">
<option value="self-hosted">Self-hosted (on-premise)</option>
<option value="cloud">Cloud (we host)</option>
<option value="hybrid">Hybrid</option>
</select>
<textarea id="entUseCase" placeholder="What do you want to build?" rows="3" class="m-input" style="resize:vertical"></textarea>
<button class="btn" onclick="submitEnterprise()" style="width:100%" id="entBtn">Submit Request</button>
<div id="entMsg" style="margin-top:12px"></div>
`;
} else if (plan === 'starter') {
title.textContent = 'Personal Agent — $99.9/mo';
body.innerHTML = `
<p style="color:var(--ink2);margin-bottom:20px;font-size:14.5px;line-height:1.6">Email and your name — then straight to payment. Your agent and private Matrix chat are ready ~10 minutes after.</p>
<input type="email" id="modalEmail" placeholder="your@email.com" class="m-input">
<input type="text" id="modalAgent" placeholder="Name your first agent (e.g. Anna)" class="m-input">
<input type="text" id="modalStudio" placeholder="How should we call you? (for your agent)" class="m-input" style="margin-bottom:18px">
<button class="btn" onclick="subscribeNow('starter')" style="width:100%" id="modalBtn">Continue</button>
<div class="m-note">SECURE CHECKOUT BY STRIPE · CANCEL ANYTIME</div>
`;
} else {
title.textContent = 'Studio — $999/mo';
body.innerHTML = `
<p style="color:var(--ink2);margin-bottom:20px;font-size:14.5px;line-height:1.6">Email and studio name — then straight to payment. Your portal is created right after.</p>
<input type="email" id="modalEmail" placeholder="your@email.com" class="m-input">
<input type="text" id="modalAgent" placeholder="Name your first agent (e.g. Anna)" class="m-input">
<input type="text" id="modalStudio" placeholder="Your studio name (white-label portal)" class="m-input" style="margin-bottom:18px">
<button class="btn" onclick="subscribeNow('${plan}')" style="width:100%" id="modalBtn">Continue</button>
<div class="m-note">SECURE CHECKOUT BY STRIPE</div>
`;
}
modal.style.display = 'flex';
}
function closeModal() {
document.getElementById('planModal').style.display = 'none';
}
async function submitEnterprise() {
const email = document.getElementById('entEmail').value.trim();
const btn = document.getElementById('entBtn');
if (!email || !email.includes('@')) { alert('Please enter your email'); return; }
btn.disabled = true; btn.textContent = 'Submitting...';
try {
const r = await fetch('https://cil.by/api/enterprise/request', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email,
company: document.getElementById('entCompany').value.trim(),
agents_needed: document.getElementById('entAgents').value.trim(),
hosting: document.getElementById('entHosting').value,
use_case: document.getElementById('entUseCase').value.trim()
})
});
const d = await r.json();
if (d.ok) {
document.getElementById('modalBody').innerHTML = `
<div style="text-align:center;padding:20px 0">
<h3 style="font-size:20px;margin-bottom:10px;font-weight:500">Request received.</h3>
<p style="color:var(--ink2);margin-bottom:26px;font-size:14.5px;line-height:1.6">We'll contact you at ${email} within 24 hours.</p>
<button class="btn" onclick="closeModal()" style="width:100%">Close</button>
</div>
`;
} else { alert(d.error || 'Error'); btn.disabled = false; btn.textContent = 'Submit Request'; }
} catch(e) { alert('Connection error: ' + e.message); btn.disabled = false; btn.textContent = 'Submit Request'; }
}
</script>
</body>
</html>

View file

@ -0,0 +1,87 @@
# Метрика времени: сколько Alex проводит с харнесом
# t-261012135328-424725 · 2026-10-12 · уровень: hour
Одобрено: m-516174075/76 («да делай»). Под будущее отдельное приложение-трекер (m-516174077) — секция «Формат данных».
## Методика
- Источник: ES-стор харнеса (aggregate=inbox, MessageReceived, user_id=7569223131, только Alex).
- Период: 2026-09-29 → 2026-10-12 (14 дней, полная история чата с харнесом).
- Сессия = цепочка сообщений с разрывами ≤30 мин. «Активное время» = сумма межсообщенных
интервалов с кэпом 5 мин (иначе ожидание ответа харнеса считалось бы «работой» Alex).
- «Охват» (span) = от первого до последнего сообщения сессии — сколько времени окно чата
было «живым». Реальное присутствие — между active и span.
- Ограничения: TG-сообщение ≠ момент входа на портал; портал-логинов в ES-сторе нет
(channel_portal инжестит 0). Портал-активность (звонки) — отдельно, ниже.
## Ключевые цифры (14 дней)
| Метрика | Значение |
|---|---|
| Сообщений Alex | 930 (868 текст, 34 голосовых, 7 документов, 1 видео) |
| Сессий (gap>30м) | 86 (в среднем ~6/день в активные дни) |
| Активное время | 22.4 ч (~96 мин/день) |
| Охват (span) | 40.3 ч |
| Медианная сессия | 5.5 мин активных; p90 = 52 мин |
| Средняя сессия | 14.2 сообщения |
Динамика по дням (активные мин): 29.09: 140 → 30.09: 131 → 01.10: 119 → 02.10: 206 →
03.10: 124 → 04.10: 161 → 05.10: 113 → 06.10: 88 → 07.10: 0 (выходной) → 08.10: 155 →
09.10: 44 → 10.10: 53 → 11.10: 1 → 12.10: 11 (день не закончен).
Неделя 1 (29.09–05.10): ~142 мин/день → Неделя 2 (06.10–12.10): ~51 мин/день — падение
в ~2.8 раза. Гипотезы: (а) новизна эффекта прошла, (б) харнес стал стабильнее и требует
меньше ручного управления, (в) Alex занят другими проектами. Стоит отслеживать; для AUM
это сигнал «харнес требует меньше внимания основателя» = положительный или тревожный
в зависимости от того, растут ли параллельно выходы харнеса.
Распределение по часам (UTC): пик 08:00–17:00 (утро Мinsk +10..+13 UTC+3), второй пик
20:00–22:00 UTC (поздний вечер). Ночью 02:00–05:00 UTC почти пусто (кроме пары ночных сессий
06.10 — день самодоработки).
По дням недели: максимум в Чт (274 мин) и Пт (250), минимум Пн (124) и Ср (131).
Топ-сессии (глубокая работа): 01.10 07:09–09:18 (62 msg, 103 мин) — запуск/настройка;
02.10 13:01–15:22 (78 msg, 86 мин); 29.09 20:12–22:00 (42 msg, 77 мин).
Голосовые: 34 шт (~4% сообщений) — штучные, но обычно тяжёлые (директивы, задачи).
Документы: 7 (PDF-договоры 05.10).
## Портал и звонки (второй канал присутствия)
В ES-сторе харнеса портал-логинов нет (portal channel: ingested=0). Звонки с харнесом
(booking) — не источник «времени с харнесом» в смысле присутствия Alex, это отдельные
мероприятия. Для полной картины трекеру нужны оба канала: TG + портал-сессии.
## Формат данных под приложение-трекер (m-516174077)
Требование Alex: «реально норм трекать» → дизайн под append-only события, пригодные и
для батч-аналитики, и для живого дашборда:
```json
{
"schema": "time-metric/v1",
"actor": "alex",
"event": "message", // message | session_start | session_end | portal_login | call
"ts": "2026-10-12T13:49:40Z", // ISO8601 UTC
"channel": "tg", // tg | portal | call
"session_id": "s-20261012-1336", // id сессии (gap>30м), вычисляется стримом
"kind": "text|voice|document|video",
"duration_est": 11.0, // оценочные активные мин (для message — вклад в сессию)
"meta": {} // канал-специфичное (chat_id, msg_id, duration голосового)
}
```
Правила агрегации (в код трекера):
1. session_gap = 30 мин, cap_per_interval = 5 мин — константы в конфиге, не в коде.
2. «Активное время» = Σ min(gap, cap); «охват» = last−first. Отчёт всегда оба числа.
3. Дни без сообщений ≠ ноль времени; но полный день тишины = 0 активности (07.10 — единственный).
4. Экспорт: дневной CSV (date, sessions, msgs, active_min, span_min) + JSON events для
приложения.
## Рекомендации
1. Метрику считать автоматом раз в день (day-тик): artifacts/time-metrics/agents-alex/day-YYYYMMDD.json — трекер потом просто читает каталог.
2. Настраиваемые пороги: session_gap и cap сейчас экспертные; после 2 недель данных — калибровка по факту (если Alex скажет «я сидел 2 часа», подогнать cap).
3. Голосовые несут непропорционально много директив — трекеру стоит отдельно считать voice_share.
4. Падение недели 2 в 2.8 раза — отслеживать как health-индикатор отношений «основатель↔харнес».
## AUM-фильтр
Метрика сама не генерит доход, но: (а) это фундамент трекер-приложения (продукт),
(б) health-индикатор удержания основателя — прямой предиктор долгосрочной AUM. ≥ +1% к AUM
на горизонте год+ — да, через продукт-трекер и раннее обнаружение оттока внимания.

View file

@ -0,0 +1,30 @@
# Дайджест hour 2026-10-11 ~23:00 UTC (тик hour-497711)
## Выжимка часа (22:00→23:00 UTC)
- **min-уровень: весь час тихий** — все 60 тиков `gate:quiet`, LLM не запускался, ошибок нет.
(Серия тихих минут теперь ×60+; вчера последний инцидент min — 502 в 13:52, давно закрыт e-261010135206.)
- **Задач за окно: 1 выполнена** — t-261011170418 (S) «англ. версия лендинга cil.by /en в стиле
редизайна (IBM Plex)» — done 17:18 UTC, артефакты: `/root/coop/static/landing_en.html`
(+ бэкап `.bak-261011`). Заметка: потребовалось 3 TaskPromoted «бюджет исчерпан» (guard возвращал
в ready), с 4-го захода закрыта.
- **Эскалации: открытых 0.** Новых за час не поднималось.
- **Входящие TG: чисто.** 99/100 replied; единственный «без ответа» m-516174015 — пустое text-сообщение
из группового чата от 2026-10-10 09:22 (артефакт-пустышка, не голос, не документ) — повода отвечать нет.
- **Нагрузка:** транзиентный всплеск load1 до ~17 на границе 23:00 (high_load warn на min-тике),
к 23:02 спала до 5.5. Совпадает со стартом часового воркера — деградации нет.
- **AUM:** 26.7 у.е. (стабильно с 10-04); burn 9router: 24ч=11.73 у.е. (дешёвый день),
7д=281.43 у.е. Замер дня уже снят (00:00: 269.7 → 281.43 к вечеру).
- **Сервисы живы:** scheduler (uptime с Oct 10), tg_inbox-поллер (с Oct 08), dashboard (с Oct 08);
часовые/минутные тики идут по расписанию.
## Батч задач
- Очередь пуста: ready 0 / running 0 / blocked 0 (done 208, closed 3). S/M/L — брать нечего.
## Наблюдения на день (не срочно)
- Файлы часовых дайджестов в docs/digests не пишутся с 10-06: gate:quiet-заглушки пишут только
DigestWritten-событие без md-файла. Легитимно для тихих часов, но день может захотеть единый формат.
- Рецепция «/en» от Alex (m-516174071-72, replied) закрыта задачей про англ. лендинг — сверка
глазами Алекса на завтра.

View file

@ -0,0 +1,31 @@
# Дайджест hour 2026-10-13 ~15:05 UTC (тик hour-333)
## Выжимка часа (14:00→15:05 UTC)
- **Тема часа — ИИШНИЦА, анонс панельной сессии «Общее видение».** Алекс дал серию поручений
(m-516174131–150): полный док с анонсом для main-комьюнити → HTML-версия → посты для соцсетей
→ «вот это же только в html». Всё закрыто, 0 unanswered.
- **Сделано (артефакты в out/):**
- `iishnitsa-announcement-main-community.md` + `.html` — полный анонс: 45 мин, 3 круга,
миссия 42, тайминг, участники (Алекс Шкор — модератор+участник; Влад Шемардинов; Юрий Бокач —
био из лендинга cil.by), «почему интересно», CTA. Дата/площадка — заглушка «в следующих постах».
- `iishnitsa-social-post-main-community.md` + `.html` — пост в канал (свёрстан как превью
Telegram, текст копируется из блока) + короткая версия для сторис/репостов + хештеги.
- **Координация с мин-уровнем:** работал параллельно (md-док в 15:01, ответы батчами), дублей в
ответах нет — все сообщения закрыты ровно один раз; README-инструкция батча соблюдена.
- **Эскалации: открытых 0.** Новых не поднималось; старые закрыты ранее.
- **Очередь задач: пуста** (ready 0 / running 0 / blocked 0). Батч S/M/L — нечего брать.
- **AUM:** 26.7 у.е. (стабильно с 10-04); burn 9router: 24ч=14.35, 7д=199.17 у.е.
- **Заметка на день:** tg-deliver-file дважды отправил announcement.html (15:04 мой тик +
15:06 мин-воркер на «только в html») — идемпотентность по подписи/ключу не отработала, кандидат
в доработку: ключ дедупа по artifact_id+caption-hash (для day-уровня).
## Батч задач
- Пусто: очередь ready 0 / running 0 / blocked 0. S/M/L — нечего брать.
## Наблюдения на день (не срочно)
- Дедуп tg-deliver-file: две доставки одного файла с разными подписями за 2 минуты — проглотилось,
но паттерн стоит закрыть (см. выше).
- Дата/площадка ИИШНИЦЫ всё ещё не известны харнесу — при появлении обновить все 4 артефакта разом.

View file

@ -508,7 +508,7 @@ footer a:hover{color:var(--acc)}
<footer>
<div class="wrap">
<span>© 2026 SIMBIOT · COOPERATIVE 42</span>
<span><a href="https://cil.by">CIL.BY</a> · <a href="https://demo.cil.by">DEMO</a> · <a href="https://cil.by/cabinet">CABINET</a></span>
<span><a href="https://cil.by">CIL.BY</a> · <a href="https://demo.cil.by">DEMO</a> · <a href="https://cil.by/cabinet">CABINET</a> · <a href="https://simbiot.cil.by">SIMBIOT</a> · <a href="https://avatar.cil.by">AVATAR (VLAD)</a> · <a href="https://vybirai.cil.by">VYBIRAI (YURA)</a></span>
</div>
</footer>

View file

@ -91,6 +91,39 @@ def _batch_cap(level_cfg):
return MAX_TASK_BATCH.get(level_cfg.name, 3)
def _resolve_reply_target(model: dict, m: dict):
"""t-261013131228: Alex реплаит на документ/фото — Telegram не отдаёт их текст
(reply_to_text пуст), а reply_to_message_id резолвится только по MessageReceived
в этом же чате. Ищем «голову» в инбоксе по (chat_id, message_id) — включая
kind=document/photo — и возвращаем её описание (caption/файл) или None."""
rid = m.get("reply_to_message_id")
if rid is None or (m.get("reply_to_text") or "").strip():
return None
for cand in (model.get("inbox") or {}).values():
if cand.get("chat_id") == m.get("chat_id") and cand.get("message_id") == rid:
kind = cand.get("kind") or "text"
txt = (cand.get("text") or "").strip()
if kind in ("document", "photo", "audio", "video", "video_note", "voice"):
label = cand.get("file_name") or cand.get("file_path") or kind
return f"{kind} «{label}»" + (f": {txt[:300]}" if txt else " (без подписи)")
return txt[:300] if txt else None
return None
def _recent_docs(model: dict, chat_id, limit=3):
"""Последние N документов/фото этого чата (caption) — контекст для реплаев
на сообщения без текста (t-261013131228): триаж видит, о каком документе речь."""
docs = [m for m in (model.get("inbox") or {}).values()
if m.get("chat_id") == chat_id and m.get("kind") in ("document", "photo")]
docs.sort(key=lambda x: x.get("ts") or "")
out = []
for d in docs[-limit:]:
label = d.get("file_name") or d.get("mime_type") or d.get("kind")
cap = (d.get("text") or "").strip()[:200]
out.append(f"«{label}»" + (f" — {cap}" if cap else " (без подписи)"))
return out
def _est_minutes(t: dict) -> int:
"""Оценка длительности: из TaskSubmitted или дефолт по классу.
@ -336,13 +369,32 @@ def build_context(level_cfg, model: dict, batch=None):
# t-261002085926: Alex ответил (reply) на конкретное
# сообщение — покажи якорь, иначе триаж не понимает,
# к какой реплике относится сообщение
+ (f" [в ответ на «{m.get('reply_to_text','').strip()[:200]}»]"
# t-261013131228: цель реплая без текста (документ/
# фото) резолвится из инбокса (reply_to_desc) —
# триаж видит, ЧТО за документ, и не угадывает
+ (f" [в ответ на «{(m.get('reply_to_text') or m.get('reply_to_desc') or '').strip()[:200]}»]"
if m.get("reply_to_message_id") is not None
and (m.get("reply_to_text") or "").strip()
and ((m.get("reply_to_text") or "").strip()
or (m.get("reply_to_desc") or "").strip())
else (" [reply на сообщение без текста]"
if m.get("reply_to_message_id") is not None else ""))
+ ": " + m.get('text','')[:1200])
batch_ids = ",".join(ids)
# t-261013131228: если в батче есть reply на сообщение без текста —
# добавляем последние документы этого чата с caption, чтобы триаж
# видел, о каком документе речь, даже если «голова» старше капа инбокса
docs_ctx = {}
for m in inbox:
if m.get("reply_to_message_id") is not None and not (
(m.get("reply_to_text") or "").strip()
or (m.get("reply_to_desc") or "").strip()):
docs = _recent_docs(model, m.get("chat_id"))
if docs:
docs_ctx[str(m.get("chat_id"))] = (
"Последние документы этого чата (возможно, к ним относится reply): "
+ "; ".join(docs))
if docs_ctx:
parts.append("КОНТЕКСТ ДОКУМЕНТОВ ЧАТА:\n" + "\n".join(docs_ctx.values()))
# t-260930064924: батч-триаж — ВСЕ неотвеченные обрабатываются ОДНИМ тиком.
# Раньше мин-уровень отвечал по одному за тик (m-516173222..228 — 7 тиков
# подряд на один разговор). Теперь в контексте лежит ГОТОВАЯ команда

View file

@ -222,6 +222,42 @@ def save_document(msg: dict):
return abs_path
def _describe_inbox_message(m: dict) -> str:
"""Короткое описание входящего для якоря реплая (t-261013131228).
Документ/фото без текста описываем тем, что есть: kind, file_name,
начало caption (text), путь к файлу — этого хватает триажу, чтобы
понять, НА ЧТО отвечает Alex, не открывая файл.
"""
kind = m.get("kind") or "text"
text = (m.get("text") or "").strip()
if text:
return f"{kind}: {text[:200]}"
fname = m.get("file_name") or ""
fpath = m.get("file_path") or ""
if fname or fpath:
return f"{kind} (файл): {fname or (fpath or '').split('/')[-1]}"
return f"[{kind} без текста]"
def _resolve_reply_target(chat_id, reply_to_message_id: int):
"""Найти в инбоксе сообщение, на которое отвечает Alex (t-261013131228).
Telegram кладёт в reply_to_message только текстовые реплики; цель без
текста (документ/фото/стикер) остаётся «чёрным ящиком». Ищем её в
инбоксе по (chat_id, message_id) и описываем. None — цели в инбоксе
нет (старее капа 100 или из чата, который бот не видел).
"""
try:
model = es.run_projections()
except Exception:
return None
for m in (model.get("inbox") or {}).values():
if m.get("chat_id") == chat_id and m.get("message_id") == reply_to_message_id:
return _describe_inbox_message(m)
return None
def receive_message(update: dict):
"""MessageReceived в стор. Идемпотентно по update_id."""
msg = update.get("message") or update.get("edited_message") or {}
@ -264,6 +300,11 @@ def receive_message(update: dict):
data = {
"update_id": update["update_id"],
"chat_id": chat.get("id"),
# t-261013113629: тип чата (private/group/supergroup/channel) и название —
# маршрутизация ответов в чат-источник (группа vs личка) и дефолтный
# чат уведомлений (личка Alex, а не последняя активная группа)
"chat_type": chat.get("type"),
"chat_title": chat.get("title"),
# tg message_id нужен для setMessageReaction (реакция «seen», e-261002202857):
# update_id ≠ message_id (глобальный стрим vs чат), фолбэк — старый hash
"message_id": msg.get("message_id"),
@ -294,6 +335,17 @@ def receive_message(update: dict):
"reply_to_text": ((msg.get("reply_to_message") or {}).get("text") or "")[:300],
"message_thread_id": msg.get("message_thread_id"),
}
# t-261013131228: Alex реплаит на сообщение без текста (документ/фото/
# стикер) — Telegram не отдаёт его содержимое в reply_to_message, и
# триаж видит голый «[reply на сообщение без текста]» без контекста
# (кейс 13.10: реплай на концепт ИИШНИЦЫ → мин-уровень анонсировал
# не то событие). Резолвим цель по инбоксу того же чата: (chat_id,
# reply_to_message_id) → описание цели (текст/caption/файл).
rt = data.get("reply_to_message_id")
if rt is not None and not (data.get("reply_to_text") or "").strip():
target = _resolve_reply_target(chat.get("id"), rt)
if target:
data["reply_to_desc"] = target
es.append_event("inbox", f"m-{update['update_id']}", "MessageReceived", data)
return f"m-{update['update_id']}"
@ -547,7 +599,20 @@ def _default_chat_id():
and not m.get("ignored")]
if not inbox:
return None # некому: чат ещё не известен
return sorted(inbox, key=lambda m: m.get("ts") or "")[-1]["chat_id"]
# t-261013113629: уведомления/файлы — в ЛИЧКУ Alex, а не в последний
# активный чат (иначе ответ в группе делает её дефолтом, и файлы/дайджесты
# уходят всем участникам группы). Личный чат = private по chat_type;
# старые записи без chat_type — chat_id > 0 (у групп он отрицательный).
def _is_private(m):
if m.get("chat_type"):
return m.get("chat_type") == "private"
try: # старые записи без chat_type: у групп chat_id < 0
return int(m.get("chat_id")) > 0
except (TypeError, ValueError):
return False
privates = [m for m in inbox if _is_private(m)]
pool = privates or inbox
return sorted(pool, key=lambda m: m.get("ts") or "")[-1]["chat_id"]
def _post_multipart(method: str, fields: dict, file_path: str,

View file

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ИИШНИЦА · Панельная сессия «Общее видение»</title>
<style>
:root { --ink:#161a2b; --accent:#3b5bdb; --soft:#f4f5fb; --line:#e3e6f2; }
* { box-sizing:border-box; }
body { margin:0; font-family:-apple-system,'Segoe UI',Roboto,Arial,sans-serif; color:var(--ink); background:#fbfbfe; line-height:1.6; }
.wrap { max-width:760px; margin:0 auto; padding:32px 20px 56px; }
.badge { display:inline-block; background:var(--accent); color:#fff; border-radius:999px; padding:4px 14px; font-size:13px; letter-spacing:.5px; }
h1 { font-size:30px; line-height:1.25; margin:14px 0 4px; }
.sub { color:#5b6178; margin:0 0 28px; font-size:15px; }
h2 { font-size:19px; margin:32px 0 10px; padding-top:18px; border-top:1px solid var(--line); }
table { width:100%; border-collapse:collapse; font-size:14px; }
th, td { text-align:left; padding:8px 10px; border-bottom:1px solid var(--line); vertical-align:top; }
th { background:var(--soft); }
td:first-child { white-space:nowrap; color:var(--accent); font-weight:600; }
ul { padding-left:20px; }
li { margin:6px 0; }
.speaker { background:var(--soft); border:1px solid var(--line); border-radius:12px; padding:14px 16px; margin:10px 0; }
.speaker b { display:block; margin-bottom:2px; }
.speaker span { font-size:14px; color:#3c4258; }
.cta { background:var(--ink); color:#fff; border-radius:14px; padding:18px 20px; margin-top:28px; font-size:15px; }
footer { margin-top:28px; color:#8a90a8; font-size:13px; text-align:center; }
em { color:var(--accent); font-style:normal; font-weight:600; }
</style>
</head>
<body>
<div class="wrap">
<span class="badge">Анонс для main-комьюнити</span>
<h1>ИИШНИЦА · Панельная сессия «Общее видение»</h1>
<p class="sub">Панель в три круга, которая заканчивается артефактом</p>
<h2>🎯 Что будет</h2>
<p>Панельная сессия в три круга — не классическая панель с вопросами модератора, а разговор, который заканчивается артефактом: <em>одним названием общего видения будущего</em>.</p>
<p>Формат держит миссия кооператива «42»: <em>все миссии одинаково важны</em> — каждый взгляд на будущее важен, любая индустрия равна.</p>
<h2>⏱ Формат и тайминг (45 минут, трое участников)</h2>
<table>
<tr><th>Время</th><th>Круг</th><th>Что происходит</th></tr>
<tr><td>00:00–00:03</td><td>Открытие</td><td>Модератор озвучивает миссию 42 как рамку сессии</td></tr>
<tr><td>00:03–00:15</td><td>Круг 1 — «Моё будущее»</td><td>Каждый участник по 4 минуты описывает будущее своей индустрии в самом дальнем горизонте, в который может заглянуть. Без вопросов, без перебиваний</td></tr>
<tr><td>00:15–00:21</td><td>Круг 2 — «Что понравилось»</td><td>По 2 минуты: каждый подсвечивает, что понравилось у других — не спорить, а связывать: где пересечение со своим будущим</td></tr>
<tr><td>00:21–00:30</td><td>Круг 3 — «Название общего видения»</td><td>Панель соглашается назвать общее видение — одно название для всех картин будущего</td></tr>
<tr><td>00:30–00:43</td><td>Вопросы из зала</td><td>Зал — четвёртый участник. Любой вопрос важен</td></tr>
<tr><td>00:43–00:45</td><td>Закрытие</td><td>Название общего видения + миссия 42, короткое резюме</td></tr>
</table>
<h2>🎤 Участники</h2>
<div class="speaker"><b>Алекс Шкор</b><span>Модератор и участник. Председатель кооператива «Друзья Коллективного Разума», архитектор кооператива (Симбиот, Кибербанк, Domostroy). AI-инфраструктура и стратегия.</span></div>
<div class="speaker"><b>Влад Шемардинов</b><span>Участник. Член правления. Видеопродакшн и контент, ReelsUp: подкасты и видео превращаются в вирусные рилсы.</span></div>
<div class="speaker"><b>Юрий Бокач</b><span>Участник. Секретарь, монетикс, финансовая архитектура, документооборот. Залоговые системы и аналитика.</span></div>
<h2>💡 Почему это будет интересно</h2>
<ul>
<li><b>Круг 1 без перебиваний</b> — 4 минуты каждому без вопросов: редкость на панелях. Глубина вместо заготовок.</li>
<li><b>Круг 2 связывает, а не спорит</b> — «что понравилось» вместо «в чём не согласен»: панель выглядит единой, зал видит созвучие индустрий.</li>
<li><b>Круг 3 — артефакт</b> — название общего видения остаётся после сессии: в заголовках, фото и цитатах. Панель производит смысл, а не обмен мнениями.</li>
<li><b>Миссия 42 как рамка</b> — «все миссии одинаково важны» легитимирует любой ответ, спикерам безопасно говорить смело.</li>
</ul>
<h2>📍 Где и когда</h2>
<p>Минск · cil.by — точная дата и площадка будут в следующих постах, следи за анонсами в канале.</p>
<div class="cta">Приходи: приведи своё видение будущего — оно не менее важно, чем наше.</div>
<footer>Кооператив «Друзья Коллективного Разума» · Минск · cil.by</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,47 @@
# ИИШНИЦА · Панельная сессия «Общее видение»
**Анонс для main-комьюнити**
---
## 🎯 Что будет
Панельная сессия в три круга — не классическая панель с вопросами модератора, а разговор, который заканчивается артефактом: **одним названием общего видения будущего**.
Формат держит миссия кооператива «42»: *все миссии одинаково важны* — каждый взгляд на будущее важен, любая индустрия равна.
## ⏱ Формат и тайминг (45 минут, трое участников)
| Время | Круг | Что происходит |
|---|---|---|
| 00:00–00:03 | Открытие | Модератор озвучивает миссию 42 как рамку сессии |
| 00:03–00:15 | Круг 1 — «Моё будущее» | Каждый участник по 4 минуты описывает будущее своей индустрии в самом дальнем горизонте, в который может заглянуть. Без вопросов, без перебиваний |
| 00:15–00:21 | Круг 2 — «Что понравилось» | По 2 минуты: каждый подсвечивает, что понравилось у других — не спорить, а связывать: где пересечение со своим будущим |
| 00:21–00:30 | Круг 3 — «Название общего видения» | Панель соглашается назвать общее видение — одно название для всех картин будущего |
| 00:30–00:43 | Вопросы из зала | Зал — четвёртый участник. Любой вопрос важен |
| 00:43–00:45 | Закрытие | Название общего видения + миссия 42, короткое резюме |
## 🎤 Участники
**Алекс Шкор** — модератор и участник. Председатель кооператива «Друзья Коллективного Разума», архитектор кооператива (Симбиот, Кибербанк, Domostroy). AI-инфраструктура и стратегия.
**Влад Шемардинов** — участник. Член правления. Видеопродакшн и контент, ReelsUp: подкасты и видео превращаются в вирусные рилсы.
**Юрий Бокач** — участник. Секретарь, монетикс, финансовая архитектура, документооборот. Залоговые системы и аналитика.
## 💡 Почему это будет интересно
- **Круг 1 без перебиваний** — 4 минуты каждому без вопросов: редкость на панелях. Глубина вместо заготовок.
- **Круг 2 связывает, а не спорит** — «что понравилось» вместо «в чём не согласен»: панель выглядит единой, зал видит созвучие индустрий.
- **Круг 3 — артефакт** — название общего видения остаётся после сессии: в заголовках, фото и цитатах. Панель производит смысл, а не обмен мнениями.
- **Миссия 42 как рамка** — «все миссии одинаково важны» легитимирует любой ответ, спикерам безопасно говорить смело.
## 📍 Где и когда
Минск · cil.by — точная дата и площадка будут в следующих постах, следи за анонсами в канале.
Приходи: приведи своё видение будущего — оно не менее важно, чем наше.
---
*Кооператив «Друзья Коллективного Разума» · Минск · cil.by*

View file

@ -0,0 +1,98 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ИИШНИЦА · Пост для соцсетей main-комьюнити</title>
<style>
:root{--paper:#faf8f3;--ink:#15150f;--signal:#FF4F00;--dim:#6b675e;--line:#d8d3c8;--card:#f3f0e8}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--paper);color:var(--ink);font:16px/1.62 -apple-system,"Segoe UI",Roboto,Arial,sans-serif;-webkit-font-smoothing:antialiased}
.mono{font-family:ui-monospace,"JetBrains Mono",Menlo,Consolas,monospace}
.wrap{max-width:680px;margin:0 auto;padding:40px 20px 80px}
.kicker{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.22em;color:var(--signal);font-weight:700;text-transform:uppercase;margin-bottom:18px}
h1{font-size:28px;font-weight:800;letter-spacing:-.01em;text-transform:uppercase;margin-bottom:6px}
.sub{font-size:15px;color:var(--dim);margin-bottom:28px}
/* Telegram-пост */
.tg{border:1px solid var(--ink);background:#fff;max-width:560px;margin:0 auto 34px;border-radius:12px;overflow:hidden;box-shadow:4px 4px 0 var(--ink)}
.tg-head{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--line)}
.tg-ava{width:38px;height:38px;border-radius:50%;background:var(--signal);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:15px}
.tg-meta b{display:block;font-size:14.5px}
.tg-meta span{font-size:12.5px;color:var(--dim)}
.tg-body{padding:16px;font-size:15px;line-height:1.6}
.tg-body .title{font-size:17px;font-weight:800;margin-bottom:10px;letter-spacing:.01em}
.tg-body p{margin:8px 0}
.tg-body ol{margin:8px 0 8px 22px}
.tg-body li{margin:5px 0}
.tg-body .speakers{margin-top:10px}
.tg-body .speakers b{display:block;margin-top:8px}
.tg-body .mission{background:var(--card);border-left:3px solid var(--signal);padding:8px 12px;margin:10px 0}
.tg-body .loc{margin-top:10px;color:var(--dim);font-size:14px}
.tg-body .cta{margin-top:10px;font-weight:600}
.tg-tags{padding:0 16px 14px;color:#2a6fb0;font-size:14px}
.tg-foot{padding:10px 16px;border-top:1px solid var(--line);display:flex;justify-content:space-between;color:var(--dim);font-size:13px}
/* Короткая версия */
.short{border:1px solid var(--ink);background:var(--card);max-width:560px;margin:0 auto 34px;padding:18px 20px}
.short .lbl{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em;text-transform:uppercase;margin-bottom:10px}
.short p{margin:6px 0;font-size:15px}
.note{max-width:560px;margin:0 auto;font-size:13.5px;color:var(--dim);border-top:1px solid var(--line);padding-top:14px}
footer{margin-top:40px;border-top:3px solid var(--ink);padding-top:12px;font-size:13px;color:var(--dim)}
</style>
</head>
<body>
<div class="wrap">
<div class="kicker">ИИШНИЦА · Соцсети main-комьюнити</div>
<h1>Пост для канала</h1>
<p class="sub">Готовая вёрстка поста: копируй текст прямо из блока — эмодзи и разбивка сохранятся.</p>
<div class="tg">
<div class="tg-head">
<div class="tg-ava">42</div>
<div class="tg-meta"><b>Кооператив 42 · main</b><span>канал комьюнити</span></div>
</div>
<div class="tg-body">
<div class="title">ПАНЕЛЬНАЯ СЕССИЯ «ОБЩЕЕ ВИДЕНИЕ» · ИИШНИЦА 🎤</div>
<p><b>45 минут · 3 участника · один результат</b> — название общего видения будущего.</p>
<p>Не классическая панель с вопросами модератора. Три круга:</p>
<ol>
<li><b>«Моё будущее»</b> — 4 минуты каждому без перебиваний: каким каждый видит будущее своей индустрии в самом дальнем горизонте</li>
<li><b>«Что понравилось»</b> — где картины будущего пересекаются</li>
<li><b>«Название общего видения»</b> — панель и зал вместе рождают одно название для всех картин будущего. Оно остаётся после сессии — в заголовках, фото и цитатах</li>
</ol>
<div class="speakers">
<b>На сцене:</b>
• Алекс Шкор — модератор и участник. Председатель кооператива, AI-инфраструктура и стратегия (Симбиот, Кибербанк, Domostroy)<br>
• Влад Шемардинов — видеопродакшн и контент, ReelsUp<br>
• Юрий Бокач — монетикс, финансовая архитектура, залоговые системы
</div>
<div class="mission">Рамку держит миссия кооператива «42»: <b>все миссии одинаково важны</b> — каждый взгляд на будущее важен, любая индустрия равна.</div>
<div class="loc">📍 Минск · точная дата и площадка — в следующих постах<br>🌐 cil.by</div>
<div class="cta">Приходи: приведи своё видение будущего — оно не менее важно, чем наше.</div>
</div>
<div class="tg-tags">#ИИШНИЦА #панельнаясессия #общеевидение #кооператив42 #ИИ #будущее #Минск</div>
<div class="tg-foot"><span>👁 просмотры</span><span>18:00</span></div>
</div>
<div class="short">
<div class="lbl">Короткая версия · сторис / репост</div>
<p><b>Панельная сессия «Общее видение» на ИИШНИЦЕ 🎤</b></p>
<p>45 минут · 3 спикера · 3 круга: моё будущее → что понравилось → название общего видения</p>
<p>Алекс Шкор · Влад Шемардинов · Юрий Бокач</p>
<p>Миссия 42: все миссии одинаково важны.</p>
<p>Минск · дата в следующих постах · cil.by</p>
</div>
<p class="note">Полный HTML-анонс сессии (для закрепа/сайта): <span class="mono">iishnitsa-announcement-main-community.html</span>. При появлении даты и площадки — обновлю обе версии и док.</p>
<footer>
ИИШНИЦА · пост для соцсетей main-комьюнити · кооператив «Друзья Коллективного Разума» · Минск · <span class="mono">cil.by</span><br>
<span class="mono">simbiot-harness / hour</span> · 2026-10-13
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,44 @@
# ИИШНИЦА · анонс для соцсетей main-комьюнити
## Пост в канал (Telegram, основной)
**ПАНЕЛЬНАЯ СЕССИЯ «ОБЩЕЕ ВИДЕНИЕ» · ИИШНИЦА 🎤**
45 минут · 3 участника · один результат — название общего видения будущего.
Не классическая панель с вопросами модератора. Три круга:
1️⃣ «Моё будущее» — 4 минуты каждому без перебиваний: каким каждый видит будущее своей индустрии в самом дальнем горизонте
2️⃣ «Что понравилось» — где картины будущего пересекаются
3️⃣ «Название общего видения» — панель и зал вместе рождают одно название для всех картин будущего. Оно остаётся после сессии — в заголовках, фото и цитатах
**На сцене:**
• Алекс Шкор — модератор и участник. Председатель кооператива, AI-инфраструктура и стратегия (Симбиот, Кибербанк, Domostroy)
• Влад Шемардинов — видеопродакшн и контент, ReelsUp
• Юрий Бокач — монетикс, финансовая архитектура, залоговые системы
Рамку держит миссия кооператива «42»: **все миссии одинаково важны** — каждый взгляд на будущее важен, любая индустрия равна.
📍 Минск · точная дата и площадка — в следующих постах
🌐 cil.by
Приходи: приведи своё видение будущего — оно не менее важно, чем наше.
---
## Короткая версия (сторис/репост)
Панельная сессия «Общее видение» на ИИШНИЦЕ 🎤
45 минут · 3 спикера · 3 круга: моё будущее → что понравилось → название общего видения
Алекс Шкор · Влад Шемардинов · Юрий Бокач
Миссия 42: все миссии одинаково важны.
Минск · дата в следующих постах · cil.by
---
## Хештеги
#ИИШНИЦА #панельнаясессия #общеевидение #кооператив42 #ИИ #будущее #Минск #AUM #миссия42
---
*Кооператив «Друзья Коллективного Разума» · Минск · cil.by · simbiot-harness / hour · 2026-10-13*

BIN
out/simbiot_04b_fixed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 KiB

View file

@ -355,6 +355,9 @@
<h3 class="footer-title">Cooperative</h3>
<ul class="footer-links">
<li><a href="https://cil.by">Portal &amp; coop — cil.by</a></li>
<li><a href="https://simbiot.cil.by">Simbiot — this site</a></li>
<li><a href="https://avatar.cil.by">Avatar2video (Vlad)</a></li>
<li><a href="https://vybirai.cil.by">VybirAI (Yura)</a></li>
</ul>
<h3 class="footer-title">Install</h3>
<code class="footer-install">curl -fsSL https://raw.cil.by/simbiot/install.sh | bash</code>

112
tests/test_reply_routing.py Normal file
View file

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
"""t-261013113629: маршрутизация ответов TG — ответ уходит в чат-источник.
Проверяет: ответ на сообщение из ГРУППЫ уходит в группу, из ЛИЧКИ — в личку;
send_batch_reply разбивает смешанный батч по чатам (не смешивает разговоры);
notify_chat/_default_chat_id выбирает личку Alex, даже если последнее
активное сообщение — из группы (старые записи без chat_type → chat_id<0).
Запуск: ./venv/bin/python tests/test_reply_routing.py
"""
import os
import sys
_DST = "/tmp/simbiot-test-reply-routing.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, tg_inbox
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL") + " " + name + (" | " + extra if not cond else ""))
if not cond:
fails.append(name)
def _recv(update_id, text, chat_id, chat_type, chat_title=None, date=1780000000):
chat = {"id": chat_id, "type": chat_type}
if chat_title:
chat["title"] = chat_title
tg_inbox.receive_message({"update_id": update_id, "message": {
"message_id": update_id % 100000, "date": date, "chat": chat,
"from": {"id": 1, "username": "alex", "first_name": "Alex"}, "text": text}})
es.init_db()
PRIVATE_ID = 424242 # личка Alex (>0)
GROUP_ID = -100999 # группа (<0)
# ── 1. send_reply: ответ в чат-источник (группа → группа, личка → личка) ──
_recv(900, "вопрос в личке", PRIVATE_ID, "private", date=1780000000)
_recv(901, "вопрос в группе", GROUP_ID, "supergroup", "Кооп 42", date=1780000001)
sends, actions = [], []
tg_inbox.send_message = lambda chat_id, text, reply_to=None, timeout=35: (
sends.append((chat_id, text)) or {"ok": True})
tg_inbox._api = lambda method, params, timeout=35: (
actions.append((method, params.get("chat_id"))) or {"ok": True})
tg_inbox.send_reply("m-900", "ответ в личку")
check("send_reply: личка → личка", sends and sends[-1][0] == PRIVATE_ID, str(sends))
tg_inbox.send_reply("m-901", "ответ в группу")
check("send_reply: группа → группа", sends and sends[-1][0] == GROUP_ID, str(sends))
model = es.run_projections()
check("send_reply: оба закрыты (replied)",
model["inbox"]["m-900"]["replied"] and model["inbox"]["m-901"]["replied"])
check("send_reply: chat_type сохранён",
model["inbox"]["m-901"]["chat_type"] == "supergroup"
and model["inbox"]["m-901"]["chat_title"] == "Кооп 42")
# реплай ушёл именно в чат-источник — проверено по sends выше; здесь — что
# ReplySent-событие тоже адресовано группе (to_chat)
_last = [e for e in es.get_events()
if e.get("event_type") == "ReplySent" and e.get("aggregate_id") == "m-901"]
check("send_reply: ReplySent-событие адресовано группе",
bool(_last) and str(GROUP_ID) in _last[-1]["data"], str(_last[-1:]))
# ── 2. send_batch_reply: смешанный батч НЕ смешивает чаты ──
_recv(910, "группа: первое", GROUP_ID, "group", "Кооп 42", date=1780000010)
_recv(911, "группа: второе", GROUP_ID, "group", "Кооп 42", date=1780000011)
_recv(912, "личка: первое", PRIVATE_ID, "private", date=1780000012)
sends.clear()
head = tg_inbox.send_batch_reply(["m-910", "m-911", "m-912"], "Сводный ответ")
sent_chats = sorted(c for c, _ in sends)
check("send_batch_reply: ДВЕ отправки (по числу чатов)", len(sends) == 2, str(sends))
check("send_batch_reply: ответ в группу ушёл в группу",
sends and any(c == GROUP_ID for c, _ in sends), str(sends))
check("send_batch_reply: ответ в личку ушёл в личку",
sends and any(c == PRIVATE_ID for c, _ in sends), str(sends))
model = es.run_projections()
check("send_batch_reply: все 3 закрыты",
all(model["inbox"][f"m-91{i}"]["replied"] for i in (0, 1, 2)))
# ── 3. _default_chat_id: дефолт — личка, не последняя группа ──
# последнее сообщение по ts — из группы (m-912 личка раньше m-911? нет:
# m-912 новее). Добавим ещё более новую группу, чтобы проверить приоритет.
_recv(920, "самое свежее — из группы", GROUP_ID, "group", "Кооп 42", date=1780000020)
default = tg_inbox._default_chat_id()
check("_default_chat_id: личка Alex, а не свежая группа",
default == PRIVATE_ID, str(default))
# старые записи без chat_type: группа = chat_id < 0
es.append_event("inbox", "m-930", "MessageReceived", {
"update_id": 930, "chat_id": PRIVATE_ID, "text": "старая личка без типа",
"kind": "text", "ts": "2026-10-13T10:00:00+00:00"})
es.append_event("inbox", "m-931", "MessageReceived", {
"update_id": 931, "chat_id": GROUP_ID, "text": "старая группа без типа",
"kind": "text", "ts": "2026-10-13T10:01:00+00:00"})
default = tg_inbox._default_chat_id()
check("_default_chat_id: без chat_type — тоже личка (chat_id>0)",
default == PRIVATE_ID, str(default))
# только группы — фолбэк на группу (лучше, чем None)
print()
print("FAILS:", fails if fails else "нет")
sys.exit(1 if fails else 0)

View file

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""t-261013131228: резолв цели реплая без текста (документ/фото).
Кейс 13.10: Alex в личке реплайнул на присланный ранее документ (концепт
панельной сессии для ИИШНИЦЫ) словами «вот этого мероприятия». Telegram не
отдаёт содержимое документа в reply_to_message → reply_to_text пустой →
мин-триаж видел «[reply на сообщение без текста]» без контекста и анонсировал
не то событие (перформанс 8 сентября вместо ИИШНИЦЫ 20.10). Фикс:
receive_message резолвит цель по инбоксу (chat_id, reply_to_message_id)
и пишет reply_to_desc; build_context показывает desc, когда текста нет.
Запуск: ./venv/bin/python tests/test_reply_target_resolve.py
"""
import os
import sys
_DST = "/tmp/simbiot-test-reply-target.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, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from harness import es, tg_inbox
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL") + " " + name + ("" if cond else " | " + extra))
if not cond:
fails.append(name)
def _recv(update_id, message_id, chat_id, text=None, document=None, reply_to=None,
chat_type="private", date=1780000000):
msg = {
"message_id": message_id, "date": date,
"chat": {"id": chat_id, "type": chat_type},
"from": {"id": 1, "first_name": "Alex"},
}
if text is not None:
msg["text"] = text
if document is not None:
msg["document"] = document
if reply_to is not None:
msg["reply_to_message"] = reply_to
tg_inbox.receive_message({"update_id": update_id, "message": msg})
DOC_CAPTION = "Концепт панельной сессии «Общее видение» для ИИШНИЦЫ (HTML): миссия 42 как рамка"
es.init_db()
# ── 1. Реплай на документ: цель резолвится из инбокса ──────────────────────
_recv(1001, 4060, 7569223131, text=DOC_CAPTION,
document={"file_id": "f1", "file_unique_id": "u1",
"file_name": "panel-concept.html", "mime_type": "text/html"})
_recv(1002, 4076, 7569223131, text="вот этого мероприятия",
reply_to={"message_id": 4060}) # Telegram текст цели не отдал
model = es.run_projections()
m = model["inbox"]["m-1002"]
check("реплай на документ: reply_to_message_id зафиксирован",
m["reply_to_message_id"] == 4060)
check("реплай на документ: reply_to_text пустой (Telegram не отдал)",
not (m.get("reply_to_text") or "").strip())
desc = m.get("reply_to_desc") or ""
check("реплай на документ: reply_to_desc резолвнулся из инбокса", bool(desc), repr(desc))
check("реплай на документ: desc несёт содержимое (caption документа)",
("Концепт панельной сессии" in desc) or ("документ" in desc.lower()), repr(desc))
# ── 2. Реплай на текстовое: desc не нужен, текст из Telegram ───────────────
_recv(2001, 5001, 7569223131, text="старое текстовое сообщение")
_recv(2002, 5002, 7569223131, text="принято",
reply_to={"message_id": 5001, "text": "старое текстовое сообщение"})
model = es.run_projections()
m = model["inbox"]["m-2002"]
check("реплай на текст: reply_to_text из Telegram",
(m.get("reply_to_text") or "").strip() == "старое текстовое сообщение")
check("реплай на текст: reply_to_desc не пишется (не нужен)",
not (m.get("reply_to_desc") or "").strip())
# ── 3. Цель вне инбокса: не выдумываем desc ────────────────────────────────
_recv(3001, 6002, 7569223131, text="вот этого мероприятия",
reply_to={"message_id": 999999})
model = es.run_projections()
m = model["inbox"]["m-3001"]
check("цель вне инбокса: reply_to_desc не выдумывается",
not (m.get("reply_to_desc") or "").strip())
# ── 4. build_context: якорь реплая несёт контекст документа ────────────────
_recv(4001, 7000, 7569223131, text=DOC_CAPTION,
document={"file_id": "f2", "file_unique_id": "u2",
"file_name": "panel-concept.html", "mime_type": "text/html"})
_recv(4002, 7001, 7569223131, text="вот этого мероприятия",
reply_to={"message_id": 7000})
# закрываем ранние входящие, чтобы в контекст попал только свежий батч
for mid in ("m-1001", "m-1002", "m-2001", "m-2002", "m-3001"):
mm = model["inbox"].get(mid)
if mm is not None:
es.append_event("inbox", mid, "MessageIgnored", {"reason": "test"})
from harness.scheduler import build_context
from harness.levels import LEVELS
ctx = build_context(LEVELS["min"], es.run_projections(), batch=[])
check("build_context: якорь реплая показывает контекст документа",
"в ответ на" in ctx and "[reply на сообщение без текста]" not in ctx
and "Концепт панельной сессии" in ctx)
print()
if fails:
print("FAILS: " + ", ".join(fails))
sys.exit(1)
print("ALL PASS")

88
tools/tg_members.py Normal file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Список участников TG-чата(ов) харнеса (t-261010, просьба Alex).
Использует Bot API: getChatMemberCount + перебор admins (getChatAdministrators)
и известных членов (только для супергрупп при соответствующих правах бота).
Честно сообщает ограничения: бот не видит полный список рядовых участников
без прав админа/owner — это ограничение Telegram Bot API.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from harness import tg_inbox as tgi # noqa: E402
def chat_members(chat_id):
tgi._load_env()
if not tgi.TG_TOKEN:
print("error: нет SIMBIOT_TG_TOKEN")
return 1
count = tgi._api("getChatMemberCount", {"chat_id": chat_id})["result"]
print(f"Чат {chat_id}: участников — {count}")
try:
chat = tgi._api("getChat", {"chat_id": chat_id})["result"]
title = chat.get("title") or chat.get("username") or "личный чат"
print(f" Название: {title}")
except Exception:
pass
try:
admins = tgi._api("getChatAdministrators", {"chat_id": chat_id})["result"]
for a in admins:
u = a.get("user") or {}
name = " ".join(x for x in [u.get("first_name"), u.get("last_name")] if x) or u.get("username") or u.get("id")
print(f" [{'@' + u['username'] if u.get('username') else u.get('id')}] {name} — {a.get('status')}")
except Exception as e:
print(f" (админов не удалось получить: {e})")
print(" Полный список рядовых участников Bot API не отдаёт — только количество и админов.")
return 0
def main():
# чаты: из inbox (все chat_id входящих) + ALLOWED
ids = []
try:
import sqlite3
from harness import paths
for db in [paths.db_path(), os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "tg_inbox.db"), os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "hg_tg_inbox.db")]:
if not os.path.exists(db):
continue
try:
con = sqlite3.connect(db)
for row in con.execute("SELECT DISTINCT payload FROM events").fetchall():
try:
p = json.loads(row[0])
cid = p.get("chat_id") or (p.get("message") or {}).get("chat", {}).get("id")
if cid:
ids.append(cid)
except Exception:
pass
con.close()
except Exception:
pass
except Exception:
pass
tgi._load_env()
ids += [n for n in (_num(x) for x in tgi.ALLOWED) if n]
seen, rc = set(), 0
for cid in ids:
if cid in seen:
continue
try:
seen.add(cid)
rc |= chat_members(cid)
except Exception as e:
print(f"Чат {cid}: ошибка {e}")
return rc
def _num(x):
try:
return int(x)
except (TypeError, ValueError):
return None
if __name__ == "__main__":
sys.exit(main() or 0)