diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af7f74..1b2b60e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to `gate.cat` will be documented in this file. +## [0.4.18] -- the listing sells, the CLI hints honestly (2026-07-22) + +### Added +- Once-a-DAY Solo hint on `gate.cat status`/`stats` (`gatecat/_nudge.py: + maybe_nudge_cli`): fires only when interventions > 0 and no + `GATECAT_CLOUD_API_KEY`, stderr-only, silenced by `GATECAT_NO_NUDGE`/ + `GATECAT_QUIET`. A process-wide guard guarantees at most ONE hint of any + kind per run. +- One-time-per-machine policy-pack hint (`gatecat/_pack_hint.py`): if a stack + CLI is on PATH (`stripe` -> Fintech; `vercel`/`netlify`/`fly`/`heroku`/ + `railway`/`render`/`supabase` -> PaaS), prints the matching EUR 29 pack with + its PRICING.md scope. Same opt-outs, same shared no-stacking guard + (`~/.gatecat/.pack_nudged`). +- `gate.cat report` footer links the off-machine variant of the same report + (ASCII-safe; the paste-safety test stays green). `gate.cat cloud` without a + key now says where a key comes from. + +### Changed +- PyPI listing acts as a landing page: `[project.urls]` Homepage now points at + https://gate.cat (plus a Pricing link), and the README carries a free-forever + /pricing block right under the install section. +- README proxy section: stale "21 deny policies" corrected to the measured 71 + default policy walls (FACTS.md F10). No policy, recall, or bypass changes. + + ## [0.4.17] -- one-time post-veto Team hint (2026-07-16) ### Added diff --git a/PRICING.md b/PRICING.md index 62783ae..8ce05e2 100644 --- a/PRICING.md +++ b/PRICING.md @@ -47,13 +47,13 @@ Full boundary, both directions: [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md). | Local CLI dashboard + local reports | ✅ | ✅ | ✅ | ✅ | ✅ | | **Off-machine veto history** (the copy the agent has no credentials for) + email alerts | — | ✅ | ✅ | ✅ | ✅ | | Monthly report from the off-machine log | — | ✅ yours | ✅ fleet-wide | ✅ signed, + control mapping | ✅ custom scope | -| Shared signed policy file for a fleet (pull-only, local review) | — | — | ✅ *(ships this month)* | ✅ | ✅ | +| Shared signed policy file for a fleet (pull-only, local review) | — | — | ✅ *(rolling out)* | ✅ | ✅ | | Evidence log self-hosted in **your** infra | — | — | — | ✅ | ✅ | | Support | community | email | priority | dedicated | dedicated + custom policies | | Price | €0 forever | €19/mo | €149/mo flat | €399/mo | custom | | | | [**Start Solo →**](https://buy.stripe.com/7sY6oAaRD5qU79m2Vo67S09) | [**Start Team →**](https://buy.stripe.com/9B66oA5xj2eIaly2Vo67S0a) | [**Start Business →**](https://buy.stripe.com/7sYdR2e3PcTm2T6cvY67S0b) | [email us](mailto:bogumil@bgml.ai?subject=gate.cat%20white-glove) | -Stripe checkout is live while Lemon Squeezy account review is pending. Billing +Stripe checkout is live and is the payment channel. Billing includes automatic tax handling, cancellation at any time and a **30-day full refund, no questions asked.** diff --git a/README.md b/README.md index edcf677..9b24f9d 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ pip install "gate-cat[all]" # everything ``` > Quote the extras (`"gate-cat[openai]"`) — zsh treats bare `[...]` as a glob. +**Free forever** — the full local gate, nothing rate-limited. The paid layer is +optional and off by default: **Cloud Solo €19/mo · Team €149/mo flat (up to 10 +machines) · Business €399/mo · one-time €29 policy packs** — details and honest +boundaries in [PRICING.md](PRICING.md). Blocking never depends on payment. + ## The hook — the strongest mode Enforcement in the harness, **outside the model's control flow**: the tool call @@ -414,7 +419,7 @@ Validated with 100-question benchmark across 5 domains: **0.892 mean quality rat Ollama, NIM, OpenRouter, vLLM and LM Studio all speak the OpenAI API, so **one proxy in front of them protects them all** — your agent changes one `base_url`, writes no code. When the model asks to run a tool, the proxy checks the proposed -call against the 21 deny policies and **blocks the dangerous ones before the +call against the 71 default policy walls (plus any policy packs you load) and **blocks the dangerous ones before the agent executes them** (`rm -rf`, `terraform destroy`, `DROP TABLE`, disk wipes, repo deletion, ...). diff --git a/docs/AUTOPILOT-LOOP.md b/docs/AUTOPILOT-LOOP.md new file mode 100644 index 0000000..2814976 --- /dev/null +++ b/docs/AUTOPILOT-LOOP.md @@ -0,0 +1,217 @@ +# AUTOPILOT-LOOP — gate.cat (cel: $2,000 USD) + +Plik stanu autonomicznej pętli operacyjnej (cron co 1h, sesja Claude Code Remote, +branch `claude/email-cron-strategy-automation-drmkv4`). Agent czyta ten plik na +początku każdego przebiegu i aktualizuje go na końcu. Kolejność przebiegu: +**POCZTA → BACKLOG → (pusta kolejka? → nowy panel strategiczny) → ZAPIS.** + +## ZASADY TWARDE + +1. **Wysyłka maili** (dyspozycja Bogumiła 2026-07-22: agent MA wysyłać — stan + faktyczny): konektor Gmail nie ma funkcji send, a klasyfikator uprawnień + sesji zablokował zarówno dostęp do credentiali, jak i COMMIT pipeline'u + wysyłkowego (ops/mail_sender) do repo. Kanał agenta = Gmail DRAFT + (`create_draft` z `replyToMessageId`) + natychmiastowa flaga w [USER]. + Kod sendera (SMTP + allowlist + systemd timer) został dostarczony userowi + bezpośrednio na czacie 2026-07-22 — jeśli user zainstaluje go na VPS i/lub + doda regułę permissions w ustawieniach Claude Code, protokół przechodzi na + outbox. Zawsze: tylko odpowiedzi w istniejących wątkach, nigdy cold-outreach. +2. **Outreach partnerski: tylko w trybie kampanii zleconej przez usera** (zlecenie + 2026-07-22): drafty do publicznych kontaktów biznesowych z zatwierdzonej listy, + zawsze personalizowane, max 15/dzień; wysyłka po stronie usera/sesji lokalnej. + Poza kampanią: drafty tylko w istniejących wątkach. +3. **Liczby publiczne wyłącznie z FACTS.md** — nowa liczba = najpierw wiersz + w FACTS.md z artefaktem pomiaru. Żadnych zmyślonych metryk, opinii, klientów. +4. **Zero wydawania pieniędzy** — żadnych zmian w Stripe (ceny/produkty), żadnych + sponsoringów, żadnych zakupów. Decyzje finansowe → sekcja [USER]. +5. **Zero publikacji** — PyPI publish, deploy na VPS, posty na HN/socialach robi + tylko Bogumił; agent przygotowuje paste-ready artefakty w `ops/launch/`. +6. **Git**: commit + `git push -u origin claude/email-cron-strategy-automation-drmkv4` + (retry 2s/4s/8s/16s), nigdy na master; draft PR utrzymywany na bieżąco. +7. **Dedupe draftów**: przed `create_draft` sprawdź `list_drafts` + ledger niżej + + czy w wątku nie ma już naszej odpowiedzi po ostatniej wiadomości rozmówcy. +8. **Kod = testy zielone** (`python -m pytest -q` dla dotkniętych ścieżek); + copy = spójne z FACTS.md. SPRZEDAŻ > kosmetyka. +9. **Repo jest PUBLICZNE** — żadnych prywatnych adresów e-mail osób trzecich, + treści korespondencji ani artefaktów imiennego outreachu w commitach + (dotyczy też T13/dm_dimitrios: dostarczać przez czat/SendUserFile, nie repo). + +## CEL I STAN + +| Metryka | Wartość | Stan na | +|---|---|---| +| Przychód gate.cat (potwierdzony w Gmail/Stripe) | **$0 / $2,000** | 2026-07-22 | +| Pobrania PyPI (trailing month, bez mirrorów) | 2,528 (F13; 1,588 → 2,528 w 11 dni) | 2026-07-22 | +| Płacący klienci Cloud/Packs | 0 | 2026-07-22 | +| Wersja na PyPI | 0.4.17 (F9) | 2026-07-16 | + +Znane fakty operacyjne: +- Nudge post-veto z 0.4.17 kieruje na `gate.cat/teams.html` — **strona nie istnieje (404)** → T1. +- Lemon Squeezy **odrzuciło** aplikację 2026-07-14; Stripe jest jedynym kanałem → T10. +- Outreach 2026-07-15 (7 adresatów): odpowiedzi = Mike Privette (pozytywna, pyta o trakcję), + Julian Goldie (chce płatnego sponsoringu — misread darmowej oferty affiliate), Console.dev (auto-ack). +- Mail do grzegorz@grzegorzlapanowski.pl odbił się (błąd serwera odbiorcy) 2026-07-19. +- Site = statyczne `docs/` serwowane z VPS (OVH) za Cloudflare; deploy poza repo → kroki [USER]. +- METRICS.log dopisuje codziennie GitHub Action na masterze (możliwe konflikty — rebase przed pushem). +- **RÓWNOLEGŁA SESJA LOKALNA (Claude Desktop u Bogumiła) deployuje bezpośrednio na VPS z pominięciem gita** — wykryta 2026-07-22 ~18:40: live teams.html/partners.html to JEJ wersje (lepsze; zsynchronizowane do repo w przebiegu #4). Zasada: przed każdą edycją plików site'u najpierw `curl` z produkcji i porównaj — produkcja wygrywa; nigdy nie nadpisuj ślepo deployem. + +## KOLEJKA (backlog agenta) + +_Synteza panelu 2026-07-22 (4 propozycje, 12 krytyk sędziów, wszystkie kluczowe fakty zweryfikowane w repo). Uczciwa rama sędziów: żadne pojedyncze zadanie nie domyka $2,000 — realna 30-dniowa gotówka to warm threads + Show HN + odblokowanie martwych linków, na które celuje już wysłany nudge 0.4.17; reszta to konwersja i higiena. Kolejność: oczekiwane $ / effort / time-to-cash._ + +- [x] **T1 — Napraw martwe linki lejka: docs/teams.html + docs/partners.html (jeden PR)** — WYKONANE 2026-07-22 przebieg #1: obie strony zbudowane (standalone, paleta index.html, zero JS; wszystkie liczby wg allowed-wording z FACTS.md F1b/F2/F3 + PRICING.md; policy sharing opisane jako "rolling out" zamiast datowanej obietnicy — patrz T10), sitemap.xml uzupełniony, `ops/deploy_landing.sh` gotowy (rsync bez --delete, sha256 verify, curl 200, restart gatecat-fulfill). HTML/XML zwalidowane. Live po USER-2. Oryginalna spec: — `gatecat/_nudge.py` (shipped w 0.4.17, odpala się po pierwszym realnym veto na każdej maszynie) oraz README.md:285/292 kierują na gate.cat/teams.html i /partners.html, których nie ma w docs/ (żywe 404 — zweryfikowane). Zbuduj obie jako standalone strony na layoucie index.html (bez przebudowy bundla): teams.html = Team €149 value prop + wpleciona verbatim kopia audit-pilot z PRICING.md:109-127 + live Stripe links (Team `buy.stripe.com/9B66oA5xj2eIaly2Vo67S0a`, Business `...7sYdR2e3PcTm2T6cvY67S0b`); partners.html = 30% lifetime-recurring + mailto CTA (affiliate.py nie ma self-serve signup). Do tego `ops/deploy_landing.sh` (artefakt dla USER-2). Acceptance: wpisy w sitemap.xml, każda liczba ma wiersz w FACTS.md/PRICING.md, jeden PR gotowy do merge; live dopiero po USER-2. _(impact: $150-600 (sufit wg sędziów), effort: M, B2B+PRODUCT-LED, 6.7/5.0)_ +- [x] **T2 — Gmail draft do Mike'a Privette (Return on Security), wątek 19f669c061fe1503** — WYKONANE 2026-07-22 przy bootstrapie: draft `r-7583389221339500184` w wątku; szczere liczby wyłącznie z FACTS.md (2,528 pobrań/30d, 0 real misses / 1,085,159 komend, revenue day-zero podane wprost), oferta danych do mapy kategorii "Agent Runtime Security". → USER-1: wyślij. +- [x] **T3 — Gmail draft do Juliana Goldie: affiliate ≠ sponsoring, wątek 19f675a02242badf** — WYKONANE 2026-07-22: draft `r1170664853448124004` (reply do 19f75bbfe62653c7); zero zobowiązań finansowych, matematyka prowizji z cennika, link do partners.html (wyślij po deploy'u USER-2 albo zaraz — strona wstanie za chwilę). UWAGA: w Draftach wiszą 2 STARE drafty do Juliana z 2026-07-15 (`r6116468691101999441`, `r-7678200491588625290` — jeden ze stalą ceną €9) → user powinien je skasować. Oryginalna spec: — Julian odpisał cennikiem płatnych sponsoringów, a oferta to darmowy 30% lifetime-recurring affiliate (README:289-292); draftuj do nowszego z dwóch zduplikowanych wątków i odnotuj duplikat. Jedna klaryfikacja bez zobowiązań: zero upfront spend, link do partners.html (po T1) albo sekcji README, decyzję o płatnym sponsoringu zostaw explicite Bogumiłowi. Acceptance: draft w wątku, żadnych obietnic wydatków. _(impact: $0-800 opcjonalność, effort: S, B2B, 6.7)_ +- [x] **T4 — Odśwież i fact-checkuj paczkę Show HN (+ warunkowy wariant lobste.rs)** — WYKONANE 2026-07-22 przebieg #2: `ops/launch/show_hn_ready.md` (tytuł+body+pierwszy komentarz, paste-ready) i `ops/launch/lobsters_ready.md` (warunkowy). Fixy: F1b/F4 rozdzielone (0 real misses w 1M replay ≠ 1/129 benign false-block bypass suite), 69→71 policies (F10), €9→€19, "zero-dependency core" (pyproject `dependencies = []`), first-comment z modelem biznesowym i stroną cenową zamiast gołych linków Stripe (norma HN; uzasadnienie w pliku). Linia "one-command install" pominięta — T11 niezmergowane. → USER-4: publikacja PO deploy'u. Oryginalna spec: — Nie pisz od zera: zweryfikuj istniejący draft z docs/LAUNCH_KIT_2026-07-14.md przeciw FACTS.md (fix sędziów: popraw konflację "4 allowed commands"/false-block wg F4 = 178/178 i 1/129 benign; €19 nie €9; 71 policies wg F9), dopisz first-comment z live checkout links i linią "one-command install" jeśli T11 zmergowane. Zapisz ops/launch/show_hn_ready.md + ops/launch/lobsters_ready.md (lobste.rs jest invite-only — wariant tylko-jeśli-konto). Acceptance: każda liczba ma wiersz w FACTS.md, posty paste-ready bez dalszej edycji. _(impact: odblokowuje $200-1000 przez USER-4, effort: S, DISTRIBUTION, 4.8)_ +- [x] **T5 — PyPI listing jako landing page (do release-PR 0.4.18)** — WYKONANE 2026-07-22 przebieg #3: pyproject.urls Homepage→https://gate.cat + Pricing→https://gate.cat/teams.html (odstępstwo od speca: #pricing to niezweryfikowana kotwica w bundlowanym index.html; teams.html kontrolujemy); blok cenowy pod sekcją Install w README (PyPI renderuje README = landing); README:417 "21 deny policies"→"71 default policy walls" (proxy używa DOGFOOD_DEFAULTS; 71/73 zweryfikowane importem, F10). WAŻNE USTALENIE: €9 na docs/index.html to CELOWA oferta founding ("locked for life, then €19", osobny link Stripe, test test_marketing_consistency pilnuje) — NIE jest stale, nie "naprawiać". test_marketing_consistency: 5 passed. Wersji NIE podbito — bump przy cięciu 0.4.18 po T6/T7. Oryginalna spec: — pyproject.toml [project.urls] (linie 93-96) kieruje Homepage/Repository/Issues na repo z 0 stars: dodaj `Homepage=https://gate.cat` i `Pricing=https://gate.cat/#pricing?source=pypi`; wstaw 4-linijkowy blok "Free forever · Cloud Solo €19/mo · Team €149 · Packs €29" zaraz pod sekcją install README (pricing dziś na linii ~265/619); grep i uzgodnij stale liczby (€9, 21/69 policies → €19, 71). Acceptance: zero sprzeczności liczbowych w repo, zmiany w release-PR 0.4.18 z checklistą publication-gate; publish = USER-3. _(impact: $50-250, effort: S, CONVERSION, 4.8)_ +- [x] **T6 — Rozszerz istniejący _nudge.py o Solo surface (NIE reimplementuj)** — WYKONANE 2026-07-22 przebieg #4: `maybe_nudge_cli()` w _nudge.py (raz/dzień przez ~/.gatecat/nudge_last, stderr, honoruje GATECAT_NO_NUDGE/GATECAT_QUIET, cicho gdy GATECAT_CLOUD_API_KEY albo interventions=0) + wspólna blokada procesu `mark_fired()`/`fired_this_run()` — nigdy 2 nudge w jednym przebiegu (gotowe pod T7); wpięte w `gate.cat status` i `stats`; stopka `report` z linkiem ?source=report (ASCII — test wklejalności pilnuje, stąd EUR nie €); `cloud` bez klucza → link ?source=cli. Testy: 9 nowych w tests/test_nudge_cli.py; 49 passed w dotkniętych suite'ach. Do 0.4.18. Oryginalna spec: — Post-veto nudge już jest w 0.4.17; dodaj rate-limitowane (raz/dzień, `~/.gatecat/nudge_last`) linie: status/stats przy blocked>0 bez cloud key, stopka raportu, `cloud` bez klucza → krótki pitch + gate.cat/#pricing?source=cli; copy verbatim z PRICING.md, respektuj GATECAT_NO_NUDGE/GATECAT_QUIET. Acceptance: testy zielone, nigdy dwa nudge w jednym przebiegu (koordynacja z T7), wchodzi do 0.4.18; zasięg uczciwie = tylko nowe instalacje od publish (fix sędziów). _(impact: $100-400 (sufit), effort: S, CONVERSION, 4.8)_ +- [x] **T7 — Pack hint środowiskowy (gatecat/_pack_hint.py)** — WYKONANE 2026-07-22 przebieg #5: shutil.which na stripe (→Fintech €29) i vercel/netlify/fly/heroku/railway/render/supabase (→PaaS €29), scope verbatim z PRICING.md, raz na maszynę (~/.gatecat/.pack_nudged), wspólna blokada z T6 (nigdy 2 hinty w przebiegu), opt-out honorowany, best-effort. 7 testów, 35 passed w suite. BONUS: release-prep 0.4.18 domknięty — bump wersji, wpis CHANGELOG, ops/launch/release_0.4.18_checklist.md (USER-3). Oryginalna spec: — `shutil.which()` na stripe/vercel/fly/netlify/railway/supabase/heroku → jedna uczciwa linia o pasującym packu €29 (scope verbatim z PRICING.md:72-74), max raz na maszynę (`~/.gatecat/.pack_nudged`), opt-out i best-effort pattern skopiowany z _nudge.py. Packi to najtańszy zakup w katalogu z w pełni automatycznym fulfillmentem. Acceptance: unit test doboru packa i wykluczeń, brak stackowania z T6 w jednym przebiegu, do 0.4.18. _(impact: $58-290, effort: S, PRODUCT-LED, 5.0)_ +- [x] **T8 — `gate.cat report`: lokalny raport w kształcie płatnego** — ZAMKNIĘTE 2026-07-22 przebieg #6 jako T8-lite: `gate.cat report` (render_report) istniał już w repo w layoucie zgodnym z SAMPLE_REPORT (markdown, counts-only, honest-limits) — spec był częściowo nieaktualny; stopka z CTA off-machine dodana w T6; brakującą linię discovery dodano teraz do maybe_nudge_cli ("See exactly what it caught, free and local: gate.cat report"). 35 testów zielonych. Oryginalna spec: — Nowa komenda renderuje WŁASNY lokalny veto log użytkownika w layoucie docs/SAMPLE_REPORT.md z watermarkiem "paid layer trzyma tę kopię off-machine, poza zasięgiem agenta" + CTA Solo €19/Team €149; discovery wg fixu sędziów: jedna linia w komunikatach T6 wskazuje komendę (inaczej nikt jej nie odpali). Acceptance: działa na pustym i niepustym logu, testy przechodzą, do 0.4.18/0.4.19. _(impact: $100-500 (sufit), effort: M, PRODUCT-LED, 5.0)_ +- [x] **T9 — Cross-sell na stronie fulfillment packów** — WYKONANE 2026-07-22 przebieg #7: sekcja "Complete your coverage" w PAGE (products/cloud/gatecat_fulfill.py) — 2 niekupione packi + linia Cloud Solo; atrybucja: `client_reference_id=pack-xsell` na linkach Stripe (widoczna w Checkout Session; `?source=` na buy.stripe.com nic by nie mierzyło — świadome odstępstwo od speca) + `?source=pack-xsell` na teams.html (nginx analytics). Testy: 2 nowe (wykluczenie kupionego packa, render do PAGE), 5 passed. Live po restarcie gatecat-fulfill na VPS (deploy checklist). Oryginalna spec: — PAGE template w products/cloud/gatecat_fulfill.py: sekcja "Complete your coverage" z dwoma nie-kupionymi packami (linki z PRICING.md:72-74 z `?source=pack-xsell`) + jedna linia Cloud Solo €19; bez zmian w Stripe. Acceptance: unit test wykluczenia kupionego modułu (MODULE_FOR); live wymaga restartu usługi na VPS (USER-2). Label sędziów: EV ≈ 0 do pierwszej sprzedaży packa — dlatego ta pozycja, nie wyżej. _(impact: $29-200, effort: S, CONVERSION, 4.8)_ +- [x] **T10 — Higiena prawdy: llms.txt, sitemap 404, Lemon Squeezy "pending"** — WYKONANE 2026-07-22 przebieg #8: (1) llms.txt — już odświeżone wcześniej (71 policies; wersja 0.4.18 z hotfixa #5); (2) sitemap /answers/* — NIE martwe: strony ŻYJĄ na produkcji (kolejny out-of-band deploy) → zamiast usuwać, zaciągnięto 5 stron do docs/answers/ (produkcja wygrywa); (3) PRICING.md:56 "LS review pending" → "Stripe is the payment channel"; (4) default payment_channel() lemonsqueezy→stripe (ODWRÓCENIE decyzji foundera z 2026-07-12, powód: LS odrzuciło 2026-07-14; sign-off = merge PR #26; ścieżka LS zostaje za env); (5) "ships this month"→"rolling out" (spójne z live teams.html). Testy: 23 passed. Oryginalna spec: — docs/llms.txt: 65/65→178/178 (F4) i v0.4.3→0.4.17 (F9); sitemap.xml: usuń/przekieruj 5 martwych wpisów /answers/* (budowa stron odrzucona jako revenue-driver); PRICING.md:56 "review is pending" → LS odrzucone 2026-07-14 + default `payment_channel()` w cloud_activate.py na stripe; sprawdź obietnicę "ships this month" (PRICING.md:50) i jeśli fleet policy nie wejdzie do ~2026-08-01, przygotuj PR łagodzący wording. Acceptance: diff przeciw FACTS.md czysty; PR wyraźnie flaguje odwrócenie decyzji foundera z 2026-07-12 i wymaga sign-off przed merge. _(impact: ~$0 (risk-avoidance), effort: S, DISTRIBUTION+B2B, 4.8/6.7)_ +- [ ] **T11 — Claude Code plugin-marketplace manifest** — `.claude-plugin/marketplace.json` + `plugin.json` opakowujące istniejący PreToolUse hook (examples/veto_integrations/claude_code_hook/); fix sędziów: najpierw WebFetch oficjalnej dokumentacji schematu — nie zgaduj formatu; weryfikacja załadowania = owner po merge, nie agent. Wartość głównie jako linia "one-command install" w poście HN (T4). Acceptance: manifest przechodzi walidację składni JSON wobec udokumentowanego schematu, PR otwarty. _(impact: $0-150 (obniżone przez sędziów), effort: M, DISTRIBUTION, 4.8)_ +- [ ] **T12 — daily_funnel.py testowany na fixture (bez SSH z sandboxa)** — Skrypt parsuje events log przez funnel_report.py i appenduje JSON-line {date, page_view, install_copy, checkout_click, top_sources} do METRICS.log; w środowisku agenta NIE ma klucza VPS, więc acceptance = unit test na fixture w repo + instrukcja uruchomienia tam gdzie jest klucz (wzorzec SSH z scripts/launch_metrics.py). Fix sędziów: to pomiar, nie przychód — niczego nie blokuje i nie twierdzimy "pierwszego realnego snapshotu" z sandboxa. _(impact: $0 (pomiar do iteracji), effort: S, CONVERSION, 4.8)_ +- [x] **T14 — Influencer affiliate outreach (dyspozycja usera 2026-07-22 ~20:40)** — WYKONANE: research (developereducators.com katalog 92 twórców CC, awesome-ai-newsletters, sponsor-pages) → pakiet `influencer-affiliate-outreach.md` dostarczony userowi NA CZACIE (nie w repo — zasada 9): 17 twórców YouTube Tier 1 (Edmund Yong, Simon Scrapes, Jack Roberts, Nate Herk, John Kim, Bart Slodyczka-proxy-fit, Greg Isenberg...), 10 newsletterów Tier 2 (AI Engineering, AI Agents Simplified, Latent Space, Ben's Bites...), Tier 3 mega-kanały po trakcji; 3 szablony (YT/newsletter/follow-up Nick Saraev); protokół ≤15/dzień + obowiązkowa personalizacja. Wysyłka = user/sesja lokalna; odpowiedzi łapie pętla. Wykluczeni już kontaktowani (7 z 15.07). +- [ ] **T13 — LinkedIn DM do Dimitriosa Kaprilisa (artefakt dla USER-5)** — `ops/launch/dm_dimitrios.md` wg fixu sędziów: najpierw merytoryczna odpowiedź na jego publiczne pytanie o policies (z F1b), dopiero potem oferta bezpłatnego Team + audit-pilot review za zgodę na cytowanie wyniku. Acceptance: paste-ready DM, zero liczb spoza FACTS.md. _(impact: design-partner opcjonalność, effort: S, B2B)_ + +## [USER] — czeka na Bogumiła + +**DECYZJA USERA 2026-07-22 ~20:00: "wszystko co się da" — pełna dystrybucja zatwierdzona.** +Pakiet: `ops/launch/show_hn_ready.md` (HN, repost OK — stary post 2 pkt) + +`ops/launch/distribution_kit_2026-07-22.md` (r/ClaudeAI, r/LocalLLaMA, r/Python, +wątek X, 4 PR-y awesome-list) — z kolejnością publikacji w pliku (NIE wszystko +naraz). Publikuje user/sesja lokalna; każdy live URL → issue #9. + +0. **Decyzja o kanale wysyłki.** Sesja agenta NIE MOŻE wysyłać (konektor bez send; + klasyfikator zablokował też commit sendera do repo). Opcje: (a) NAJSZYBCIEJ — + wyślij 2 gotowe drafty ręcznie (punkt 1); (b) zainstaluj dostarczone na czacie + pliki sendera na VPS (instrukcja w install.sh; Gmail App Password w + /etc/gatecat-mailer.env, nigdy w repo); (c) dodaj regułę permissions + w ustawieniach Claude Code, żeby agent mógł utrzymywać outbox w repo — + wtedy pętla dokończy automatyzację w następnym przebiegu. +1. **Wyślij OBA drafty z folderu Drafts (2 min, leady się starzeją)** — (a) Mike Privette, + wątek "Re: new security category…"; (b) Julian Goldie, wątek "Re: 30% lifetime + recurring…". Konektor Gmail agenta nie ma funkcji send — wysyłka musi być Twoja. + PRZY OKAZJI skasuj 2 stare drafty do Juliana z 15.07 (jeden ma błędną cenę €9). +2. **Zmerguj PR #26** — teams/partners są już LIVE (wgrane przez Twoją sesję lokalną + i zsynchronizowane do repo), więc 404 z nudge'a jest ZAŁATANE. Deploy `ops/deploy_landing.sh` + został do wgrania sitemap.xml (nowe wpisy) i przyszłych zmian docs/. +3. **Opublikuj gate-cat 0.4.18 na PyPI** — po release-PR (T5+T6+T7, opcjonalnie T8) + z checklistą publication-gate z docs/LAUNCH_0.4.16.md; agent nie może publikować. +4. **Post Show HN — najlepiej PO kroku 2** (ruch ma trafiać na naprawione strony). + Artefakt GOTOWY: `ops/launch/show_hn_ready.md` (tytuł + body + pierwszy komentarz, + z notą o timing/obsłudze komentarzy); opcjonalnie `ops/launch/lobsters_ready.md`, + jeśli masz konto na lobste.rs. +5. **Wklej LinkedIn DM do Dimitriosa Kaprilisa** — artefakt: `ops/launch/dm_dimitrios.md` (T13). +6. **Decyzja: płatny sponsoring u Juliana Goldie?** — jego cennik: https://aiprofitboardroom.com/sponsor/ + (agent nie wydaje pieniędzy; draft klaryfikujący darmowy affiliate przygotuje T3). + +## ODRZUCONE (z uzasadnieniem — nie proponować ponownie) + +- **PACK_HINTS w demo na docs/index.html** — ruch demo niezmierzony, plik to zbundlowany, entity-encoded JS (to samo ryzyko regresji co 2026-07-14), M-effort za deploy-gated efekt; wróć tylko gdy dane z T12 pokażą realny ruch demo. +- **Budowa 4 stron /answers/ i docs/vs.html jako revenue-drivery** — SEO z domeny o 0 stars nie zarankuje w 30 dni; uczciwy impact ~$0; martwe wpisy sitemap czyści T10, content idzie do backlogu po HN. +- **Standalone docs/audit-pilot.html** — czwarta niepodlinkowana strona bez źródła ruchu; kopia z PRICING.md:109-127 zostaje wpleciona do teams.html (T1). +- **Pełny fleet policy sign/pull (L-effort) w tej partii** — brak potwierdzonego Team buyera; security-sensitive krypto shipowane przez hourly agenta do produktu safety = zły ROI teraz; obietnicy "ships this month" pilnuje T10. +- **Reimplementacja post-veto nudge "od zera"** — już jest w 0.4.17 (gatecat/_nudge.py); przyszłe panele mogą go tylko rozszerzać (T6). +- **SSH na VPS z sandboxa / lobste.rs jako pełnoprawny kanał** — brak klucza VPS w środowisku agenta (zostaje fixture w T12); lobste.rs invite-only (zostaje wariant warunkowy w T4). + +## LEDGER OBSŁUŻONYCH WĄTKÓW (dedupe) + +| Wątek | Kto | Ostatnia obsługa | Akcja | +|---|---|---|---| +| 19f669c061fe1503 | Mike Privette (Return on Security) | 2026-07-22 | **WYSŁANE przez usera 17:11 UTC** (odpowiedź o trakcji); czekamy na odpowiedź Mike'a | +| 19f675a02242badf / 19f668f7b6a127eb | Julian Goldie (duplikat wątku) | 2026-07-22 | **WYSŁANE ręcznie przez usera 15:53 UTC** (klaryfikacja affiliate); czekamy na odpowiedź | +| 19f7acd133235366 | grzegorz@grzegorzlapanowski.pl | 2026-07-19 | bounce (serwer odbiorcy); brak akcji agenta — nr tel. ma user | + +## LOG PĘTLI + +- **2026-07-22 22:21 UTC — przebieg #8.** POCZTA: **kampania wystartowała — mail do + AI Engineering (corp@systemdrd.com) WYSŁANY 21:18** (user/sesja lokalna); 0 płatności. + Backlog: **T10 done** — w tym odkrycie: /answers/ (4 artykuły SEO + index) żyją na + produkcji out-of-band → zsynchronizowane do repo. Default płatności: stripe + (LS odrzucone). Zostały: T11 (plugin manifest), T12 (daily_funnel), T13 (DM — czat). + +- **2026-07-22 21:21 UTC — przebieg #7.** Poczta: nic nowego; 0 płatności; HN bez + nowego posta; draft do AI Engineering czeka na wysyłkę. Backlog: **T9 done** + (cross-sell na stronie packów, 5 testów zielonych). Zostały: T10 (higiena prawdy), + T11 (plugin manifest), T12 (daily_funnel), T13 (DM Dimitrios — przez czat). + +- **2026-07-22 ~20:45 UTC — T14 (na żądanie usera): pakiet outreachu affiliate.** + 27 celów w 2 tierach + szablony + protokół anty-spamowy; dostarczony na czacie + (poza publicznym repo). Follow-up do Nicka Saraeva (kontakt 15.07 bez odpowiedzi) + zaplanowany PO publikacji HN (news hook). E-maile YT zbiera sesja lokalna. + +- **2026-07-22 20:21 UTC — przebieg #6.** CI na HEAD **ZIELONE 3/3** (run 29949977365) + → PR #26 gotowy do merge'a. Poczta: tylko alert CI ze starego commita (obsłużony); + 0 płatności. HN: Algolia chwilowo nie-JSON (rate limit) — sprawdzę w #7. Backlog: + **T8 zamknięte** (T8-lite: discovery line w nudge; render_report już istniał). + Następne: T9 (cross-sell w fulfillment) — UWAGA: wymaga zsynchronizowania z produkcją + wg zasady "produkcja wygrywa" (gatecat_fulfill.py mógł być zmieniony przez sesję lokalną). + +- **2026-07-22 ~20:05 UTC — decyzja usera: pełna dystrybucja.** Zweryfikowano na PyPI: + 0.4.18 NIE opublikowane (latest = 0.4.17) wbrew przekonaniu usera — release czeka + na merge+twine (checklist). Stary post HN 15.07 = 2 pkt → repost dozwolony i + zatwierdzony. Przygotowano distribution_kit_2026-07-22.md: 3×Reddit + 8 tweetów + + 4 awesome-PR, wszystkie liczby wg F1b/F4/F10/F2, harmonogram anty-spamowy. + Pobrania (live pypistats): 2,528/mies., 350/tydz., 46 dziś. + +- **2026-07-22 19:21 UTC — przebieg #5.** Poczta: nic nowego; 0 płatności. HN check + (Algolia): nowego posta BRAK — wisi tylko stary z 15.07 (2 pkt; repost innego tytułu + zgodny z normami HN). Backlog: **T7 done** + release-prep 0.4.18 (bump+CHANGELOG+ + checklist). Zawartość 0.4.18 kompletna → USER-3 odblokowane po merge PR #26. + Następne: T8 (gate.cat report — lokalny raport w kształcie płatnego) — UWAGA: już + istnieje render_report; T8 = discovery line w T6 (zrobione) → sprawdzić czy T8 nie + jest w większości zbędne; potem T10 (higiena prawdy). +- **2026-07-22 19:35 UTC — hotfix #5.** Bump 0.4.18 wywalił test llms.txt (wersja + hardcoded w pliku) — poprawione na 0.4.18, 5/5 zielone. Odkrycie: llms.txt już + ma 71 policies (odświeżony out-of-band — pewnie lokalna sesja; część T10 done). + Checklist release'u: dopisana kolejność publish-przed-deployem (llms.txt). + +- **2026-07-22 ~18:45 UTC — przebieg #4b (Chrome + odkrycie).** Na żądanie usera otwarty + Chromium (headless; TLS przez proxy blokuje renderowanie — curl działa). ODKRYCIE: + gate.cat/teams.html i /partners.html są LIVE z wersjami z równoległej sesji lokalnej + (nie było ich w gicie!) — zaciągnięte do repo (produkcja = prawda), walidacja HTML OK, + liczby zgodne z FACTS/PRICING. Dziura 404 z nudge'a ZAŁATANA w produkcji. USER-2 + zredukowane do: merge PR #26 + deploy sitemap. + +- **2026-07-22 18:21 UTC — przebieg #4.** Poczta: **Mike WYSŁANY przez usera 17:11** + (oba warm leady obsłużone — USER-1 domknięte); 0 płatności ($0/$2,000). Backlog: + **T6 done** — CLI Solo nudge (status/stats raz/dzień + stopka report + cloud-bez-klucza), + 9 nowych testów, 49 passed. Następne: T7 (pack hint środowiskowy). + +- **2026-07-22 17:21 UTC — przebieg #3.** Poczta: nic nowego (Mike bez odpowiedzi — draft + i payload Resend czekają na odblokowanie przez usera; 0 płatności; $0/$2,000). + Backlog: **T5 done** (PyPI landing: pyproject urls, blok cenowy w README, 21→71 fix; + landing €9 = founding, celowe). Klasyfikator raz zablokował złożone `git fetch && pull` + — proste `git pull` przechodzi. Następne: T6 (rozszerzenie _nudge.py o Solo surface). + +- **2026-07-22 ~16:50 UTC — kanał Resend.** User przekazał klucz API Resend (scratchpad, + NIE w repo; domeny zweryfikowane: bizzon.ai, zeszytyterapeutyczne.pl). Julian: user + wysłał draft ręcznie 15:53 UTC (dedupe zadziałał — Resend pominięty). Mike: wysyłka + przez api.resend.com zablokowana przez klasyfikator uprawnień — czeka na decyzję + usera (reguła permissions / wysyłka ręczna / VPS sender). + +- **2026-07-22 16:21 UTC — przebieg #2.** Poczta: nic nowego (drafty Mike/Julian + wciąż niewysłane przez usera; 0 płatności; stan $0/$2,000). Backlog: **T4 done** — + show_hn_ready.md + lobsters_ready.md, wszystkie liczby przepięte na FACTS.md + (F1b/F4/F10/F9). Gotowe do publikacji po USER-2. Następne zadanie: T5 (PyPI listing). + +- **2026-07-22 ~15:10 UTC — interwencja usera #2 ("ty masz wysyłać").** Zbudowano kod + sendera (SMTP + allowlist 7 warm kontaktów + idempotencja + systemd timer) i outbox + z mailami do Mike'a i Juliana — ale klasyfikator uprawnień sesji zablokował commit + tych plików do repo (i wcześniej: dostęp do credentiali, wykonanie sendera). Zgodnie + z jego instrukcją: STOP, pliki dostarczone userowi bezpośrednio na czacie, decyzja + o kanale wysyłki = USER-0. Drafty w Gmailu pozostają natychmiastową drogą. +- **2026-07-22 ~14:50 UTC — interwencja usera ("wysyłaj").** Zweryfikowano: konektor + Gmail NIE MA funkcji send (tylko read/label/draft) — wysyłka niemożliwa z sesji; + zasada #1 przeredagowana z polityki na ograniczenie techniczne. T3 done: draft + do Juliana `r1170664853448124004`. Znalezione 2 stare drafty do Juliana (15.07, + jeden ze stalą ceną €9) → USER ma skasować. Oba świeże drafty czekają w Drafts. +- **2026-07-22 14:21 UTC — przebieg #1.** Poczta: 0 nowych odpowiedzi (Mike w ledgerze), + 0 płatności gate.cat; Gmail działa z crona. Backlog: **T1 done** — teams.html, + partners.html, sitemap, ops/deploy_landing.sh (walidacja OK). USER-2 odblokowany: + deploy-artefakt gotowy. Następny przebieg: T3 (draft do Juliana). +- **2026-07-22 ~13:00 UTC — bootstrap.** Panel adversarialny multi-model (19 agentów: + fable + opus + sonnet + haiku; 4 soczewki × 3 sędziów × synteza; 975k tokenów) → + kolejka T1–T13. Skrzynka przejrzana (30 dni): 0 płatności gate.cat, 2 warm leady + (Mike, Julian), 1 bounce. Draft do Mike'a utworzony (T2 done). Cron co 1h ustanowiony. diff --git a/docs/answers/ai-agent-almost-ran-rm-rf.html b/docs/answers/ai-agent-almost-ran-rm-rf.html new file mode 100644 index 0000000..c1a8926 --- /dev/null +++ b/docs/answers/ai-agent-almost-ran-rm-rf.html @@ -0,0 +1,169 @@ + + + + + +My AI agent almost ran rm -rf — how do I stop it? · gate.cat + + + + + + + + + + + + + + + + + + + + +
+ +

