- TypeScript 45.6%
- Go 40.3%
- HTML 11.2%
- CSS 2.4%
- JavaScript 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Tiptap's focus() scrolls the selection into view a frame later. ProseMirror then measures a text range, and jsdom has no Range.getClientRects. On the Forgejo runner that frame landed inside the next test and failed the whole panel suite with an unhandled TypeError; on a fast machine it never did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .ai | ||
| .forgejo/workflows | ||
| api | ||
| cmd/straylight | ||
| deploy | ||
| design | ||
| internal | ||
| web | ||
| .dockerignore | ||
| .env.dev.example | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Dockerfile.web | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
| Taskfile.yml | ||
Straylight
Self-hosted tracker zadań w stylu Linear dla agentów kodujących. Ludzie planują i zatwierdzają pracę w Web UI, agenci pobierają zadania przez MCP, a zadanie może nieść pakiet spec zgodny z pi-sdd-kit.
Projekt jest w budowie: gotowy jest rdzeń (etap 1) — REST API dla użytkowników, zespołów, projektów, zadań, relacji, komentarzy i aktywności, z logowaniem lokalnym i OIDC — oraz agenci, specyfikacje i serwer MCP (etap 2). Web UI i cykle rozwija się dalej; plan opisuje .ai/sdd/PLAN.md.
Uruchomienie
task verify # format, vet, testy, build
export STRAYLIGHT_ADMIN_EMAIL=admin@example.com
export STRAYLIGHT_ADMIN_PASSWORD='co-najmniej-12-znakow'
./straylight migrate # tworzy lub aktualizuje schemat
./straylight serve # http://localhost:8080/healthz, API pod /api
Kontrakt API: api/openapi.yaml.
| Zmienna | Domyślnie | Znaczenie |
|---|---|---|
STRAYLIGHT_ADDR |
:8080 |
adres nasłuchu |
STRAYLIGHT_DB |
straylight.db |
plik bazy SQLite |
STRAYLIGHT_BASE_URL |
http://localhost:8080 |
publiczny origin (bez ścieżki); wymagany przy OIDC |
STRAYLIGHT_ADMIN_EMAIL |
— | e-mail konta startowego admina |
STRAYLIGHT_ADMIN_PASSWORD |
— | hasło konta startowego, min. 12 znaków; tworzy konto tylko, gdy go nie ma |
STRAYLIGHT_ADMIN_PASSWORD_FILE |
— | hasło z pliku zamiast ze zmiennej |
STRAYLIGHT_ADMIN_NAME |
Admin |
nazwa konta startowego |
STRAYLIGHT_LOCAL_LOGIN |
true |
logowanie hasłem włączone |
STRAYLIGHT_SESSION_TTL |
168h |
czas bezczynności, po którym sesja wygasa |
STRAYLIGHT_OIDC_ISSUER, _CLIENT_ID, _CLIENT_SECRET |
— | logowanie OIDC (np. Pocket ID); komplet trzech je włącza. Redirect URI: <BASE_URL>/api/auth/callback |
STRAYLIGHT_TRUSTED_PROXIES |
— | sieci CIDR zaufanych reverse proxy, rozdzielone przecinkami |
STRAYLIGHT_AUTO_MIGRATE |
false |
migracja schematu przy starcie serve |
STRAYLIGHT_LEASE_TTL |
15m |
jak długo agent trzyma zadanie bez heartbeat (od 1m do 24h) |
STRAYLIGHT_HEARTBEAT_INTERVAL |
5m |
najkrótszy odstęp między przedłużeniami lease (od 10s); nie może przekraczać połowy STRAYLIGHT_LEASE_TTL |
STRAYLIGHT_MCP_RATE |
120 |
żądań na minutę na agenta (od 10 do 6000) |
Za reverse proxy ustaw STRAYLIGHT_TRUSTED_PROXIES. Bez tego limiter prób logowania widzi wszystkich pod adresem proxy i pięć błędnych prób na minutę blokuje logowanie na dane konto wszystkim.
Agenci i MCP
Agent (Claude Code, pi) pobiera zadania z serwera MCP pod <serwer>/mcp (Streamable HTTP, bez sesji) i uwierzytelnia się własnym tokenem. Kontrakt narzędzi: api/mcp-tools.md.
- Utworzenie agenta. Administrator wchodzi w panelu w Ustawienia → Agenci → Nowy agent, podaje nazwę, rodzaj (
claudealbopi), zdolności (etykiety zadań, które agent może brać) i zespoły. Agent widzi i zmienia tylko zadania swoich zespołów. - Token. Jawny token (
slg_…) pokazuje się tylko raz, w oknie po utworzeniu; potem panel zna tylko prefiks. Trzymaj go w zmiennej środowiskowejSTRAYLIGHT_TOKEN, a nie w plikach konfiguracji, repozytorium ani historii powłoki. Wymiana tokenu i dezaktywacja agenta (też w panelu) działają od razu: stary token dostaje401, a lease agenta są zwalniane. - Podłączenie. Polecenia dla Claude Code i pi (zmienne
STRAYLIGHT_URLiSTRAYLIGHT_TOKEN, skillstraylight-agent, sprawdzenie połączenia) opisujedeploy/agents/README.md; pi:deploy/agents/pi/README.md. Okno tokenu w panelu podaje te same polecenia z adresem tej instancji. - Pobieranie zadań.
next_taskwybiera i przydziela zadanie w stanietodo(najpierw przypisane do agenta, potem wg priorytetu i wieku); agent trzyma je przez lease i przedłuża go przezheartbeat. Zadanie ze specyfikacją jest dostępne dopiero po zatwierdzeniu bramkitasks:approvedprzez człowieka; bramki (requirements,design,tasks) zatwierdza wyłącznie człowiek w panelu. - Limity.
STRAYLIGHT_LEASE_TTLiSTRAYLIGHT_HEARTBEAT_INTERVALustawiają czas lease i najkrótszy odstęp między jego przedłużeniami; interwał musi być co najmniej10si nie większy niż połowa TTL, inaczej serwer nie wystartuje.STRAYLIGHT_MCP_RATEogranicza liczbę żądań na agenta na minutę (po przekroczeniu429zRetry-After). Agent może utworzyć najwyżej 100 zadań na godzinę, żądanie MCP ma najwyżej 1 MiB, a po 20 błędnych tokenach z jednego adresu kolejne dostają429. Limity liczone są w pamięci jednego procesu.
Konta lokalne i zespoły
- Konta lokalne. Pierwszego administratora tworzą
STRAYLIGHT_ADMIN_EMAILiSTRAYLIGHT_ADMIN_PASSWORD(albo…_FILE); logowanie hasłem wyłączaSTRAYLIGHT_LOCAL_LOGIN=false. Administrator tworzy kolejne konta w panelu: Ustawienia → Użytkownicy → Nowy użytkownik (e-mail, nazwa, rola administrator lub członek, hasło startowe), zmienia role, dezaktywuje konta (dezaktywowane konto traci sesje) i ustawia nowe hasło. Hasło ma od 12 do 128 znaków; jego zmiana kończy pozostałe sesje. Własne hasło zmienia się z menu użytkownika (Zmień hasło). - Zespoły. Ustawienia → Zespoły: administrator tworzy zespół (klucz 2–5 liter A–Z, prefiks numerów zadań, np.
RZL-12, niezmienny; nazwa), dodaje i usuwa członków, zmienia nazwę i archiwizuje zespół. Zarchiwizowany zespół jest tylko do odczytu, a jego zadania znikają z kolejki agentów. Członek widzi tylko zespoły, do których należy.
Uruchomienie w kontenerze
cp .env.example .env # ustaw STRAYLIGHT_ADMIN_PASSWORD (min. 12 znaków)
task compose:up # buduje oba obrazy i czeka, aż usługi będą zdrowe
task compose:logs
task compose:down # dane zostają w wolumenie straylight-data
docker-compose.yml uruchamia dwie usługi:
| Usługa | Obraz | Rola |
|---|---|---|
web |
Dockerfile.web (nginx bez uprawnień roota) |
jedyne wejście: panel pod http://localhost:8080/, a /api/ i /healthz przekazuje do api |
api |
Dockerfile (binarium Go) |
REST API, migracja schematu przy starcie, baza SQLite w wolumenie; bez opublikowanego portu |
Panel i API są pod jednym originem, więc STRAYLIGHT_BASE_URL wskazuje adres usługi web. Konfiguracja nginx leży w deploy/nginx/. Ustawia te same nagłówki cache i bezpieczeństwa co serwer wbudowany w binarium, więc zmieniaj je razem z internal/adapter/spa.
Z Podmanem: COMPOSE="podman compose" task compose:up.
Bez kontenerów nadal działa jedno binarium: task build osadza panel i ./straylight serve serwuje go pod /. Obraz api panelu nie zawiera; w kontenerach wejściem jest web.
Trzy rzeczy przed wystawieniem poza własną maszynę:
- Port jest publikowany tylko na
127.0.0.1, bo ruch nie jest szyfrowany. Do sieci wystawiaj go przez reverse proxy z TLS i ustawSTRAYLIGHT_BASE_URLna publiczny adreshttps://. - Kontenery dostają stałą podsieć
STRAYLIGHT_SUBNET(domyślnie172.30.42.0/24), aapidomyślnie ufa jej jakoSTRAYLIGHT_TRUSTED_PROXIES. Dzięki temu limiter prób logowania widzi adres, z którego klient połączył się zweb, a nie adres konteneraweb. NagłówekX-Forwarded-Forod klienta jest zastępowany, więc nie da się nim ominąć limitu. - Za dodatkowym reverse proxy kontener
webwidzi adres tego proxy, nie przeglądarki. Limiter traktuje wtedy wszystkich jak jednego klienta i pięć błędnych prób logowania na minutę blokuje dane konto wszystkim. To samo dotyczy środowisk, w których ruch do kontenera przechodzi przez NAT (Docker Desktop, Podman na macOS).
CI i obrazy
Pipeline Forgejo Actions jest w .forgejo/workflows/ci.yml.
| Zdarzenie | Co się dzieje |
|---|---|
| push do dowolnej gałęzi | task ci (testy Go, panel, budżety wydajności, e2e), potem budowa obu obrazów bez publikacji |
push do main |
to samo oraz publikacja :main i :sha-<7 znaków> |
tag vX.Y.Z |
to samo oraz publikacja :X.Y.Z i :latest |
Obrazy trafiają do rejestru kontenerów tej instancji Forgejo:
git.eiac.dev/tools/straylight-api
git.eiac.dev/tools/straylight-web
Jednorazowa konfiguracja:
- Runner z etykietą
docker(zwykły executor kontenerowy, bez dostępu do Dockera). Przy innej etykiecie zmieńruns-onw obu zadaniach. Obrazy buduje Kaniko, więc runner nie potrzebuje uprawnień. - Sekrety repozytorium albo organizacji (
Ustawienia ▸ Actions ▸ Sekrety):REGISTRY_USER(nazwa użytkownika) iREGISTRY_TOKEN(token dostępu z zakresemwrite:package). Automatyczny token zadania nie ma dostępu do pakietów. Bez sekretów publikacja zmaini z tagów kończy się błędem; gałęzie ich nie potrzebują.
Tag wydania musi mieć postać vX.Y.Z i wskazywać commit należący do main; inaczej pipeline kończy się błędem przed budową obrazów. Sekrety rejestru dostaje tylko krok publikacji z main i z tagów. Kaniko pobiera kod bez tokenu, więc przy prywatnym repozytorium krok budowy trzeba uzupełnić o GIT_USERNAME i GIT_PASSWORD. Go i Task są pobierane ze sprawdzeniem sumy sha256 (sumy w env workflow zmienia się razem z wersją), a akcje są przypięte do commitów.
Kontrola tagu chroni przed pomyłką, nie przed osobą z prawem zapisu: workflow jest brany z wypychanego commita, więc ktoś, kto może wypchnąć gałąź albo tag, może go też zmienić. Właściwą granicą są ustawienia repozytorium: chroniona gałąź main i chronione tagi v* (Ustawienia ▸ Gałęzie i ▸ Tagi), a token rejestru powinien mieć wyłącznie zakres write:package.
Wersję obrazu Playwright w zadaniu testów trzeba podnosić razem z @playwright/test w web/package-lock.json.
Układ
| Ścieżka | Zawartość |
|---|---|
cmd/straylight |
composition root |
internal/domain |
encje i reguły, tylko biblioteka standardowa |
internal/usecase |
interaktory i porty (ports.go) |
internal/adapter |
rest, mcp, sse, sqlite, oidc |
api/ |
kontrakty: openapi.yaml, mcp-tools.md |
web/ |
React SPA |
.ai/ |
steering i specyfikacje SDD |