Files
agent-helm/helmd/README.md
Bjorn Blomberg 77682e91b5
All checks were successful
helmd-image / build (push) Successful in 3m22s
helmd-release / build-release (push) Successful in 4m28s
helmd hub: central server på Pi5 + agent-uplink + helmd ctl + web-login
Hub-läge i samma binär (doc/hub-plan.md): agenter ansluter utåt med
delad agent-nyckel (hub-sektion i config.json), web-/CLI-klienter
loggar in (lokala konton, pbkdf2; Authenticator-interface för LDAP
senare) och styr alla agenters sessioner via proxy + merged SSE.
Webbklienten autodetekterar hub/local via /api/mode. Nya kommandon:
helmd hub [setpass|users|agentkey], helmd ctl (login/agents/sessions/
prompt/screen/answer/events …). Enhetstester + scripts/run-hub-smoke.sh
(15-stegs e2e: hub + agent + ctl).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KikHkfCiC3yELbsMN8fT9
2026-08-06 23:19:18 +02:00

9.7 KiB

helmd — agent-helm v2-servern

En enskild statisk Go-binär som äger kommunikationen med en tmux-session där en CLI-agent (agy, claude, …) kör, och exponerar ett REST-API + SSE som webb- och mobilklienterna pratar med.

Designen bygger på insikten från Gemini-OAuth-döden: att integrera mot en specifik agents hook-mekanism är skört. helmd behandlar i stället tmux som sanningskälla — den läser skärmen, känner igen frågor och skickar tangenter. Fungerar därför med vilken TUI-agent som helst, och överlever att helmd själv startas om (sessioner adopteras tillbaka).

  webb-PWA / Android ──(LAN eller SSH-tunnel)──► helmd :8788 ──tmux──► agy
                                                    │
                                                    └──► ntfy.brasse-pc.eu (pushar till mobilen)

Säkerhetsmodell

  • Ingen publik exponering. Servern binder 127.0.0.1:8788 som default. På det privata nätet kan man binda 0.0.0.0:8788 (helmd serve --listen 0.0.0.0:8788). Utifrån: SSH-tunnel — ssh -L 8788:127.0.0.1:8788 <host> — eller Android-klientens inbyggda tunnel (steg 3 i planen).
  • Bearer-token på alla endpoints. Genereras vid första start, lagras 0600 i ~/.config/helmd/config.json, visas med helmd token. Skickas som Authorization: Bearer <t>, X-Api-Token: <t> eller ?token=<t> (för SSE/EventSource och <img>-länkar).
  • Delad HTML serveras med Content-Security-Policy: sandbox så att ett delat dokument aldrig kan anropa API:et med klientens token.

Kommandon

helmd serve [--listen host:port] [--config fil]   starta agent-servern
helmd share [--note text] [--session namn] <fil>  dela en fil till klienterna
helmd token                                       skriv ut API-token
helmd hub [--listen host:port] [--config fil]     starta hubben (Pi5)
helmd hub setpass <användare> [--password PW]     sätt/skapa hub-konto
helmd hub users                                   lista hub-konton
helmd hub agentkey                                skriv ut agent-nyckeln
helmd ctl <kommando>                              CLI-styrklient mot hubben
helmd version

Hubben (helmd hub)

Central server (Pi5) som agenter ansluter utåt till och där web- och CLI-klienter loggar in och styr alla agenters sessioner. Full design och beslut: ../doc/hub-plan.md.

  • Agent-anslutning: ingen inloggning/konto — agenten skickar den delade agent-nyckeln (agent_key i hubbens config) vid registrering. Hämta den med helmd hub agentkey på hubben, eller efter inloggning med helmd ctl agentkey / Inställningar-fliken i webben. Transporten är long-poll över HTTP (/hub/register, /hub/work, /hub/result, /hub/events) — agenten kör jobben mot sin egen lokala mux, så hela API-tabellen ovan funkar oförändrad genom hubben. Agenter ser aldrig något om varandra.

  • Klient-login: POST /api/login med användarnamn + lösenord → Bearer-token. Konton ligger lokalt i hub-konfigen (pbkdf2-sha256); Authenticator-interfacet i hubauth.go är utbytespunkten för LDAP/SSO längre fram. Första starten seedas admin (lösenord från HELMD_HUB_ADMIN_PASSWORD eller slumpat — skrivs ut i loggen).

  • Klient-API: GET /api/agents (lista + online-status), ANY /api/agents/{namn}/… (proxas till agentens /api/…), GET /api/events (merged SSE, fältet agent på varje event), GET /api/agentkey, GET/POST /api/login|logout|me. GET /api/mode (oautentiserad) svarar hub/local så att den inbäddade webbklienten kan välja login-form — i hub-läge får den användarnamn/lösenord och en agentväljare.

  • Agentens config (config.json) pekar ut hubben:

    "hub": {
      "url": "http://192.168.0.19:8790",
      "name": "brasse-linux01",
      "key": "<agent_key>",
      "enabled": true
    }
    

    Det lokala API:t på :8788 fortsätter fungera parallellt — hubben nere betyder bara att uplinken backar och försöker igen.

  • Hubbens config (~/.config/helmd/hub.json, HELMD_HUB_CONFIG överstyr): listen (default 127.0.0.1:8790), auth (local), users, agent_key, session_ttl_hours (default 168).

  • CLI-klienten: helmd ctl login --server http://192.168.0.19:8790ctl agents, ctl sessions <agent>, ctl new <agent> <namn>, ctl screen|prompt|question|answer|keys|kill <agent> <session> …, ctl shares <agent>, ctl events. Sparar server + token i ~/.config/helmd/ctl.json.

  • Smoke-test av hela flödet: bash scripts/run-hub-smoke.sh (hub + agent + ctl lokalt, 15 kontroller).