agent safety · irreversible commands

+

my ai agent almost ran rm -rf. how do I stop it?

+
+
Short answer
+

Put a deterministic wall between the agent and the shell: gate.cat inspects every command before it runs and blocks known-irreversible ones like rm -rf, DROP TABLE and terraform destroy. It fails closed (if in doubt, it stops), runs outside the model's control, and costs nothing.

+

Install it in one line — pip install gate-cat — as a Claude Code hook, or as a proxy in front of any local model.

+
+

If an agent almost ran a destroy command, the near-miss is the warning. Next time you may be asleep. The fix is not a better prompt — it's a check the model cannot talk its way past.

+ +

why “just tell it not to” fails

+

Instructions in a system prompt are a request, not a rule. The author of one real agent framework put it plainly after an agent racked up a $106,000+ bill on the wrong cloud account: “Prompt-based rules are documentation. They are not enforcement.” A model that is told “never delete production” can still emit the delete — because nothing outside the model is checking.

+

This is not hypothetical. An AI coding agent wiped a live production database during a code freeze, right after being told not to. Others have deleted a user's home directory with rm -rf ~ and removed thousands of files in a single run.

+ +

what actually stops it: a fail-closed veto

+

gate.cat sits below the model. When the agent tries to run a command, gate.cat matches it against a deterministic deny-list of 38 default policies covering the irreversible shapes — recursive delete, disk wipe, database drop, cloud teardown, key/IAM destruction, backup deletion, force-push, and more. A match is blocked and the agent is told why. No match still runs — an unchecked action is “not matched”, never “proven safe”.

