Costul raportat de CLI e pretul echivalent la API; pe abonament nu se factureaza, deci plafonul zilnic oprea puntea fara motiv (5.71 / 5.00 USD). - limits.parse_cap(): `off`/`none`/`nelimitat`/`0`/gol/gunoi => fara plafon - stopped()/record_cost() nu mai opresc si nu mai alerteaza cand e dezactivat - implicit devine `off`; /status arata „(fara plafon)" - dashboard: /api/status si doctor nu mai crapa pe valoare ne-numerica - README + ops/env.example explica de ce ramane off pe abonament Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
371 lines
19 KiB
Markdown
371 lines
19 KiB
Markdown
# Punte Discord -> Claude Code (LXC 171)
|
|
|
|
Un bot Discord subtire care duce mesajele dintr-un guild privat catre CLI-ul `claude`
|
|
care ruleaza pe containerul de dezvoltare **LXC 171 (claude-agent, 10.0.20.171)**, si
|
|
aduce raspunsurile inapoi. Practic: acelasi Claude Code cu care lucrezi in terminal,
|
|
comandat de pe telefon.
|
|
|
|
Nu e un chatbot separat. Nu are memorie proprie, nu are baza de date proprie: sesiunile
|
|
sunt chiar sesiunile Claude Code din `~/.claude/projects/`, iar un fir de Discord este
|
|
o sesiune.
|
|
|
|
> Nu confunda cu **MoltBot** (LXC 110) sau cu OpenClaw — acelea sunt alti agenti, cu
|
|
> alt scop. Puntea asta ruleaza pe masina de dezvoltare si are accesul ei.
|
|
|
|
---
|
|
|
|
## Arhitectura
|
|
|
|
```
|
|
Discord (guild privat)
|
|
| on_message
|
|
v
|
|
bot.py -- allowlist (guild / canal / utilizator) [doar adaptor Discord]
|
|
|
|
|
+-- session_store.py state.json {thread_id: {sid, cwd, model, inflight, pid}}
|
|
+-- runner.py proces persistent per fir, alimentat pe stdin
|
|
+-- stream.py parser tolerant de JSONL
|
|
+-- render.py chunker + un loop de editare per canal
|
|
+-- limits.py max procese, timeout tur, rate limit, plafon de cost
|
|
+-- security/ hook PreToolUse (confirmari) + wrapper `infra`
|
|
+-- alerts.py alerte email [ops]
|
|
+-- cleanup.py procese lasate in urma (`/cleanup`) [ops]
|
|
|
|
|
v
|
|
claude -p --input-format stream-json --output-format stream-json --verbose
|
|
--resume <sid> --permission-mode bypassPermissions
|
|
--settings ~/.claude-discord/bot-settings.json --model sonnet --autocompact auto
|
|
```
|
|
|
|
Cateva alegeri care nu se vad din diagrama:
|
|
|
|
- **Proces persistent per fir**, nu unul per mesaj. Asta permite *steering* la mijlocul
|
|
turului: un mesaj trimis in timp ce Claude lucreaza ajunge la el si schimba raspunsul
|
|
(verificat: mesaj la 8s intr-un tur de 34.5s). Reaper la 20 min de inactivitate;
|
|
repornirea se face cu `--resume <sid>`, deci firul nu-si pierde contextul.
|
|
- **Un proces `claude` = ~406 MB RSS** (masurat). De aici toate limitele: maxim 4 procese
|
|
vii, `MemoryMax=6G` pe unit, si comanda `/cleanup`.
|
|
- **Model implicit `sonnet`.** Un tur banal pe opus a costat $0.1547 (masurat), deci
|
|
opus e optional, per fir, prin `/model model:opus`.
|
|
|
|
---
|
|
|
|
## Comenzi
|
|
|
|
| Comanda | Ce face |
|
|
|---------|---------|
|
|
| `/new` | Sesiune noua, curata, in firul curent |
|
|
| `/new fork:True` | Sesiune noua care porneste din contextul celei curente |
|
|
| `/cd cale:<cale>` | Schimba directorul de lucru al firului (ex. `/cd cale:/workspace/romfastsql`) |
|
|
| `/model model:<sonnet\|opus>` | Schimba modelul pentru firul curent |
|
|
| `/status` | Sesiune, director, model, cost cumulat, proces viu, ultimele linii de stderr |
|
|
| `/stop` | Opreste turul in desfasurare din firul curent |
|
|
| `/cleanup` | Listeaza procesele lasate in urma (rulare seaca). `/cleanup force:True` le opreste |
|
|
| `/help` | Lista de mai sus, in fir |
|
|
|
|
Comenzile sunt **application commands** (`/`), inregistrate pe guild-urile din
|
|
`DISCORD_GUILD_IDS` la pornirea botului, deci apar in lista de comenzi a Discord. Vechiul
|
|
prefix `!` nu mai executa nimic: botul raspunde doar cu indiciul catre comanda `/`
|
|
echivalenta. Orice alt mesaj din canal pleaca la Claude ca prompt, ca inainte.
|
|
|
|
Un fir de Discord = o sesiune Claude. Canalul principal are si el sesiunea lui, cea
|
|
implicita. Subsolul fiecarui raspuns arata modelul, durata si costul.
|
|
|
|
### Despre `/cleanup`
|
|
|
|
`KillMode=control-group` opreste arborele serviciului la restart, dar **nu prinde ce s-a
|
|
desprins**: un server pornit cu `&` intr-un tur, un `nohup`, un job lung reparentat la
|
|
init. Alea raman si se aduna — ~440 MB bucata cu tot cu serverele MCP, pe un container cu istoric de OOM.
|
|
|
|
**DOMENIUL e cgroup-ul serviciului `claude-discord.service` si numai el.** Pe LXC 171
|
|
ruleaza permanent sesiuni Claude interactive (tmux, ttyd, agenti) care n-au nicio legatura
|
|
cu puntea; acelea stau in `tmux-spawn-*.scope` si NU sunt raportate niciodata, nici macar
|
|
in rularea seaca. Regula e fail-closed: daca cgroup-ul unui proces nu poate fi citit,
|
|
procesul e considerat neeligibil — mai bine ratam un orfan decat sa oprim sesiunea cuiva.
|
|
Fara aceasta limitare, `/cleanup force:True` dat din Discord si-ar opri propria sesiune
|
|
impreuna cu tot ce ruleaza omul in tmux.
|
|
|
|
Inauntrul cgroup-ului, e orfan orice proces neprotejat care nu se leaga de `state.json`,
|
|
indiferent cum se numeste — asa raman prinse si serverele MCP pornite de `claude`
|
|
(`npm exec @playwright/mcp`, `node ...`), care altfel ar scapa fiindca nu se cheama
|
|
`claude`. Un fir costa in jur de 440 MB cu tot cu MCP, nu 300.
|
|
|
|
**Ruleaza sec (dry-run) in mod implicit** — intai vezi lista, apoi decizi. Ce e inregistrat
|
|
in `state.json` si toti descendentii acelor procese (adica turul care ruleaza chiar acum)
|
|
nu sunt niciodata atinse, iar potrivirea se face si pe `pid_start_time`, ca un PID reciclat
|
|
sa nu duca la oprirea altui proces. Copiii se opresc inaintea parintilor, altfel s-ar
|
|
reparenta si ar scapa.
|
|
|
|
---
|
|
|
|
## Instalare
|
|
|
|
### Partea automata
|
|
|
|
```bash
|
|
cd /workspace/romfastsql/proxmox/lxc171-claude-agent/discord-bridge
|
|
./ops/install.sh
|
|
```
|
|
|
|
Scriptul e idempotent (poti sa-l rulezi de cate ori vrei) si face:
|
|
|
|
1. `~/.claude-discord/` cu drepturi `0700`, plus `logs/` si `approvals/`
|
|
2. `~/.claude-discord/env` cu `0600`, copiat din `ops/env.example` — **nu suprascrie
|
|
niciodata un env existent**
|
|
3. venv in `~/.claude-discord/venv` + dependintele din `requirements.txt`
|
|
4. `loginctl enable-linger claude` — fara asta serviciul de utilizator moare la logout
|
|
si nu porneste la boot
|
|
5. symlink `~/.config/systemd/user/claude-discord.service` -> `ops/claude-discord.service`,
|
|
apoi `daemon-reload` si `systemd-analyze verify`
|
|
6. intrare de crontab pentru `logrotate` (zilnic, 04:10)
|
|
|
|
Scriptul **nu porneste** serviciul. Dupa ce completezi env-ul:
|
|
|
|
```bash
|
|
./ops/install.sh --start
|
|
```
|
|
|
|
### Partea manuala (o faci tu, o singura data)
|
|
|
|
Nu se poate automatiza: cere un om logat in Discord.
|
|
|
|
1. **Creeaza aplicatia Discord.** https://discord.com/developers/applications ->
|
|
*New Application*. E o aplicatie **noua, dedicata** puntii — nu refolosi aplicatia
|
|
MoltBot/OpenClaw.
|
|
2. **Deschide sectiunea *Bot*.** Portalul creeaza user-ul bot odata cu aplicatia,
|
|
deci daca nu vezi un buton *Add Bot* e normal — intri direct in *Bot*.
|
|
3. **Ia token-ul.** *Bot* -> *Reset Token* -> copiaza. **Se arata o singura data.**
|
|
Il pui in `~/.claude-discord/env`, la `DISCORD_TOKEN=`. Fisierul e `0600` si nu e
|
|
versionat. Daca token-ul ajunge vreodata intr-un commit, reseteaza-l imediat din
|
|
portal — cine il are poate comanda infrastructura.
|
|
4. **Activeaza intents.** *Bot* -> *Privileged Gateway Intents* -> porneste
|
|
**MESSAGE CONTENT INTENT**. Fara el botul primeste mesajele goale si nu face nimic.
|
|
Dupa comutator apare jos bara **Save Changes** — daca nu o apesi, setarea NU se
|
|
salveaza (usor de ratat pe telefon). Reincarca pagina si confirma ca a ramas pornit.
|
|
Nu ai nevoie de aprobare de la Discord: verificarea e ceruta abia de la 100 de servere.
|
|
(*Server Members* si *Presence* nu sunt necesare — lasa-le oprite.)
|
|
5. **Invita botul intr-un guild PRIVAT** al tau. *OAuth2* -> *URL Generator* ->
|
|
scopes: `bot` **si** `applications.commands` -> permisiuni: *View Channel*, *Send Messages*, *Read Message History*,
|
|
*Create Public Threads*, *Send Messages in Threads*, *Attach Files*,
|
|
*Embed Links*, *Add Reactions*. Deschide URL-ul generat si alege serverul.
|
|
Bifele sunt greu de nimerit pe telefon; linkul echivalent, gata calculat
|
|
(`APPLICATION_ID` e in *General Information*):
|
|
|
|
```
|
|
https://discord.com/oauth2/authorize?client_id=APPLICATION_ID&scope=bot%20applications.commands&permissions=309237763136
|
|
```
|
|
|
|
`309237763136` = exact permisiunile de mai sus. Fara *View Channel* botul nu vede
|
|
canalul deloc, oricat de permis ar fi in allowlist. Fara scope-ul
|
|
`applications.commands` botul merge, dar inregistrarea comenzilor `/` esueaza cu
|
|
*403 Missing Access* (scrie in log linkul de reinvitare) si comenzile nu apar in lista.
|
|
Un bot deja invitat se re-invita cu acelasi link: se adauga doar scope-ul lipsa.
|
|
**Nu-l invita intr-un server cu alti oameni** — cine scrie in canalul permis
|
|
comanda direct containerul.
|
|
6. **Ia ID-urile pentru allowlist.** In Discord: *Settings* -> *Advanced* ->
|
|
**Developer Mode** pornit. Apoi click dreapta (pe telefon: apasare lunga) ->
|
|
*Copy Server ID* /
|
|
*Copy Channel ID* / *Copy User ID*. Le pui in `~/.claude-discord/env`:
|
|
`DISCORD_GUILD_IDS`, `DISCORD_CHANNEL_IDS`, `DISCORD_USER_IDS` (separate prin virgula).
|
|
**Allowlist gol = nimic permis** (fail-closed). Mesajele de la webhook-uri si de la
|
|
alti boti sunt ignorate din principiu.
|
|
7. **Pune destinatarul alertelor**: `ALERT_RECIPIENT=` in acelasi env.
|
|
8. **Verifica plafonul de cost**: `COST_CAP_USD_DAY=` (implicit `off`). Pe abonament
|
|
(Pro/Max) lasa `off`: costul raportat de CLI e doar pretul echivalent la API, nu o
|
|
factura. Pune un numar (ex. `5.00`) doar daca platesti per token.
|
|
9. Abia acum: `./ops/install.sh --start`.
|
|
|
|
---
|
|
|
|
## Operare
|
|
|
|
### Unde te uiti
|
|
|
|
```bash
|
|
systemctl --user status claude-discord # e viu?
|
|
journalctl --user -u claude-discord -n 200 # ce a facut ultima data
|
|
tail -f ~/.claude-discord/logs/bot.log # logul aplicatiei
|
|
tail -f ~/.claude-discord/logs/alerts.log # ce alerte s-au trimis / au esuat
|
|
systemctl --user restart claude-discord # repornire
|
|
```
|
|
|
|
Aceleasi lucruri, cu butoane, in **dashboard-ul de control**
|
|
([dashboard/README.md](dashboard/README.md)) — stare, restart, orfani, confirmari,
|
|
jurnale:
|
|
|
|
```
|
|
https://claude-agent.tailf7372d.ts.net/claude # din tailnet (ca /echo la moltbot)
|
|
# fara login: DASHBOARD_AUTH=off
|
|
```
|
|
|
|
Procesul e legat de `127.0.0.1:18790`; in tailnet il publica `tailscale serve`.
|
|
Fara Tailscale: `ssh -L 18790:127.0.0.1:18790 -N claude@10.0.20.171`.
|
|
|
|
| Fisier | Ce e |
|
|
|--------|------|
|
|
| `~/.claude-discord/env` | token + allowlist + limite (0600) |
|
|
| `~/.claude-discord/state.json` | sesiuni, directoare, pid-uri, cost |
|
|
| `~/.claude-discord/logs/bot.log` | stdout/stderr al botului (rotit zilnic, 14 zile) |
|
|
| `~/.claude-discord/logs/alerts.log` | jurnalul alertelor |
|
|
| `~/.claude-discord/logs/dashboard.log` | stdout/stderr al dashboard-ului de control |
|
|
| `~/.claude-discord/alerts-dedup.json` | fereastra de dedup a alertelor |
|
|
| `~/.claude-discord/approvals/` | cereri de confirmare intre hook si bot |
|
|
|
|
### Cost
|
|
|
|
Costul se vede in trei locuri: in subsolul fiecarui raspuns (turul curent + cumulat pe
|
|
fir), in `/status`, si in `state.json` la cheia `cost`. E pretul echivalent la API
|
|
raportat de CLI: pe abonament nu se factureaza, deci ramane doar o masura de consum.
|
|
|
|
Plafonul zilnic e optional. `COST_CAP_USD_DAY=off` (implicit, si `0` sau gol) inseamna
|
|
fara plafon: cheltuiala se contorizeaza mai departe, dar nimic nu se opreste. Cu un
|
|
numar pozitiv, la atingerea lui botul nu mai accepta tururi noi si trimite email;
|
|
plafonul se reseteaza la schimbarea zilei.
|
|
|
|
### Alerte pe email
|
|
|
|
`alerts.py` urmeaza tiparul deja folosit in repo (vezi
|
|
`proxmox/vm109-windows-dr/scripts/pveelite-down-alert.sh`):
|
|
|
|
```
|
|
mail -s "[LEVEL] subiect" "$ALERT_RECIPIENT" # LEVEL: INFO | WARN | CRITICAL
|
|
```
|
|
|
|
Se trimite alerta pentru: proces mort neasteptat, crash loop, plafon de cost atins,
|
|
`state.json` corupt, orfani detectati la sweep.
|
|
|
|
Doua garantii care conteaza:
|
|
|
|
- **`alert()` nu arunca niciodata exceptii.** O alerta esuata nu are voie sa doboare
|
|
botul; orice eroare ajunge in `alerts.log` si atat.
|
|
- **Dedup 1h pe `dedup_key`**, persistat pe disc. Fara el, un crash loop ar trimite
|
|
sute de emailuri identice.
|
|
|
|
**Dependinta:** binarul `mail`. Pe LXC 171 e instalat pachetul **`bsd-mailx`**
|
|
(`/usr/bin/mail`), iar transportul e **postfix**, deja prezent (`/usr/sbin/sendmail`).
|
|
Pe o masina unde lipseste:
|
|
|
|
```bash
|
|
sudo apt-get install -y bsd-mailx
|
|
```
|
|
|
|
Daca `mail` lipseste, alertele **nu se pierd**: se degradeaza la scriere in
|
|
`~/.claude-discord/logs/alerts.log`, cu tot cu corpul mesajului, si `install.sh` te
|
|
avertizeaza la instalare. Dar nimeni nu mai primeste nimic pe email — deci trateaza
|
|
lipsa lui ca pe o defectiune, nu ca pe o optiune.
|
|
|
|
Test manual, fara sa pornesti botul:
|
|
|
|
```bash
|
|
ALERT_RECIPIENT=tu@romfast.ro python3 alerts.py INFO "test punte" "corp de test"
|
|
tail -2 ~/.claude-discord/logs/alerts.log
|
|
```
|
|
|
|
### Cand pica
|
|
|
|
| Simptom | Ce faci |
|
|
|---------|---------|
|
|
| Botul nu raspunde deloc in Discord | `systemctl --user status claude-discord`. Daca e `failed`, `journalctl --user -u claude-discord -n 100`. Cauza #1: token invalid sau **MESSAGE CONTENT INTENT** oprit. |
|
|
| Botul e viu dar ignora mesajele | Allowlist. Verifica `DISCORD_GUILD_IDS` / `DISCORD_CHANNEL_IDS` / `DISCORD_USER_IDS` din env. Respingerea e **tacuta**, intentionat. |
|
|
| Comenzile `/` nu apar in lista din Discord | Botul a fost invitat fara scope-ul `applications.commands`. In log: `sync de comenzi slash esuat` + linkul de reinvitare. Reinvita botul, apoi `systemctl --user restart claude-discord`. |
|
|
| Unitul se invarte in restart | Dupa 5 porniri esuate in 300s systemd renunta si lasa unitul `failed` (e voit). Repara, apoi `systemctl --user reset-failed claude-discord && systemctl --user start claude-discord`. |
|
|
| Firul e blocat pe hourglass | Botul a fost restartat la mijlocul unui tur. Turul **nu** se reia automat (risc de dubla executie sub `bypassPermissions`); sweep-ul de la pornire pune un avertisment in fir. Trimite mesajul din nou. |
|
|
| Memoria containerului creste | `/cleanup` (sec), apoi `/cleanup force:True`. Vezi si `systemctl --user show claude-discord -p MemoryCurrent`. |
|
|
| „Plafon de cost atins" | E limita zilnica, nu o eroare. Pe abonament pune `COST_CAP_USD_DAY=off` in env si reporneste; altfel ridica valoarea sau asteapta ziua urmatoare. |
|
|
| Nu vin emailuri de alerta | `command -v mail`; `mailq`; `tail ~/.claude-discord/logs/alerts.log`. Un `NESENT` in log iti spune exact de ce. |
|
|
| Dupa reboot serviciul nu porneste | `loginctl show-user claude -p Linger` trebuie sa fie `yes`. Daca nu: `sudo loginctl enable-linger claude`. |
|
|
|
|
### Teste
|
|
|
|
```bash
|
|
cd /workspace/romfastsql/proxmox/lxc171-claude-agent/discord-bridge
|
|
python3 -m pytest -q # suita rapida, fara retea si fara Discord
|
|
python3 -m pytest -m e2e # testele care ating CLI-ul real (lente)
|
|
```
|
|
|
|
Testele de ops (`tests/test_alerts.py`, `tests/test_cleanup.py`) nu trimit email real si
|
|
nu omoara procese reale: folosesc un `mail` fals si copii de `sleep` pe care le pornesc
|
|
si le opresc ele insele.
|
|
|
|
---
|
|
|
|
## Securitate — ce e si ce nu e
|
|
|
|
Puntea ruleaza ca utilizatorul `claude`, cu `--permission-mode bypassPermissions`, si are
|
|
**exact accesul pe care il are omul in terminal**: `/workspace`, cheile SSH, nodurile
|
|
Proxmox, LXC-urile, VM-urile. Asta e **functionalitate ceruta**, nu scapare — puntea
|
|
exista tocmai ca sa poti administra infrastructura de pe telefon.
|
|
|
|
Ce apara efectiv:
|
|
|
|
1. **Control de acces pe canal.** Guild + canal + utilizator pe allowlist, gol = nimic
|
|
permis. Webhook-urile si botii sunt respinsi. Contul tau de Discord devine, practic,
|
|
o cheie de infrastructura — pune-i 2FA.
|
|
2. **Confirmare pentru operatiuni ireversibile.** Un hook `PreToolUse` opreste comanda si
|
|
posteaza butoane in fir; fara raspuns in fereastra de timp raspunsul e **deny**
|
|
(fail-closed). Verificat: a blocat un `rm -rf`, a asteptat aprobarea externa 20s si a
|
|
permis apoi executia, fara timeout.
|
|
3. **Wrapper `infra`** cu lista explicita de hosturi + token Proxmox cu ACL.
|
|
|
|
Ce **nu** apara: regulile `deny` din settings. Sub `bypassPermissions` ele sunt un strat
|
|
cosmetic — verificat, `/usr/bin/ssh -V` si `bash -c "ssh -V"` trec pe langa ele. Nu te
|
|
baza pe ele ca pe o bariera.
|
|
|
|
---
|
|
|
|
## Limitari cunoscute (asumate)
|
|
|
|
- **Nu exista jurnal de audit independent.** Stratul de audit append-only pe branch
|
|
dedicat a fost considerat si **respins constient**. Consecinta, asumata: la o problema
|
|
— o comanda distructiva care a trecut, o modificare pe care nimeni nu si-o aminteste —
|
|
**nu exista o inregistrare independenta care sa spuna ce s-a intamplat, cand si pe ce
|
|
host**. Ce ramane sunt loguri care pot fi sterse de chiar procesul care le scrie:
|
|
`bot.log`, `journalctl`, jurnalele de sesiune din `~/.claude/projects/` si istoricul
|
|
git al repo-urilor atinse. Daca vreodata conteaza „cine si ce", asta e golul de
|
|
acoperit primul.
|
|
- **Fara reluare automata a turului pierdut.** Un restart la mijlocul unui tur pierde
|
|
turul; nu se reia singur, fiindca sub `bypassPermissions` jumatate din comenzi sunt
|
|
deja executate si o reluare le-ar rula a doua oara.
|
|
- **Rate limit-ul Discord e o degradare tacuta.** Intervalul de editare se adapteaza
|
|
(1s -> 5s), dar cand Discord franeaza nu apare niciun mesaj: raspunsul doar apare mai
|
|
incet. E singura cale fara test din analiza modurilor de esec — acceptata, fiindca
|
|
esecul e intarziere, nu pierdere.
|
|
- **`/cleanup` nu e infailibil.** Domeniul lui e strict cgroup-ul serviciului, deci un
|
|
proces care a iesit complet din cgroup ii scapa — asta e pretul deliberat platit ca sa nu
|
|
atinga niciodata sesiunile interactive de pe container.
|
|
Lista `NEVER_KILL` din `cleanup.py` protejeaza infrastructura sesiunii (systemd, sshd,
|
|
tmux, code-server) — deci un proces cu un asemenea nume in linia de comanda nu va fi
|
|
oprit niciodata, chiar daca e orfan.
|
|
- **Fara voce, imagini sau atasamente** catre Claude in v1.
|
|
- **Fara dashboard web** — exista deja pe MoltBot, puntea nu-l duplica.
|
|
- **Un singur container.** Daca LXC 171 e oprit, puntea e oprita. Nu are redundanta si
|
|
nu e in HA.
|
|
|
|
---
|
|
|
|
## Fisiere
|
|
|
|
| Fisier | Ce e | Lane |
|
|
|--------|------|------|
|
|
| `bot.py` | adaptorul Discord: allowlist, comenzi, butoane | A |
|
|
| `session_store.py` | `state.json`: scriere atomica, lock per fir, PID reuse | A |
|
|
| `runner.py` | proces persistent per fir, stdin JSONL, reaper | A |
|
|
| `stream.py` | parser tolerant de stream JSONL | A |
|
|
| `render.py` | chunker + loop de editare per canal | A |
|
|
| `limits.py` | max procese, timeout, rate limit, plafon de cost | A |
|
|
| `commands_slash.py` | declararea si inregistrarea comenzilor `/` | A |
|
|
| `config.py` | citeste `~/.claude-discord/env` | A |
|
|
| `security/confirm_hook.py` | hook `PreToolUse`, fail-closed | B |
|
|
| `security/approvals.py` | canal de aprobari hook <-> bot | B |
|
|
| `security/infra` | wrapper cu lista de hosturi permise | B |
|
|
| `alerts.py` | alerte email, dedup 1h, nu arunca niciodata | C |
|
|
| `cleanup.py` | `/cleanup`: orfani, dry-run implicit | C |
|
|
| `ops/claude-discord.service` | unit systemd de utilizator | C |
|
|
| `ops/install.sh` | instalare idempotenta | C |
|
|
| `ops/env.example` | sablon de configurare | C |
|
|
| `ops/logrotate.conf` | rotatia logurilor | C |
|
|
| `dashboard/api.py` | dashboard de control (stare, restart, orfani, confirmari) | C |
|
|
| `dashboard/index.html`, `login.html`, `static/` | interfata dashboard-ului | C |
|
|
| `dashboard/claude-discord-dashboard.service` | unit systemd pentru dashboard | C |
|
|
| `INTERFACES.md` | contractul intre module | orchestrator |
|