helmd share är agentens verktyg för att dela filer (html, md, png, jpg, …) till klienterna: agenten kör kommandot i sin shell, filen laddas upp till servern och dyker upp i klientens delningsflik. Tillåtna filtyper styrs av share_exts i konfigen.

API

Alla svar är JSON om inget annat sägs. Auth krävs överallt.

Metod & väg Gör
GET /api/health {ok, version, sessions}
GET /api/sessions lista sessioner (namn, alive, seq, pending question)
POST /api/sessions {name, cmd?, cwd?} startar agent i ny tmux-session helm-<name>; {name, adopt:true, tmux?} adopterar befintlig
GET /api/sessions/{n} en sessions tillstånd
DELETE /api/sessions/{n} döda sessionen
GET /api/sessions/{n}/screen pane-text; ?ansi=1 behåller färger, ?history=N tar med N rader scrollback
POST /api/sessions/{n}/prompt {text, submit?:bool} klistrar in text (radbrytningar ok) och trycker Enter
GET /api/sessions/{n}/question {question} — detekterad väntande fråga eller null
POST /api/sessions/{n}/answer {option:N} väljer alternativ N (1-baserat). Numrerade listor väljs med siffertangent, onumrerade navigeras med pil+Enter
POST /api/sessions/{n}/keys {keys:["Escape","Down","Enter"]} råa tmux-tangenter
POST /api/sessions/{n}/mode {action:"cycle"} skickar Shift+Tab (lägesbyte i agy/claude)
GET /api/config / PUT /api/config läs/ändra agentkommando, args, workdir, poll-intervall, ntfy-inställningar (token ändras aldrig via API)
GET /api/shares / POST /api/shares lista / ladda upp (multipart, fält file, note, session)
GET /api/shares/{id} / …/raw metadata / själva filen
DELETE /api/shares/{id} ta bort delning
POST /api/notify {topic?, title?, message, priority?} → ntfy; topic måste finnas i allowed_topics (agent-helm, claude)
GET /api/events SSE-ström: screen (seq), question, session, share

Frågedetektering

Pollern läser panen (default var 500:e ms), och DetectQuestion känner igen agy:s frågeformer: godkännande-listor (1. Yes … 4. No), trust-/val-listor med >-markör, och gamla Gemini-CLI:s inramade pickers. Varje fråga får en stabil hash — servern ntfy:ar en gång per ny fråga till topic agent-helm (kan stängas av med notify_on_question:false).

Verifierat mot riktiga agy 1.1.9-sessioner (trust-prompt, kommandogodkännande) och mot mock i integrationstestet.

Konfiguration

~/.config/helmd/config.json (skapas vid första start; override med HELMD_CONFIG eller --config):

{
  "listen": "127.0.0.1:8788",
  "token": "<genereras>",
  "agent_cmd": "agy",
  "agent_args": [],
  "workdir": "/home/brasse",
  "share_dir": "~/.local/share/helmd/shares",
  "share_exts": [".html", ".htm", ".md", ".txt", ".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".pdf"],
  "poll_ms": 500,
  "cols": 200, "rows": 50,
  "ntfy": {
    "server": "https://ntfy.brasse-pc.eu",
    "topic": "agent-helm",
    "allowed_topics": ["agent-helm", "claude"],
    "enabled": true,
    "notify_on_question": true
  }
}

Webbklienten

Inbäddad i binären (web/ + go:embed) och serveras på / — surfa till servern (t.ex. http://127.0.0.1:8788 genom en SSH-tunnel) och klistra in din token. Ramverksfri (ren ES-moduler): sessionslista, ANSI-färgad skärmvy (SSE + poll), promptfält, frågekort med knappar, tangent-verktygsrad, delningsflik (bilder, sandboxad HTML, markdown) och inställningar. Statiska filer kräver ingen auth (koden är publik); allt data går genom det token-skyddade API:et.

Docker på Pi5 (LAN-only)

Dockerfile bygger en arm64-image med tmux+bash via .gitea/workflows/helmd-image.yamllocalhost:5000/helmd:latest. Stack: /srv/dockge-staks/helmd/ med host-port 8788 och volym /srv/docker/helmd → /data. Ingen NPM-vhost — nås bara på LAN (http://192.168.0.19:8788) eller via SSH-tunnel utifrån. Sessionerna kör i containern (bash tills en agent installeras där).

Bygga & testa

cd helmd
go test ./...                       # enhetstester (frågedetektorn m.m.)
CGO_ENABLED=0 go build -o build/helmd .
bash scripts/run-integration.sh    # 26 API-tester i isolerad Docker-container

Integrationstestet startar helmd i en debian:stable-slim-container med en mock-agent (testdata/mock-agent.sh) som härmar agy:s båda frågetyper, och kör hela API-ytan med curl.

CI / release

.gitea/workflows/helmd-release.yaml kör på push till master som rör helmd/: go vet + go test, bygger linux x64 + arm64 statiskt och publicerar på den rullande releasen helmd-latest (helmd-linux-x64, helmd-linux-arm64, checksums.txt).

Kända begränsningar (v1)

  • mode cycle skickar Shift+Tab (BTab) — verifierat tangentnamn i tmux men inte bekräftat att agy byter läge på det; justera via /keys om agy använder annan tangent.
  • Ingen WebSocket-terminal ännu — klienterna pollar screen + lyssnar på SSE. Räcker för kort-UI:t; rå terminal-vy kommer med webbklienten (steg 2).
  • En helmd-instans per maskin/användare; flera agenter = flera sessioner i samma helmd.