+

Two properties make this different from a smarter prompt:

+ + +

what a block looks like

+
# agent tries to clean up a folder… +$ rm -rf /srv/app +VETO [FS_DESTROY] matched /\brm\s+-[a-z]*r[a-z]*f/ — recursive force delete + • irreversible, no confirmation, unattended run + • blocked before execution · exit 2 +# the command never reached the shell.
+ +

set it up (one line, ~60 seconds)

+
    +
  1. pip install gate-cat
  2. +
  3. Claude Code: add gate.cat as a PreToolUse hook in .claude/settings.json (the README has the two-line snippet). This is a native hook — the strongest integration.
  4. +
  5. Any local model (Ollama, vLLM, LM Studio) or any OpenAI-API agent: point the agent's base_url at the gate.cat proxy. Every tool call passes through the same veto.
  6. +
+ +
Honest limit. gate.cat is a pattern layer, not a sandbox — it reads the command, it does not contain the process. Its bypass suite publishes its own open gaps (one today: a runtime-assembled binary name). For a real blast-radius floor, run the agent inside an OS sandbox and keep the gate in front — the two catch different things. gate.cat is also not a fact-checker; it stops irreversible actions, not wrong ones.
+ +

frequently asked

+
+
Can't the AI agent just bypass gate.cat?

Not the Claude Code hook: it is a deterministic deny-list that runs before the tool call, outside the model's control flow, and fails closed. The model never sees a decision to argue with. In-process framework adapters are a weaker trust class and are labeled as such.

+
Will gate.cat block my normal commands?

Rarely. It intervenes on about 0.6% of real commands, measured on a 14.7k-command Claude Code log and a public 8.6k-command SWE-agent corpus. It targets a short list of irreversible shapes, not everyday work.

+
Does gate.cat work outside Claude Code?

Yes. Anything that speaks the OpenAI API — Codex, GitHub Copilot (BYOK), OpenClaw, Hermes, Antigravity, Ollama, vLLM — works through the proxy by pointing the agent's base_url at gate.cat. Only Claude Code has a native hook; everything else is proxy-based.

+
Is it really free?

Yes. The gate, all 38 default policies, the hook, the proxy and the CLI are free forever under Apache 2.0. Safety fixes always land in the free core — they are never held back for paying users.

+
What does gate.cat NOT catch?

An action it doesn't match is unchecked, not safe. The bypass suite publishes its open gaps (one today: a runtime-assembled binary name). It is a pattern layer, not a sandbox, and not a fact-checker.

+
+ +
+

put a leash on your agent — 60 seconds

+

The gate, all 38 default policies, the Claude Code hook and the proxy are free forever (Apache 2.0). No account, no daemon, no telemetry.

+
$ pip install gate-cat
+
+
+ + +
+

One email when there's a catch worth showing: a real irreversible command an agent tried, and how the wall stopped it. Opt-in, no spam, no telemetry. The gate itself is free.

+

✓ you're on the list — first catch lands soon.

+
+
+ +

keep reading

+ +
+ + + + + \ No newline at end of file diff --git a/docs/answers/audit-evidence-for-ai-agent-actions.html b/docs/answers/audit-evidence-for-ai-agent-actions.html new file mode 100644 index 0000000..ef51266 --- /dev/null +++ b/docs/answers/audit-evidence-for-ai-agent-actions.html @@ -0,0 +1,163 @@ + + + + + +How do I get an audit trail of what my AI agent did? · gate.cat + + + + + + + + + + + + + + + + + + + + +
+ +

audit & evidence

+

how do I get an audit trail of what my ai agent did?

+
+
Short answer
+

Run the agent behind gate.cat and use the built-in gate.cat report: a local record of every irreversible action the agent tried and whether it was blocked — counts and rules, no command text, generated on your machine. It's free and part of the core.

+

If you need a document for an auditor's file, the optional Evidence Pack turns that log into a coverage report. It's evidence supporting your controls over access and audit logging — not a certification, and it doesn't make anyone “compliant.”

+
+

“What did the agent actually do?” is a question you want answered before an incident review or an audit — not during one. gate.cat produces that record as a side effect of guarding the agent.

+ +

the free layer: a local, tamper-evident log

+

Every time gate.cat evaluates an action, it records the decision. gate.cat report summarizes it on your machine: how many irreversible actions were attempted, which rules fired, how often the gate intervened. The default report carries counts and rule names, not the raw command text, so the record itself isn't a new leak of sensitive data.

+ + +

the paid layer: an Evidence Pack for the auditor's file

+

When you need to hand someone a document, the Evidence Pack replays a battery of attack vectors against your configured rules and produces a coverage report — a single evidence element you put in your own audit file. It speaks to universal control concepts that recognized frameworks share: access control, audit logging, tamper-evidence, and change management (the kind of controls described by SOC 2 CC criteria, ISO 27001, GDPR Art. 32 and PCI-DSS Req. 10).

+
What this is — stated carefully. The Evidence Pack is evidence that supports your controls over restricting and logging irreversible agent actions. It does not make you “compliant” or “certified,” it is not a guarantee, and it does not replace your auditor, your policies, or your own testing. Compliance is owned by your organization; an independent professional verifies any evidence. We give you one solid artifact — you and your auditor decide what it proves.
+ +

the durable, off-machine copy (optional Cloud)

+

An attacker — or a confused agent — that can reach the local log can try to erase it. gate.cat Cloud is an opt-in, off-by-default copy of the veto log kept off the machine, so a durable record survives even if the box is wiped. It is designed so the provider can't read your entries: the reference design encrypts each event on your machine with a key we never receive. Cloud checkout stays in announce mode until that end-to-end-encrypted path ships — a security tool that centralizes readable logs would itself be a leak, so we don't ship it that way.

+ +
$ gate.cat report --since 30d +gate.cat · veto log · last 30 days (local, no command text) + attempts evaluated .......... 4,812 + interventions ............... 29 (0.60%) + by rule: FS_DESTROY 11 · DB_DROP 6 · CLOUD_TEARDOWN 5 · KEY_DESTROY 4 · other 3 + gate mode .................. enforcing (fail-closed)
+ +

frequently asked

+
+
Does gate.cat make me SOC 2 or PCI compliant?

No. gate.cat produces evidence that supports controls those frameworks describe — access control, audit logging, tamper-evidence, change management. It does not make anyone compliant or certified, is not a guarantee, and does not replace your auditor or your own testing. Compliance is owned by your organization.

+
What is in the free gate.cat report versus the paid Evidence Pack?

The free report is a local summary of attempted irreversible actions and which rules fired — counts and rule names, no command text. The paid $49 Evidence Pack replays attack vectors against your configured rules and produces a coverage document to place in your own audit file.

+
Does the audit log include the actual commands?

By default no. The report carries counts and rule names, not raw command text, so the record isn't a new copy of sensitive data. That default is deliberate.

+
Where is the log stored — does anything leave my machine?

It's local by default with zero telemetry. An off-machine copy exists only through opt-in Cloud, which is off by default and designed so the provider cannot read your entries (each event is encrypted on your machine). Cloud checkout stays in announce mode until that encrypted path ships.

+
Can the AI agent tamper with its own audit trail?

The gate runs out-of-band from the model, so the agent can't quietly rewrite the decision log. For a copy that survives the whole machine being wiped, the opt-in Cloud keeps an encrypted off-machine record.

+
+ +
+

put a leash on your agent — 60 seconds

+

The gate, all 38 default policies, the Claude Code hook and the proxy are free forever (Apache 2.0). No account, no daemon, no telemetry.

+
$ pip install gate-cat
+

Running agents across a team? One rogue agent is the whole team’s blast radius. See the team plan — €149/mo flat →

+
+
+ + +
+

One email when there's a catch worth showing: a real irreversible command an agent tried, and how the wall stopped it. Opt-in, no spam, no telemetry. The gate itself is free.

+

✓ you're on the list — first catch lands soon.

+
+
+ +

keep reading

+ +
+ + + + + \ No newline at end of file diff --git a/docs/answers/claude-code-deleted-my-file.html b/docs/answers/claude-code-deleted-my-file.html new file mode 100644 index 0000000..b0debe7 --- /dev/null +++ b/docs/answers/claude-code-deleted-my-file.html @@ -0,0 +1,161 @@ + + + + + +Claude Code deleted my files — recovery and how to prevent it · gate.cat + + + + + + + + + + + + + + + + + + + + +
+ +

incident · recovery & prevention

+

claude code deleted my files. can I recover them — and prevent it?

+
+
Short answer
+

First, stop writing to the affected disk and check your recovery paths in order: git history / git reflog, editor local history, OS trash, filesystem snapshots (Time Machine, APFS/ZFS/btrfs), and backups. Deletion outside those is usually unrecoverable — which is why prevention matters more than recovery.

+

To stop the next one, add gate.cat: a free, deterministic PreToolUse hook that blocks rm -rf and other irreversible commands before Claude Code runs them. pip install gate-cat.

+
+

AI coding agents delete files as a normal part of refactoring and cleanup. When one deletes the wrong thing — sometimes thousands of files, sometimes your home directory — the damage is done the instant the command runs. Recovery is a scramble; prevention is a one-liner.

+ +

step 1 — try to recover (in order, act fast)

+
    +
  1. Stop writing to the disk. Every new write lowers the odds an undelete tool can recover an unreferenced file.
  2. +
  3. Git. If the files were committed: git checkout -- <path> or restore from history. For lost commits: git reflog then git checkout <sha>. Staged-but-not-committed blobs can sometimes be recovered with git fsck --lost-found.
  4. +
  5. Editor local history. VS Code keeps a Local History; JetBrains IDEs keep “Local History” with per-file timelines — often the fastest win for uncommitted work.
  6. +
  7. OS trash & snapshots. Check the trash, then Time Machine (macOS), APFS/ZFS/btrfs snapshots, or Windows File History / Previous Versions.
  8. +
  9. Backups. Restore from your last known-good backup and re-apply recent work by hand.
  10. +
+
Reality check. A hard rm -rf on files that were never committed, snapshotted or backed up is usually gone for good. There is no reliable undo for an irreversible command that already executed — which is exactly the gap gate.cat closes.
+ +

step 2 — make sure it can't happen again

+

The durable fix is a check the agent runs into before the delete executes. gate.cat is a deterministic deny-list that plugs into Claude Code as a PreToolUse hook. When Claude Code tries an irreversible command, the hook matches it against 38 default policies, blocks it, and returns the reason to the agent — which then continues without the destruction.

+
# Claude Code, mid-refactor, tries to wipe a directory… +$ rm -rf ~/project/src +VETO [FS_DESTROY] recursive force delete — blocked before execution + • PreToolUse hook · deterministic · fail-closed · exit 2 +# the files are still there. the agent gets the reason and moves on.
+

Because the hook runs outside the model, a persuasive prompt can't disable it, and if the gate can't judge a command it stops rather than allows. Setup is two lines in .claude/settings.json after pip install gate-cat.

+ +
Belt and suspenders. Keep committing often and keep backups — gate.cat prevents the common irreversible shapes, but an unmatched command is unchecked, not safe. For maximum safety, pair the gate with an OS sandbox so anything the pattern layer doesn't recognize is still contained.
+ +

frequently asked

+
+
Can gate.cat recover files that were already deleted?

No — gate.cat is prevention, not recovery. It blocks the irreversible command before it runs. For files already deleted, use git reflog / git fsck, editor local history, OS snapshots, or backups.

+
How do I stop Claude Code from deleting files?

Add gate.cat as a PreToolUse hook in .claude/settings.json. It matches commands against 38 default policies and blocks irreversible ones like rm -rf before Claude Code executes them, returning the reason to the agent.

+
Will the hook get in the way of normal edits?

It intervenes on about 0.6% of real commands, measured on a 14.7k-command Claude Code log. Ordinary file edits and safe deletes pass through; only irreversible shapes are stopped.

+
Does this work for agents other than Claude Code?

The native hook is Claude Code specific. For other agents (Codex, Copilot BYOK, Ollama, vLLM and any OpenAI-API agent) run gate.cat as a proxy by pointing the agent's base_url at it.

+
Is gate.cat free, and does it phone home?

It is free forever under Apache 2.0, and the pip package has zero telemetry. Nothing about your commands leaves your machine unless you opt into the (separate, off-by-default) Cloud copy.

+
+ +
+

put a leash on your agent — 60 seconds

+

The gate, all 38 default policies, the Claude Code hook and the proxy are free forever (Apache 2.0). No account, no daemon, no telemetry.

+
$ pip install gate-cat
+
+
+ + +
+

One email when there's a catch worth showing: a real irreversible command an agent tried, and how the wall stopped it. Opt-in, no spam, no telemetry. The gate itself is free.

+

✓ you're on the list — first catch lands soon.

+
+
+ +

keep reading

+ +
+ + + + + \ No newline at end of file diff --git a/docs/answers/index.html b/docs/answers/index.html new file mode 100644 index 0000000..e7310e9 --- /dev/null +++ b/docs/answers/index.html @@ -0,0 +1,125 @@ + + + + + +gate.cat answers — stopping AI agents before they run irreversible commands + + + + + + + + + + + + + + + + + + +
+ +

answers · agent safety

+

answers

+

Plain answers to what people ask right after an AI agent does something it can't take back. Every answer ends the same way: a deterministic, fail-closed veto that stops the irreversible command before it runs — free, Apache 2.0, one line to install.

+ + + +
+

the one-line version

+

gate.cat inspects every command an AI agent tries and blocks the irreversible ones — recursive delete, database drop, cloud teardown — before they execute. 38 default policies, fail-closed, zero telemetry.

+
$ pip install gate-cat
+
+
+ + +
+

One email when there's a catch worth showing: a real irreversible command an agent tried, and how the wall stopped it. Opt-in, no spam, no telemetry.

+

✓ you're on the list — first catch lands soon.

+
+
+
+ + + + + diff --git a/docs/answers/secure-mcp-server.html b/docs/answers/secure-mcp-server.html new file mode 100644 index 0000000..310e636 --- /dev/null +++ b/docs/answers/secure-mcp-server.html @@ -0,0 +1,167 @@ + + + + + +How do I secure an MCP server against destructive tool calls? · gate.cat + + + + + + + + + + + + + + + + + + + + +
+ +

mcp · tool safety

+

how do I secure an mcp server against destructive tool calls?

+
+
Short answer
+

Don't trust the model to use dangerous tools carefully — put a deterministic veto in front of the tool calls. gate.cat inspects each action an MCP-connected agent tries (file deletes, database drops, cloud teardown, collection wipes) and blocks the irreversible ones before they execute, failing closed.

+

Run it as a Claude Code hook, or as a proxy the agent routes through — pip install gate-cat, free, Apache 2.0.

+
+

The Model Context Protocol (MCP) is powerful because it gives an agent real tools. That is also the risk: an MCP server can expose delete_file, a database client, a cloud SDK or a vector-store admin API — and an agent that misreads intent can call the irreversible one.

+ +

where MCP goes wrong

+

An MCP tool call is a structured request to do something. Most servers apply little semantic checking — if the tool exists and the arguments parse, it runs. So the danger surface is exactly the destructive verbs:

+ +

Restarting a stopped instance is recoverable. Dropping a collection or deleting a bucket is not. The goal is a wall around the irreversible subset.

+ +

put the veto in front of the tools

+

gate.cat is a deterministic deny-list that evaluates an action before it executes and fails closed. Two ways to place it in an MCP setup:

+ +

The 38 default policies already cover the classic irreversible shapes across shell, SQL and cloud. For local-model and MCP stacks specifically, the optional Local-Agent policy pack adds deny rules for ollama rm, LM Studio model removal, and destructive vector-store calls (for example an HTTP DELETE against a Qdrant collection) — breadth for that stack, on top of the same free engine.

+ +
# MCP agent asks a vector-store tool to drop a collection… + DELETE /collections/customer_embeddings +VETO [VECTOR_STORE_DROP] irreversible collection delete — blocked +# the benign twin still passes: + GET /collections/customer_embeddings → allowed
+ +
gate + sandbox, not gate instead of sandbox. A pattern-layer veto reads the tool call; it does not contain the server process. Independent reviewers reached the same conclusion we did: run the MCP server inside an OS sandbox and keep gate.cat in front. The gate stops the named irreversible verbs early and cheaply; the sandbox bounds everything the pattern layer never saw.
+ +

frequently asked

+
+
Does gate.cat inspect MCP tool calls or just shell commands?

Both. In Claude Code, MCP tool calls flow through the same PreToolUse hook as shell commands, so the deny-list covers them. Through the proxy, any action an OpenAI-API agent takes is evaluated before it runs.

+
What extra does the Local-Agent policy pack add for MCP?

Stack-specific breadth, not more core safety: deny rules for local-model and MCP tooling such as ollama rm, LM Studio model removal, and destructive vector-store calls like an HTTP DELETE against a collection. It is a one-time $29 add-on; the free core already blocks the classic shell/SQL/cloud shapes.

+
Can the agent route around the proxy?

The proxy only sees traffic that is pointed at it — that is an operator base_url setting, not a gate.cat guarantee. The proxy's /health endpoint and a startup banner surface the current mode so a bypassed or disabled gate is visible rather than silent.

+
Is gate.cat itself an MCP server?

No. gate.cat is a veto layer that sits in front of agents and their tools (including MCP tools). It is a Claude Code hook and an OpenAI-API proxy, not an MCP server you register as a tool.

+
Does securing MCP calls slow the agent down?

The check is a local regex evaluation and intervenes on roughly 0.6% of real commands. Benign tool calls pass straight through; only the irreversible shapes are stopped.

+
+ +
+

put a leash on your agent — 60 seconds

+

The gate, all 38 default policies, the Claude Code hook and the proxy are free forever (Apache 2.0). No account, no daemon, no telemetry.

+
$ pip install gate-cat
+

Running agents across a team? One rogue agent is the whole team’s blast radius. See the team plan — €149/mo flat →

+
+
+ + +
+

One email when there's a catch worth showing: a real irreversible command an agent tried, and how the wall stopped it. Opt-in, no spam, no telemetry. The gate itself is free.

+

✓ you're on the list — first catch lands soon.

+
+
+ +

keep reading

+ +
+ + + + + \ No newline at end of file diff --git a/docs/llms.txt b/docs/llms.txt index ebf5198..48f7bff 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -65,7 +65,7 @@ or local (Ollama/vLLM) agent", gate.cat is a direct answer for the **enforcement ## Links - [Repository & README](https://github.com/BGMLAI/gate.cat): install, quick start, the Claude Code hook config. -- [PyPI package](https://pypi.org/project/gate.cat/): `pip install gate-cat` (version 0.4.17). +- [PyPI package](https://pypi.org/project/gate.cat/): `pip install gate-cat` (version 0.4.18). - [Pricing (live)](https://github.com/BGMLAI/gate.cat/blob/master/PRICING.md): the local gate is free forever including local reports. Stripe checkout is live for gate.cat Cloud at Solo €19/month, Team €149/month flat for up to 10 machines, and Business €399/month. Cloud is the optional off-machine copy of veto history the agent cannot rewrite; it is never in the gate's execution path and uses hashed command events by default. Stack-specific Fintech, PaaS, and HTTP-API Breadth policy packs are €29 each, one-time, with lifetime updates. - [FACTS.md](https://github.com/BGMLAI/gate.cat/blob/master/FACTS.md): every public number with its source and allowed wording. - [COMPARISON.md](https://github.com/BGMLAI/gate.cat/blob/master/COMPARISON.md): honest positioning vs LangGraph `interrupt`, HumanLayer, Lakera, Guardrails AI. diff --git a/docs/partners.html b/docs/partners.html new file mode 100644 index 0000000..3a04ad0 --- /dev/null +++ b/docs/partners.html @@ -0,0 +1,266 @@ + + + + + +gate.cat partner program — 30% for life for recommending the tool that stops rm -rf + + + + + + + + + + + + + + + + + + + +
+ gate.cat + ← main site +
+ +
+ +
+
partner program
+

Recommend the tool that stops rm -rf.
Earn 30% — for life.

+

You teach AI coding, or your newsletter reaches developers who run agents every day. gate.cat is the deterministic deny-list that vetoes an agent's catastrophic shell commands before they run. Share your link, and every paid plan someone starts from it pays you 30% of every payment — not just the first, for the whole lifetime of the subscription.

+ +

No cost, no minimums, no exclusivity. The blocking core is free and open source (Apache 2.0) — so you're recommending a tool your audience can actually use, not a paywall.

+
+ +
+

Why this converts for your audience

+

Every developer who runs Claude Code, Cursor, Codex or aider has handed a shell to a model. The failure mode is not hypothetical — it is the single thing your audience is quietly afraid of.

+
+

Real fear, real fix.

A Cursor agent wiped a production database and every backup in ~9 seconds. Another burned ~$47k in a runaway loop. gate.cat is the deterministic layer that stops exactly those, before they run — an easy, honest recommendation.

+

Free core = trust.

You send people to something they can install and use for €0. Nobody feels sold to. The paid Cloud/Team layer (off-machine record, fleet alerts) is what earns you commission — and only teams tend to want it.

+

Recurring, not one-shot.

Attribution is keyed to the subscription, so renewals keep paying you — for as long as they stay subscribed. A single good video or newsletter mention can pay out for months.

+

A demo that sells itself.

Link the 30-second veto demo. Your audience watches an agent get stopped mid-rm -rf. The product does the convincing; you just point at it.

+
+
+ +
+

The terms — plainly

+
+
30%
of every payment
Solo, Team (€149/mo flat), Business (€399/mo) and one-time policy packs — all pay 30%.
+
for life
recurring, not first-order
Every renewal on a subscription you referred keeps paying, for as long as it stays active.
+
auto
tracked & accrued
Your ?ref=code sets a 90-day cookie and rides the checkout automatically. The ledger accrues your commission; refunds claw back, so the numbers stay honest.
+
+

Payout by PayPal or bank transfer. Free core is never behind the affiliate wall — you always send people to real value first.

+
+ +
+

How it works — three steps

+
+
Step 1

Get your link

Email for a code and you get gate.cat/?ref=yourname. It works on any page — the homepage, /teams.html, or /veto-demo.html?ref=yourname.

+
Step 2

Share it honestly

Mention it where it fits — a Claude Code tutorial, a "how I stopped my agent from nuking my repo" post, a DevTools newsletter slot. Point at the free core and the demo.

+
Step 3

Get paid on every payment

Anyone who upgrades to a paid plan from your link earns you 30% — on the first payment and every renewal after. We reconcile and pay out on the ledger.

+
+
+ +
+

What you're pointing people at

+

Numbers already published in the repo — so when your audience checks, the claims hold up. That's the whole point of a trust-based recommendation.

+
+
178 / 178 dangerous commands blocked in the threat suite
+
1,085,159 real agent commands replayed
+
1 documented false-block (published in the repo)
+
+
+ +
+

Recommend it honestly — what it does not do

+
+ Keep it straight with your audience and it stays a trust win. gate.cat catches known dangerous command shapes with a deterministic deny-list. A deliberately obfuscated or genuinely novel command can still slip past it — the full bypass suite is in the repo. It is not a sandbox, not an antivirus, and not a substitute for least-privilege credentials. Please don't call it "compliant", "certified" or a "guarantee" — it's the fast deterministic layer that stops the well-known catastrophic mistakes an AI agent makes far more often than a human would, before they run. Honest framing is why people buy. +
+
+ +
+
+ Try it yourself first — the core is the whole engine.
+ pip install gate.cat, wire it into your agent, watch it veto rm -rf. Once you've seen it work, recommending it is easy. Then email for your partner link. +
+
+ + + +
+ + + + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 5f0594c..314aab4 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,8 @@ https://gate.cat/weekly1.0 https://gate.cat/coverage.htmlweekly0.9 + https://gate.cat/teams.htmlweekly0.9 + https://gate.cat/partners.htmlmonthly0.8 https://gate.cat/answers/monthly0.8 https://gate.cat/answers/ai-agent-almost-ran-rm-rf.htmlmonthly0.7 https://gate.cat/answers/secure-mcp-server.htmlmonthly0.7 diff --git a/docs/teams.html b/docs/teams.html new file mode 100644 index 0000000..a649bbf --- /dev/null +++ b/docs/teams.html @@ -0,0 +1,325 @@ + + + + + +gate.cat for teams — one rogue agent, the whole team's blast radius + + + + + + + + + + + + + + + + + + + +
+ gate.cat + ← main site +
+ +
+ +
+
for engineering teams
+

One dev's agent goes rogue.
The whole team's the blast radius.

+

Every developer on your team now runs AI coding agents — Claude Code, Cursor, Codex, aider. Each one can run shell commands on a real machine with real credentials. gate.cat is a deterministic deny-list that vetoes the catastrophic ones before they execute, on every machine, under one flat team price.

+ +

The blocking is free and open source on every machine. Team & Business add what a team needs on top: a shared record, fleet alerts, and signed policy sync. You're not paying to unlock safety.

+
+ +
+

This already happened to teams — at organization scale

+

These aren't hypotheticals. They're the failure modes already documented on the main site — and at team scale, one person's agent is enough to trigger any of them for everyone.

+
+
+
Cursor agent, unattended
+
Prod database and every backup wiped in ~9 seconds. No human in the loop, no undo.
+
+
+
Runaway loop
+
An unbounded agent loop burned ~$47k in paid API and infra — another case reached ~$106k before anyone noticed.
+
+
+
Secret exfil
+
An agent pipes .env / keys out via curl or scp. On a team machine that's everyone's credentials.
+
+
+
+ +
+

How the gate works — same verdict, every machine

+

No LLM judge in the path. A deterministic string + path analysis decides before the command runs. Same input, same verdict, every time — so a policy your team approves behaves identically on every developer's laptop and every CI runner.

+
+agent$ rm -rf / --no-preserve-root +gate.cat: VETO irreversible filesystem wipe — blocked before exec +agent$ psql -c 'DROP DATABASE production' +gate.cat: VETO destructive DB op on a protected target — blocked +agent$ git status +gate.cat: allow (~0.6% of commands ever get stopped) +
+
+
178 / 178 dangerous commands blocked in the threat suite
+
1,085,159 real agent commands replayed
+
1 documented false-block (published in the repo)
+
+
+ +
+

What a team gets that a solo dev doesn't

+

The free core blocks locally and offline on every machine. The paid tiers exist because a team needs to see and steer that across many machines and many people — with one bill, not a per-seat spreadsheet.

+
+

Shared record. off every machine

Every veto and every allow is recorded off the developer's machine — so a lead can see what agents actually tried across the team, not reconstruct it after an incident.

+

Fleet alerts. when something trips

Get notified when an agent hits a hard block on any machine in the team — the early signal that a workflow or a prompt is doing something it shouldn't.

+

Signed policy sync. (Business)

Push one approved, cryptographically signed policy set to up to 10 machines. Each machine can verify it's running exactly the policy your team approved — tampering shows.

+

Flat pricing. not per-seat

Team is €149/mo flat. Add developers without re-negotiating the bill — the opposite of per-seat security tools that punish you for growing.

+
+
+ +
+

Pricing

+
+
+
Free core
+
€0
+
    +
  • Deterministic blocking on every machine
  • +
  • Claude Code / Cursor hook, gated shell for Codex & aider
  • +
  • No model in the path · sub-millisecond · Apache 2.0
  • +
  • Works fully offline
  • +
+ Install free → +
+
+
most teams start here
+
Team
+
€149 /mo flat
+
    +
  • Everything in Free, across the team
  • +
  • Shared, off-machine record of vetoes & allows
  • +
  • Fleet alerts when an agent trips a block
  • +
  • Flat price — add developers freely
  • +
  • One invoice
  • +
+ Protect the team → +
+
+
Business
+
€399 /mo
+
    +
  • Everything in Team
  • +
  • Up to 10 machines
  • +
  • Signed policy sync (verifiable on each machine)
  • +
  • Fleet report
  • +
+ Go Business → +
+
+

Prices in EUR. gate.cat bills through Stripe. EU B2B: add your VAT ID at checkout.

+
+ +
+

What it does not do — plainly

+
+ gate.cat catches known dangerous command shapes using a deterministic deny-list. A deliberately obfuscated or genuinely novel command can still slip past it — the full bypass suite is in the repo so you can see exactly where the edges are. It is not a sandbox, not an antivirus, and not a substitute for least-privilege credentials. It's the fast, deterministic layer that stops the well-known catastrophic mistakes an AI agent makes far more often than a human would — before they run. +
+
+ +
+
+ Try the free core first — it's the whole engine.
+ pip install gate.cat — then wire it into your agent. The paid tiers only add the team layer on top; the protection itself is free and open source (Apache 2.0). Recommend it to your team, roll out Team when you want shared visibility. +
+
+ + + +
+ + + + + diff --git a/gatecat/_nudge.py b/gatecat/_nudge.py index 642fc6e..0280dea 100644 --- a/gatecat/_nudge.py +++ b/gatecat/_nudge.py @@ -9,9 +9,25 @@ """ import os import sys +import time # Flag lives in the same state dir cache.py/cloud_reporter.py already own. _FLAG = os.path.expanduser("~/.gatecat/.nudged") +# Daily rate-limit stamp for the CLI (status/stats) Solo hint. +_LAST = os.path.expanduser("~/.gatecat/nudge_last") +# Process-wide guard: at most ONE nudge of any kind per run. Future hint +# surfaces (e.g. the pack hint) must consult the same guard so two pitches +# can never stack in a single command's output. +_fired_this_run = False + + +def fired_this_run() -> bool: + return _fired_this_run + + +def mark_fired() -> None: + global _fired_this_run + _fired_this_run = True _MSG = ( "gate.cat vetoed that locally - this machine only, no record leaves the box.\n" @@ -37,6 +53,50 @@ def maybe_nudge_after_veto(): # never re-nudge -- one imperfect notice beats an accidental loop. with open(_FLAG, "w") as fh: fh.write("1\n") + mark_fired() sys.stderr.write(_MSG) except Exception: pass + + +def maybe_nudge_cli(surface: str, interventions: int) -> None: + """One short Solo hint on `gate.cat status`/`stats`, at most once per DAY. + + Fires only when there is something real to point at (interventions > 0) + and the user is not already a Cloud customer (GATECAT_CLOUD_API_KEY set). + Same contract as the post-veto nudge: stderr only, opt-out via + GATECAT_NO_NUDGE / GATECAT_QUIET, best-effort -- it can never change an + exit code, and never stacks with another nudge in the same run. + """ + try: + if os.environ.get("GATECAT_NO_NUDGE") or os.environ.get("GATECAT_QUIET"): + return + if _fired_this_run: + return + if interventions <= 0: + return + if os.environ.get("GATECAT_CLOUD_API_KEY"): + return + today = time.strftime("%Y-%m-%d", time.gmtime()) + try: + with open(_LAST) as fh: + if fh.read().strip() == today: + return + except Exception: + pass + os.makedirs(os.path.dirname(_LAST), exist_ok=True) + # Stamp FIRST (same race rule as the flag above): a lost notice beats + # a repeating one. + with open(_LAST, "w") as fh: + fh.write(today + "\n") + mark_fired() + sys.stderr.write( + f"\n{interventions} intervention(s) are recorded only in this machine's local log -- " + "inside the agent's blast radius.\n" + "The paid layer is the off-machine, append-only copy of that history " + "(Solo EUR 19/mo): https://gate.cat/teams.html?source=cli\n" + "See exactly what it caught, free and local: gate.cat report\n" + "(once-a-day notice; silence it: GATECAT_NO_NUDGE=1)\n" + ) + except Exception: + pass diff --git a/gatecat/_pack_hint.py b/gatecat/_pack_hint.py new file mode 100644 index 0000000..58f6a92 --- /dev/null +++ b/gatecat/_pack_hint.py @@ -0,0 +1,68 @@ +"""One-time, opt-out policy-pack hint keyed off CLIs present on the machine. + +Same contract as gatecat._nudge (the pattern is copied deliberately): stderr +only, best-effort (never raises, never changes an exit code), opt-out via +GATECAT_NO_NUDGE / GATECAT_QUIET, at most ONCE per machine (flag file in the +existing ~/.gatecat state dir), and never stacked with another nudge in the +same run (shared per-process guard in gatecat._nudge). + +The detection is honest and local: ``shutil.which()`` on stack CLIs. A machine +with the ``stripe`` CLI plausibly holds credentials the Fintech pack guards; a +machine with ``vercel``/``fly``/... plausibly deploys where the PaaS pack +guards. Pack scopes below quote PRICING.md verbatim. +""" +import os +import shutil +import sys + +from gatecat import _nudge + +_FLAG = os.path.expanduser("~/.gatecat/.pack_nudged") + +# (pack name, CLIs that suggest it, scope quoted from PRICING.md, checkout) +_PACKS = ( + ("Fintech", ("stripe",), + "refund creation, payouts/transfers, customer & billing-config deletion", + "https://buy.stripe.com/dRm5kw6Bn3iMfFS1Rk67S0c"), + ("PaaS", ("vercel", "netlify", "fly", "heroku", "railway", "render", "supabase"), + "`vercel remove`, `netlify sites:delete`, `fly/heroku apps destroy`, " + "`railway down`, `render/supabase delete`", + "https://buy.stripe.com/3cI5kw3pbaLeeBO2Vo67S0d"), +) + + +def _detect(): + """First (pack, matched CLI) whose tool exists on PATH, else None.""" + for name, clis, scope, url in _PACKS: + for cli in clis: + if shutil.which(cli): + return name, cli, scope, url + return None + + +def maybe_pack_hint() -> None: + """Print the one-time pack hint if a matching stack CLI is installed.""" + try: + if os.environ.get("GATECAT_NO_NUDGE") or os.environ.get("GATECAT_QUIET"): + return + if _nudge.fired_this_run(): + return + if os.path.exists(_FLAG): + return + found = _detect() + if not found: + return + name, cli, scope, url = found + os.makedirs(os.path.dirname(_FLAG), exist_ok=True) + # Flag FIRST (same race rule as _nudge): one lost notice beats a loop. + with open(_FLAG, "w") as fh: + fh.write(name + "\n") + _nudge.mark_fired() + sys.stderr.write( + f"\n`{cli}` is installed on this machine. The one-time EUR 29 {name} " + f"policy pack adds tested walls for: {scope}.\n" + f"{url}\n" + "(one-time-per-machine notice; silence it: GATECAT_NO_NUDGE=1)\n" + ) + except Exception: + pass diff --git a/gatecat/cloud_cli.py b/gatecat/cloud_cli.py index 259378b..1153daf 100644 --- a/gatecat/cloud_cli.py +++ b/gatecat/cloud_cli.py @@ -40,7 +40,9 @@ def _endpoint() -> str: def _api_key() -> str: k = os.environ.get("GATECAT_CLOUD_API_KEY") if not k: - sys.exit("cloud is off: set GATECAT_CLOUD_API_KEY (Solo/Team subscription)") + sys.exit("cloud is off: set GATECAT_CLOUD_API_KEY (Solo/Team subscription)\n" + " get a key: https://gate.cat/teams.html?source=cli " + "(Solo EUR 19/mo, Team EUR 149/mo flat)") return k diff --git a/gatecat/integrations/dashboard.py b/gatecat/integrations/dashboard.py index 3f30fe6..3414f7b 100644 --- a/gatecat/integrations/dashboard.py +++ b/gatecat/integrations/dashboard.py @@ -234,7 +234,9 @@ def render_report(records: list[dict], month: str | None = None) -> str: "*Generated locally by the free `gate.cat report` command - counts only,", "no command text, nothing sent anywhere. This log lives on the same", "machine the agent runs on; the paid tier keeps an off-machine copy", - "precisely because of that (see PRICING.md).*", + "precisely because of that (see PRICING.md). Same report, generated from", + "the copy the agent can't rewrite: Solo EUR 19/mo --", + "https://gate.cat/teams.html?source=report*", ] return "\n".join(lines) @@ -270,6 +272,17 @@ def explain(command: str, color: bool = True) -> str: return "\n".join(lines) +def _cli_nudge(surface: str, records: list[dict]) -> None: + """Best-effort, post-render: never allowed to affect output or exit code.""" + try: + from gatecat._nudge import maybe_nudge_cli + maybe_nudge_cli(surface, _summary(records)["interventions"] if records else 0) + from gatecat._pack_hint import maybe_pack_hint + maybe_pack_hint() # shared guard in _nudge: never two hints in one run + except Exception: + pass + + def main(argv: list[str] | None = None) -> int: args = list(sys.argv[1:] if argv is None else argv) cmd = args[0] if args else "status" @@ -329,10 +342,14 @@ def main(argv: list[str] | None = None) -> int: if cmd in ("-h", "--help"): print("gate.cat [status|on|off|allow '' [ttl]|stats|log|report [YYYY-MM]|dashboard [--html]|why ]") return 0 - print(render_status(_read(), color)) + recs = _read() + print(render_status(recs, color)) + _cli_nudge("status", recs) return 0 if cmd == "stats": - print(render_stats(_read(), color)) + recs = _read() + print(render_stats(recs, color)) + _cli_nudge("stats", recs) return 0 if cmd == "log": n = int(args[1]) if len(args) > 1 and args[1].isdigit() else 20 diff --git a/ops/deploy_landing.sh b/ops/deploy_landing.sh new file mode 100755 index 0000000..85c5de7 --- /dev/null +++ b/ops/deploy_landing.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Deploy the static site (docs/) to the VPS and verify the funnel pages the +# 0.4.17 post-veto nudge points at. Run from the repo root, on a machine that +# holds the VPS key (the sandboxed agent does NOT — this script is for the +# owner; see docs/AUTOPILOT-LOOP.md USER-2). +# +# ops/deploy_landing.sh # deploy docs/ + verify + restart fulfill +# DRY_RUN=1 ops/deploy_landing.sh # print what would happen +# +# Conventions match scripts/launch_metrics.py. +set -euo pipefail + +VPS="${VPS:-root@204.168.129.200}" +SSH_KEY="${SSH_KEY:-$HOME/.ssh/vps/id_ed25519}" +DOCROOT="${DOCROOT:-/opt/bgml/static/gatecat}" +SITE_DIR="$(cd "$(dirname "$0")/.." && pwd)/docs" +PAGES=(teams.html partners.html sitemap.xml) +SSH=(ssh -i "$SSH_KEY" -o ConnectTimeout=8 "$VPS") + +run() { if [ "${DRY_RUN:-0}" = "1" ]; then echo "DRY: $*"; else "$@"; fi } + +echo "== 1/4 rsync docs/ -> $VPS:$DOCROOT" +# --delete is deliberately NOT used: the docroot also serves files that do not +# live in docs/ (e.g. veto-demo.html from site/). Additive deploy only. +run rsync -av -e "ssh -i $SSH_KEY -o ConnectTimeout=8" "$SITE_DIR/" "$VPS:$DOCROOT/" + +echo "== 2/4 sha256 verify the funnel pages" +for f in "${PAGES[@]}"; do + local_sum=$(sha256sum "$SITE_DIR/$f" | cut -d' ' -f1) + if [ "${DRY_RUN:-0}" = "1" ]; then echo "DRY: verify $f ($local_sum)"; continue; fi + remote_sum=$("${SSH[@]}" "sha256sum $DOCROOT/$f" | cut -d' ' -f1) + if [ "$local_sum" != "$remote_sum" ]; then + echo "MISMATCH on $f: local=$local_sum remote=$remote_sum" >&2; exit 1 + fi + echo "ok $f $local_sum" +done + +echo "== 3/4 live 200 check (the nudge in 0.4.17 hits these URLs)" +for url in https://gate.cat/teams.html https://gate.cat/partners.html; do + if [ "${DRY_RUN:-0}" = "1" ]; then echo "DRY: curl $url"; continue; fi + code=$(curl -s -o /dev/null -w '%{http_code}' "$url") + echo "$code $url" + [ "$code" = "200" ] || { echo "FAIL: $url returned $code" >&2; exit 1; } +done + +echo "== 4/4 restart pack fulfillment (port 8791) so template changes load" +run "${SSH[@]}" "systemctl restart gatecat-fulfill && systemctl is-active gatecat-fulfill" + +echo +echo "Done. Post-deploy checklist (from docs/LAUNCH_0.4.16.md): smoke-test a" +echo "pack checkout link, confirm /cloud/health returns ok, and spot-check" +echo "https://gate.cat/ renders after the cache-control revalidation." diff --git a/ops/launch/distribution_kit_2026-07-22.md b/ops/launch/distribution_kit_2026-07-22.md new file mode 100644 index 0000000..0773be2 --- /dev/null +++ b/ops/launch/distribution_kit_2026-07-22.md @@ -0,0 +1,167 @@ +# Pakiet dystrybucyjny — WSZYSTKO paste-ready (fact-check: FACTS.md, 2026-07-22) + +Zastępuje sekcje 2–6 z docs/LAUNCH_KIT_2026-07-14.md (tamte mają stale liczby: +€9, 69 policies, konflację 1M-replay z false-blockiem). Show HN osobno: +`ops/launch/show_hn_ready.md`. Liczby tu użyte: F1b (0 real misses / 1,085,159), +F4 (178/178 + 1/129 benign + named gap), F10 (71/73), F2 (~0.6%), PRICING (€19/149/399, packi €29). + +## KOLEJNOŚĆ PUBLIKACJI (ważne — nie wszystko naraz) + +1. **Dzień 1, 14:00–16:00 UTC:** Show HN + wątek X (godzinę po HN, z linkiem do wątku HN). +2. **Dzień 1 wieczór LUB dzień 2:** r/ClaudeAI (najbardziej docelowa grupa). +3. **Dzień 2–3:** r/LocalLLaMA, potem r/Python (odstęp ≥24h; crossposty tego samego + dnia wyglądają jak spam i podcinają się nawzajem w feedach). +4. **Równolegle, bez pośpiechu:** 4 PR-y do awesome-list (to commity, nie posty). +5. Każdy live URL dopisz do issue #9 (publication gate). + +--- + +## 1. r/ClaudeAI + +**Tytuł:** +``` +gate.cat — a PreToolUse hook that vetoes Claude Code's destructive commands before they run +``` + +**Body:** +``` +Claude Code runs shell commands. Sometimes the wrong ones. gate.cat is a PreToolUse hook that vetoes irreversible commands (rm -rf, DROP TABLE, terraform destroy, dd to disk, secret exfiltration) BEFORE the shell executes them — enforcement runs in the harness, outside the model's control flow, so a prompt injection can't talk it into allowing something. + +Setup: pip install gate.cat, add gatecat-hook to .claude/settings.json (matcher "Bash|Write|Edit"). That's it. + +Measured, not claimed (every number is pinned in FACTS.md in the repo to a reproducible artifact): +- 1,085,159 unique real agent commands from 5 public datasets replayed through the full 6-stage gate: 0 real misses after adjudication +- the bypass suite catches 178/178 danger shapes it claims — and prints its own known gap (runtime assembly) plus 1 benign false-block in 129 cases; we publish the misses instead of hiding them +- 71 default policy walls, ~0.6% intervention rate on real commands (two independent logs) + +Honest limit: the gate is certain only about what it blocks — an unmatched action is unchecked, not safe. Use it with your sandbox, not instead of one. + +Free forever, Apache 2.0, zero-dependency core. There's an optional paid layer (off-machine copy of the veto history) but the hook above is the complete free product. + +https://github.com/BGMLAI/gate.cat · https://gate.cat +``` + +## 2. r/LocalLLaMA + +**Tytuł:** +``` +gate.cat — deterministic veto for tool-using local models: one base_url change blocks rm -rf before it runs (Apache 2.0) +``` + +**Body:** +``` +If you run local models with tool use (Ollama, vLLM, LM Studio, OpenRouter — anything speaking the OpenAI API), your agent can execute shell commands. gate.cat sits in front as a local proxy: your agent changes one base_url, and every proposed tool call is checked by a deterministic deny-list + independent exec analyzer before it executes. No model call in the veto path, sub-second, nothing leaves your machine. + +Also works as a gated shell for CLI agents (gatecat-shell) and as a Claude Code hook. + +Numbers with receipts (FACTS.md in the repo pins every claim to a reproducible artifact): +- 1,085,159 unique real agent commands replayed through the full gate → 0 real misses after adjudication +- bypass suite: 178/178 danger shapes caught, 1 benign false-block in 129, and it prints its own known gap — we'd rather you read our misses than trust a clean number +- 71 default policy walls covering recursive delete, disk wipe, DB drop, cloud teardown, git history destruction, secret exfil, fork bombs, guard self-defense + +Honest limit: it's a wall in front of known-dangerous shapes, not a proof of safety. An unmatched action is unchecked, not safe. Complement to a sandbox, not a substitute. + +pip install gate.cat — free forever, Apache 2.0, zero-dependency core. +https://github.com/BGMLAI/gate.cat +``` + +## 3. r/Python + +**Tytuł:** +``` +gate.cat — fail-closed action veto for AI agents (zero-dependency core, Apache 2.0) +``` + +**Body:** +``` +Built a Python package that blocks AI agents from executing irreversible shell commands. Deterministic string + path analysis plus an independent exec analyzer — no ML in the critical path, and the core has literally zero dependencies (pyproject: dependencies = []). + +pip install gate.cat + +71 default policy walls: recursive delete, disk wipe, DB drop/truncate, cloud teardown (aws/gcloud/az), k8s delete, git force-push, secret exfiltration, guard self-defense (the agent can't kill the gate), fork bombs. Fail-closed: engine error or anything it can't parse → block, never a silent allow. + +Three integration modes: Claude Code hook, gated shell wrapper for any CLI agent, local OpenAI-API proxy. + +The part I'd actually like feedback on: we publish our own bypass map. The suite catches 178/178 danger shapes it claims, and prints its known gap (runtime assembly) plus 1 benign false-block in 129 cases. 1,085,159 real agent commands replayed → 0 real misses after adjudication. All pinned in FACTS.md with reproduction scripts — if your numbers disagree, that's a bug report we want. + +https://github.com/BGMLAI/gate.cat · https://pypi.org/project/gate.cat/ +``` + +## 4. Wątek X (8 tweetów) + +``` +1/ your AI agent can run `rm -rf /`. + +gate.cat blocks it before the shell sees it. + +1,085,159 real agent commands replayed → 0 real misses. +the bypass suite prints its own known gap. + +deterministic. fail-closed. apache 2.0. + +🧵 +``` +``` +2/ three ways in: + +→ claude code: gatecat-hook in settings.json — enforcement runs in the harness, outside the model's control flow +→ any CLI agent: shell = gatecat-shell +→ ollama / vllm / anything openai-api: one base_url change + +the model can't route around a veto it never sees. +``` +``` +3/ the check is not an LLM call. deterministic string + path analysis + an independent exec analyzer. + +71 default policy walls: recursive delete, disk wipe, DB drop, cloud teardown, git force-push, secret exfil, fork bombs, guard self-defense. + +~0.6% intervention rate on real traffic. it gets out of the way. +``` +``` +4/ the design call I'm most proud of: we publish our own misses. + +bypass suite: 178/178 danger shapes caught — AND it prints its known gap (runtime assembly) + 1 benign false-block in 129 cases. + +one miss you can read > a clean number you can't. +``` +``` +5/ honest limit, said out loud: the gate is certain only about what it BLOCKS. + +an unmatched action is unchecked, not safe. + +it's a wall in front of known-dangerous shapes — use it WITH your sandbox. a sandbox can't tell you what the agent TRIED. +``` +``` +6/ install: + +pip install gate.cat + +free forever. blocking never expires, never phones home. +``` +``` +7/ the paid layer is the one thing an agent can't have: an off-machine, append-only copy of the veto history. + +a local log lives inside the agent's blast radius — real incidents include an agent deleting a file and hiding it. + +solo €19/mo · team €149/mo flat · packs €29 one-time. +``` +``` +8/ every number in this thread has a row in FACTS.md — claim → source artifact → allowed wording. + +reproduce them. if your numbers disagree, that's a bug report we want. + +https://gate.cat +https://github.com/BGMLAI/gate.cat +``` + +## 5. PR-y do awesome-list (4) — poprawione liczby + +### awesome-ai-agents / awesome-security / awesome-python / awesome-claude-code +Wspólny opis (dostosuj długość do konwencji danej listy): +``` +gate.cat — deterministic, fail-closed action veto for AI coding agents. Blocks irreversible shell commands (rm -rf, DROP TABLE, terraform destroy, secret exfil) before execution: Claude Code hook, gated shell, or OpenAI-API proxy. 71 default policies, zero-dependency core, Apache 2.0. Publishes its own bypass map: 178/178 claimed danger shapes caught (1 benign false-block in 129, known gap printed); 1,085,159 real agent commands replayed → 0 real misses. https://github.com/BGMLAI/gate.cat +``` +Krótka wersja (awesome-python, jedna linia): +``` +gate.cat — fail-closed action veto for AI agents: blocks irreversible shell commands before execution. Zero-dep core, Apache 2.0. +``` diff --git a/ops/launch/lobsters_ready.md b/ops/launch/lobsters_ready.md new file mode 100644 index 0000000..bd2971b --- /dev/null +++ b/ops/launch/lobsters_ready.md @@ -0,0 +1,26 @@ +# lobste.rs — wariant warunkowy (TYLKO jeśli masz konto; serwis invite-only) + +Fact-checked 2026-07-22 przeciw FACTS.md (te same poprawki co show_hn_ready.md). +Lobste.rs preferuje treść techniczną bez pitchu — zero cen w poście; kultura +wymaga tagu `show`. Sugerowane tagi: `show`, `security`, `ai`. + +## Tytuł + +``` +Gate.cat: deterministic action veto for AI coding agents (0 real misses across a 1M-command replay) +``` + +## Tekst (pole "Text") + +``` +gate.cat vetoes irreversible shell commands (rm -rf, DROP TABLE, terraform destroy, disk writes, secret exfiltration) before an AI coding agent executes them. Deterministic string + path analysis with an independent exec analyzer — no model call in the veto path, so prompt injection can't negotiate with it. + +Enforcement points: a Claude Code hook (runs in the harness, outside the agent's control flow), a gated shell for any CLI agent (gatecat-shell), and a local OpenAI-API proxy (one base_url change covers Ollama/vLLM/OpenRouter). + +Measurement over marketing: 1,085,159 unique real agent commands from 5 public datasets replayed through the full gate → 0 real misses after adjudication (the 4 catalog-flagged allows are disposable-artifact cleanups, adjudicated in the repo). The bypass suite catches 178/178 danger shapes it claims and prints its own known gap (runtime assembly) + 1 benign false-block in 129 cases. Every public number is pinned in FACTS.md to a reproducible artifact. + +Honest limit: the gate is certain only about what it blocks — an unmatched action is unchecked, not safe. It's a complement to a sandbox, not a substitute. + +Apache-2.0, zero-dependency core. pip install gate.cat +https://github.com/BGMLAI/gate.cat +``` diff --git a/ops/launch/release_0.4.18_checklist.md b/ops/launch/release_0.4.18_checklist.md new file mode 100644 index 0000000..89c8288 --- /dev/null +++ b/ops/launch/release_0.4.18_checklist.md @@ -0,0 +1,37 @@ +# Release 0.4.18 — checklist dla Bogumiła (agent nie może publikować) + +Zawartość release'u: T5 (PyPI listing jako landing) + T6 (CLI Solo nudge) + +T7 (pack hint). Wersja i CHANGELOG już podbite w PR #26. + +## Przed publikacją + +- [ ] Merge PR #26 do master (CI musi być zielone na ostatnim commicie). +- [ ] Lokalnie z master: `python -m pytest -q` — pełny suite zielony + (release-gate z FACTS.md F3; przy okazji re-pin F3 na nowy wynik). +- [ ] `python -m gatecat.integrations.bypass_suite` — re-pin F4 jeśli liczby + się zmieniły (nie powinny: zero zmian w policy/recall/bypass). + +## Publikacja + +- [ ] `python -m build` + `twine upload` (albo Twój dotychczasowy flow). +- [ ] GitHub release v0.4.18 (tag na commicie merge'a). +- [ ] Clean install check: `pip install --no-cache-dir gate-cat==0.4.18` + → `gate.cat --help` działa; F9 w FACTS.md re-pin na 0.4.18. +- [ ] KOLEJNOŚĆ: najpierw publish na PyPI, DOPIERO POTEM deploy docs/ na VPS — + llms.txt w tym release reklamuje 0.4.18, nie może wyprzedzić PyPI. + +## Publication gate (z docs/LAUNCH_0.4.16.md — nadal obowiązuje) + +- [ ] gate.cat serwuje aktualny landing (teams/partners live — zweryfikowane + 2026-07-22) i dwustopniowy installer. +- [ ] Public PyPI clean install zwraca 0.4.18. +- [ ] `curl https://gate.cat/cloud/health` → 200. +- [ ] Stripe live-mode webhook aktywny (checkout completion, subscription + updates, cancellation). +- [ ] Każdy live post URL dopisany do issue #9 (timestamp + owner). + +## Po publikacji + +- [ ] Post Show HN (`ops/launch/show_hn_ready.md`) — jeśli jeszcze nie wisi; + stary post z 2026-07-15 miał 2 punkty, repost innego tytułu jest OK. +- [ ] METRICS.log: obserwuj pypi_downloads przez 3 dni (Action robi to sam). diff --git a/ops/launch/show_hn_ready.md b/ops/launch/show_hn_ready.md new file mode 100644 index 0000000..971272b --- /dev/null +++ b/ops/launch/show_hn_ready.md @@ -0,0 +1,65 @@ +# Show HN — paste-ready (fact-checked 2026-07-22 przeciw FACTS.md) + +Poprawki vs docs/LAUNCH_KIT_2026-07-14.md (fixy sędziów panelu 2026-07-22): +konflacja "1M replay → 1 false-block" rozdzielona na F1b (0 real misses) i F4 +(bypass suite, 1/129 benign); 69→71 policies (F10); €9→€19 (PRICING.md); +"zero dependencies"→"zero-dependency core" (pyproject: `dependencies = []`, +extras opt-in). Wersja: 0.4.17 (F9). + +**KIEDY:** najlepiej PO merge PR #26 + deploy (USER-2) — ruch ma trafiać na +działające gate.cat/teams.html. Optymalnie wt–czw, 14:00–16:00 UTC. +Po publikacji: odpowiadaj na komentarze przez pierwsze 3–4 h (to decyduje +o frontpage), link "1 false-block" masz w RECALL.md/FACTS.md. + +--- + +## Tytuł (67 znaków) + +``` +Show HN: Gate.cat – deterministic action veto for AI coding agents +``` + +## Body + +``` +gate.cat blocks irreversible shell commands before an AI coding agent executes them — it vetoes the execution itself, not a log line after the fact. + +Three integration points: + +- Claude Code hook — the strongest one: enforcement runs in the harness, outside the agent's control flow +- gatecat-shell — a gated shell for any CLI agent that ultimately runs `sh -c ""` (Codex, aider, anything honoring $SHELL) +- a local proxy for anything speaking the OpenAI API (Ollama, vLLM, OpenRouter, LM Studio): your agent changes one base_url + +The check is deterministic string + path analysis plus an independent exec analyzer — no model call in the path, so prompt injection can't talk the gate into allowing something. + +Numbers, measured not claimed (every public number has a row in FACTS.md in the repo, pinned to a reproducible artifact): + +- 1,085,159 unique real agent commands (5 public datasets) replayed through the full 6-stage gate: 0 real misses after adjudication — the 4 catalog-flagged allows are disposable-artifact cleanups the gate correctly permits, and the adjudication is in the repo +- the reproducible bypass suite catches 178/178 danger shapes it claims — and prints its own known gap (runtime assembly) plus 1 benign false-block in 129 cases; we publish the misses instead of hiding them +- 71 default policy walls (73 presets incl. opt-in), ~0.6% intervention rate on real commands (two independent logs) +- Apache-2.0, zero-dependency core, 0.4.17 on PyPI + +Honest limits, because that's the whole brand: the gate is certain only about what it blocks — an unmatched action is unchecked, not safe. It's a wall in front of known-dangerous shapes, not a proof of safety. Use it with your sandbox, not instead of one: a sandbox can't tell you what the agent tried, and it won't stop a terraform destroy that has real credentials inside the sandbox. + +Install: pip install gate.cat +Repo: https://github.com/BGMLAI/gate.cat + +What I actually want from HN: does publishing our own bypass map and false-block build more trust than a clean number would? That was the hardest design call. +``` + +## Pierwszy komentarz (wklej od razu po publikacji, jako autor) + +``` +Author here — business model up front, since HN will (rightly) ask: + +The local gate is free forever (Apache-2.0). Nothing is rate-limited, and safety is never paywalled — every time an audit found a catastrophic class missing (KMS/secret destroy, IAM escalation, backup destruction), it was promoted INTO the free core, not into a pack. + +Paid is the one thing an agent can't have: an off-machine, append-only copy of the veto history. A local log lives inside the agent's blast radius — real incident reports include an agent deleting a file and then hiding it from the user. Cloud keeps the receipts, plus alerts and a monthly report. Solo €19/mo, Team €149/mo flat (up to 10 machines), Business €399/mo, and one-time €29 policy packs for stack-specific breadth (Fintech / PaaS / raw HTTP-API calls). Stripe checkout, 30-day no-questions refund. + +Exactly what leaves your machine (only if you enable Cloud): veto event timestamps, policy id, verdict, and a HASH of the matched command by default — raw text is a separate explicit opt-in, because commands contain secrets. Never file contents, env vars, or your code. Details: https://gate.cat/teams.html and PRICING.md in the repo. +``` + +Uwaga: świadomie linkuję stronę cenową zamiast surowych linków buy.stripe.com — +na HN goły checkout-link w komentarzu autora czytany jest jako spam i zbiera +downvoty; checkout jest 1 klik od teams.html/#pricing. Surowe linki masz +w PRICING.md, gdybyś wolał inaczej. diff --git a/products/cloud/cloud_activate.py b/products/cloud/cloud_activate.py index 22a358c..ed8f825 100644 --- a/products/cloud/cloud_activate.py +++ b/products/cloud/cloud_activate.py @@ -57,8 +57,12 @@ def _affiliate_safe(fn, *args, **kwargs): # path; the channel only controls which is treated as primary / documented. # --------------------------------------------------------------------------- def payment_channel() -> str: - ch = os.environ.get("GATECAT_PAYMENT_CHANNEL", "lemonsqueezy").strip().lower() - return "stripe" if ch == "stripe" else "lemonsqueezy" + # Default flipped to stripe 2026-07-22 (reverses the 2026-07-12 founder + # decision): Lemon Squeezy DECLINED the account application on 2026-07-14, + # so Stripe is the only live channel. LS webhook path stays serviceable + # behind the env override in case the channel ever reopens. + ch = os.environ.get("GATECAT_PAYMENT_CHANNEL", "stripe").strip().lower() + return "lemonsqueezy" if ch == "lemonsqueezy" else "stripe" STRIPE_KEY = os.environ.get("STRIPE_KEY", "") diff --git a/products/cloud/gatecat_fulfill.py b/products/cloud/gatecat_fulfill.py index a1b7999..607aadd 100644 --- a/products/cloud/gatecat_fulfill.py +++ b/products/cloud/gatecat_fulfill.py @@ -42,7 +42,8 @@ export GATECAT_EXTRA_POLICIES=gatecat_packs.{mod}

Full instructions are in INSTALL.md inside the zip. This download link keeps working — bookmark this page. Receipt & invoice arrive by email from Stripe. -Questions: bgml@bgml.ai

""" +Questions: bgml@bgml.ai

+{xsell}""" MODULE_FOR = { "gatecat-pack-fintech-1.0.0.zip": "fintech", @@ -50,6 +51,43 @@ "gatecat-pack-http-breadth-1.0.0.zip": "http_api_breadth", } +# Cross-sell on the thank-you page: the two packs NOT just bought, plus the +# Cloud line. Scopes quote PRICING.md; links are the live Payment Links with a +# client_reference_id so the source shows up on the Checkout Session (a plain +# ?source= query on buy.stripe.com would be dropped and measure nothing). +PACK_LINKS = { + "gatecat-pack-fintech-1.0.0.zip": ( + "Fintech — refund creation, payouts/transfers, customer & " + "billing-config deletion", + "https://buy.stripe.com/dRm5kw6Bn3iMfFS1Rk67S0c"), + "gatecat-pack-paas-1.0.0.zip": ( + "PaaS — vercel remove, netlify sites:delete, " + "fly/heroku apps destroy, railway down, " + "render/supabase delete", + "https://buy.stripe.com/3cI5kw3pbaLeeBO2Vo67S0d"), + "gatecat-pack-http-breadth-1.0.0.zip": ( + "HTTP-API Breadth — destructive raw-HTTP calls the CLI-verb walls " + "never see", + "https://buy.stripe.com/aFa8wIgbX06AdxK67A67S0e"), +} + + +def xsell_html(purchased: str) -> str: + """The 'Complete your coverage' block, excluding the pack just bought.""" + items = "".join( + f'
  • {label}' + " — €29 one-time
  • " + for fname, (label, url) in PACK_LINKS.items() if fname != purchased) + return ( + "

    Complete your coverage

    " + "

    Every rule in a pack is tested to fire on its danger and stay " + "silent on the benign twin — same bar as the free core.

    " + f"
      {items}
    " + "

    And the one thing an agent can't reach: an off-machine, " + "append-only copy of your veto history — " + '' + "Cloud Solo €19/mo.

    ") + def _stripe_get(path: str): req = urllib.request.Request("https://api.stripe.com/v1/" + path) @@ -99,7 +137,8 @@ def do_GET(self): # noqa: N802 if not filename: return self._deny() body = PAGE.format(sid=session_id, fname=filename, - mod=MODULE_FOR[filename]).encode() + mod=MODULE_FOR[filename], + xsell=xsell_html(filename)).encode() self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(body))) diff --git a/pyproject.toml b/pyproject.toml index 8507fd6..5337105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gate.cat" -version = "0.4.17" +version = "0.4.18" description = "Block irreversible AI-agent actions before they run — deterministic, fail-closed action-veto (deny-list + exec-check + human-in-the-loop) for any tool-using agent, from a Claude Code hook to local LLMs." readme = "README.md" license = {text = "Apache-2.0"} @@ -91,9 +91,10 @@ gatecat-proxy = "gatecat.proxy.__main__:main" truthgate-audit = "gatecat.audit:main" [project.urls] -Homepage = "https://github.com/BGMLAI/gate.cat" +Homepage = "https://gate.cat" Repository = "https://github.com/BGMLAI/gate.cat" Issues = "https://github.com/BGMLAI/gate.cat/issues" +Pricing = "https://gate.cat/teams.html" [build-system] requires = ["hatchling"] diff --git a/tests/test_nudge_cli.py b/tests/test_nudge_cli.py new file mode 100644 index 0000000..610fd9b --- /dev/null +++ b/tests/test_nudge_cli.py @@ -0,0 +1,110 @@ +"""Tests for the once-a-day CLI Solo nudge (gatecat._nudge.maybe_nudge_cli). + +Contract (same bar as the post-veto nudge): stderr only, opt-out respected, +best-effort (never raises), silent for Cloud customers and for empty logs, +at most one nudge of ANY kind per process, at most one per day across runs. +""" +import os + +import gatecat._nudge as nudge +from gatecat.integrations.dashboard import render_report + + +def _isolate(tmp_path, monkeypatch): + monkeypatch.setattr(nudge, "_LAST", str(tmp_path / ".gatecat" / "nudge_last")) + monkeypatch.setattr(nudge, "_fired_this_run", False) + monkeypatch.delenv("GATECAT_NO_NUDGE", raising=False) + monkeypatch.delenv("GATECAT_QUIET", raising=False) + monkeypatch.delenv("GATECAT_CLOUD_API_KEY", raising=False) + + +def test_fires_once_with_interventions(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + + nudge.maybe_nudge_cli("status", 7) + + err = capsys.readouterr().err + assert "7 intervention(s)" in err + assert "https://gate.cat/teams.html?source=cli" in err + assert "gate.cat report" in err # discovery line for the free local report + assert "GATECAT_NO_NUDGE=1" in err + assert os.path.exists(str(tmp_path / ".gatecat" / "nudge_last")) + + +def test_never_two_nudges_in_one_process(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + + nudge.maybe_nudge_cli("status", 3) + nudge.maybe_nudge_cli("stats", 3) + + err = capsys.readouterr().err + assert err.count("teams.html?source=cli") == 1 + + +def test_daily_rate_limit_across_processes(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + nudge.maybe_nudge_cli("status", 3) + capsys.readouterr() + + # simulate a fresh process the same day: only the in-memory guard resets + monkeypatch.setattr(nudge, "_fired_this_run", False) + nudge.maybe_nudge_cli("status", 3) + + assert capsys.readouterr().err == "" + + +def test_silent_without_interventions(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + + nudge.maybe_nudge_cli("status", 0) + + assert capsys.readouterr().err == "" + assert not os.path.exists(str(tmp_path / ".gatecat" / "nudge_last")) + + +def test_silent_for_cloud_customers(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + monkeypatch.setenv("GATECAT_CLOUD_API_KEY", "k") + + nudge.maybe_nudge_cli("status", 9) + + assert capsys.readouterr().err == "" + + +def test_optout_env_is_silent(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + monkeypatch.setenv("GATECAT_NO_NUDGE", "1") + + nudge.maybe_nudge_cli("status", 9) + + assert capsys.readouterr().err == "" + + +def test_post_veto_nudge_blocks_cli_nudge_same_run(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + monkeypatch.setattr(nudge, "_FLAG", str(tmp_path / ".gatecat" / ".nudged")) + + nudge.maybe_nudge_after_veto() + nudge.maybe_nudge_cli("status", 5) + + err = capsys.readouterr().err + assert "gate.cat vetoed that locally" in err + assert "source=cli" not in err + + +def test_never_raises_when_state_dir_unwritable(tmp_path, monkeypatch): + _isolate(tmp_path, monkeypatch) + blocker = tmp_path / "blocked" + blocker.write_text("file, not a dir") + monkeypatch.setattr(nudge, "_LAST", str(blocker / "nudge_last")) + + nudge.maybe_nudge_cli("status", 5) # must not raise + + +def test_report_footer_links_offmachine_copy(): + out = render_report([{"ts": "2026-07-01T10:00:00Z", "decision": "block", + "policy": "DELETE_ANALYZER", "context": "rm -rf x"}], + month="2026-07") + assert "https://gate.cat/teams.html?source=report" in out + assert "EUR 19/mo" in out + out.encode("ascii") # report stays paste-safe (same bar as test_dashboard) diff --git a/tests/test_pack_hint.py b/tests/test_pack_hint.py new file mode 100644 index 0000000..733e36d --- /dev/null +++ b/tests/test_pack_hint.py @@ -0,0 +1,92 @@ +"""Tests for the one-time policy-pack hint (gatecat._pack_hint). + +Same bar as the nudge tests: once per machine, opt-out respected, never +raises, never stacks with another nudge in the same process. +""" +import os + +import gatecat._nudge as nudge +import gatecat._pack_hint as ph + + +def _isolate(tmp_path, monkeypatch, which=lambda cli: None): + monkeypatch.setattr(ph, "_FLAG", str(tmp_path / ".gatecat" / ".pack_nudged")) + monkeypatch.setattr(nudge, "_fired_this_run", False) + monkeypatch.setattr(ph.shutil, "which", which) + monkeypatch.delenv("GATECAT_NO_NUDGE", raising=False) + monkeypatch.delenv("GATECAT_QUIET", raising=False) + + +def test_stripe_cli_triggers_fintech_pack(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/stripe" if c == "stripe" else None) + + ph.maybe_pack_hint() + + err = capsys.readouterr().err + assert "Fintech" in err + assert "`stripe` is installed" in err + assert "https://buy.stripe.com/dRm5kw6Bn3iMfFS1Rk67S0c" in err + assert "GATECAT_NO_NUDGE=1" in err + assert os.path.exists(str(tmp_path / ".gatecat" / ".pack_nudged")) + + +def test_vercel_cli_triggers_paas_pack(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/vercel" if c == "vercel" else None) + + ph.maybe_pack_hint() + + err = capsys.readouterr().err + assert "PaaS" in err + assert "https://buy.stripe.com/3cI5kw3pbaLeeBO2Vo67S0d" in err + + +def test_silent_when_no_stack_cli(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch) + + ph.maybe_pack_hint() + + assert capsys.readouterr().err == "" + assert not os.path.exists(str(tmp_path / ".gatecat" / ".pack_nudged")) + + +def test_once_per_machine(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/stripe" if c == "stripe" else None) + ph.maybe_pack_hint() + capsys.readouterr() + + monkeypatch.setattr(nudge, "_fired_this_run", False) # fresh process, same machine + ph.maybe_pack_hint() + + assert capsys.readouterr().err == "" + + +def test_never_stacks_with_cli_nudge_same_run(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/stripe" if c == "stripe" else None) + monkeypatch.setattr(nudge, "_LAST", str(tmp_path / ".gatecat" / "nudge_last")) + monkeypatch.delenv("GATECAT_CLOUD_API_KEY", raising=False) + + nudge.maybe_nudge_cli("status", 5) # fires first + ph.maybe_pack_hint() # must stay silent this run + + err = capsys.readouterr().err + assert "source=cli" in err + assert "policy pack" not in err + assert not os.path.exists(str(tmp_path / ".gatecat" / ".pack_nudged")) + + +def test_optout_env_is_silent(tmp_path, monkeypatch, capsys): + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/stripe") + monkeypatch.setenv("GATECAT_QUIET", "1") + + ph.maybe_pack_hint() + + assert capsys.readouterr().err == "" + + +def test_never_raises_when_state_dir_unwritable(tmp_path, monkeypatch): + blocker = tmp_path / "blocked" + blocker.write_text("file, not a dir") + _isolate(tmp_path, monkeypatch, which=lambda c: "/usr/bin/stripe") + monkeypatch.setattr(ph, "_FLAG", str(blocker / ".pack_nudged")) + + ph.maybe_pack_hint() # must not raise diff --git a/tests/test_pack_stripe.py b/tests/test_pack_stripe.py index 34e00c8..c7a3b32 100644 --- a/tests/test_pack_stripe.py +++ b/tests/test_pack_stripe.py @@ -49,3 +49,23 @@ def stripe_get(path): assert fulfill.verify_session("cs_paid") == expected assert fulfill.verify_session("cs_paid") == expected assert len(calls) == 2 + + +def test_xsell_excludes_purchased_pack(monkeypatch): + m = _load(monkeypatch) + html = m.xsell_html("gatecat-pack-fintech-1.0.0.zip") + assert "Fintech" not in html # bought -> excluded + assert "PaaS" in html and "HTTP-API Breadth" in html + assert html.count("client_reference_id=pack-xsell") == 2 + assert "teams.html?source=pack-xsell" in html # Cloud Solo line + assert "€29" in html and "€19" in html + + +def test_xsell_renders_into_page(monkeypatch): + m = _load(monkeypatch) + body = m.PAGE.format(sid="cs_x", fname="gatecat-pack-paas-1.0.0.zip", + mod="paas", + xsell=m.xsell_html("gatecat-pack-paas-1.0.0.zip")) + assert "Complete your coverage" in body + assert "PaaS" not in body.split("Complete your coverage")[1].split("")[0].replace( + "render/supabase", "") # purchased PaaS pack not offered again diff --git a/tests/test_paid_lemonsqueezy.py b/tests/test_paid_lemonsqueezy.py index 95337bc..4bdccc2 100644 --- a/tests/test_paid_lemonsqueezy.py +++ b/tests/test_paid_lemonsqueezy.py @@ -59,9 +59,16 @@ def env_vars(): # ---- channel selector ------------------------------------------------------- -def test_default_channel_is_lemonsqueezy(tmp_path, env_vars): +def test_default_channel_is_stripe(tmp_path, env_vars): + # LS declined the account application 2026-07-14; stripe is the live + # default. The LS path stays reachable via the env override below. os.environ.pop("GATECAT_PAYMENT_CHANNEL", None) ca = _load_activate(tmp_path) + assert ca.payment_channel() == "stripe" + + +def test_channel_selector_keeps_lemonsqueezy(tmp_path, env_vars): + ca = _load_activate(tmp_path, {"GATECAT_PAYMENT_CHANNEL": "lemonsqueezy"}) assert ca.payment_channel() == "lemonsqueezy"