helmd v2: Go-server som styr agent i tmux — REST+SSE, frågedetektor, fildelning, ntfy, CI-release
- tmux som sanningskälla (agentoberoende, överlever omstart via adoption) - API: sessions, prompt, question/answer, keys, mode, config, shares, notify, events - frågedetektor testad mot riktiga agy 1.1.9-dumpar + mock - integrationstest: 26 tester i isolerad debian-container (scripts/run-integration.sh) - e2e-verifierad mot riktig agy (trust-fråga -> svar -> prompt) - .gitea/workflows/helmd-release.yaml -> rullande helmd-latest (x64+arm64) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
137
helmd/main.go
Normal file
137
helmd/main.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var version = "dev" // sätts av CI via -ldflags
|
||||
|
||||
var exec_LookPath = exec.LookPath
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "serve":
|
||||
fs := flag.NewFlagSet("serve", flag.ExitOnError)
|
||||
listen := fs.String("listen", "", "override listen-adress (t.ex. 0.0.0.0:8788)")
|
||||
confPath := fs.String("config", configPath(), "sökväg till config.json")
|
||||
fs.Parse(os.Args[2:])
|
||||
cfg, err := loadConfig(*confPath)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if *listen != "" {
|
||||
cfg.Listen = *listen
|
||||
}
|
||||
if err := serve(cfg); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
case "token":
|
||||
cfg, err := loadConfig(configPath())
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
fmt.Println(cfg.Token)
|
||||
|
||||
case "share":
|
||||
fs := flag.NewFlagSet("share", flag.ExitOnError)
|
||||
server := fs.String("server", "http://127.0.0.1:8788", "helmd-serverns adress")
|
||||
note := fs.String("note", "", "beskrivning som visas i klienten")
|
||||
session := fs.String("session", "", "session som delningen hör till")
|
||||
fs.Parse(os.Args[2:])
|
||||
if fs.NArg() != 1 {
|
||||
fatal(fmt.Errorf("användning: helmd share [flaggor] <fil>"))
|
||||
}
|
||||
cfg, err := loadConfig(configPath())
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
id, err := shareFile(*server, cfg.Token, fs.Arg(0), *note, *session)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
fmt.Printf("delad: %s (id %s)\n%s/api/shares/%s/raw\n", filepath.Base(fs.Arg(0)), id, *server, id)
|
||||
|
||||
case "version", "--version", "-v":
|
||||
fmt.Println("helmd", version)
|
||||
|
||||
default:
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, `helmd — agent-helm-server: styr en CLI-agent i tmux via REST-API
|
||||
|
||||
användning:
|
||||
helmd serve [--listen host:port] [--config fil] starta servern
|
||||
helmd share [--note text] [--session namn] <fil> dela en fil till klienterna
|
||||
helmd token skriv ut API-token
|
||||
helmd version`)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, "helmd:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// shareFile uploads a file to a running helmd and returns the share id.
|
||||
func shareFile(server, token, path, note, session string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
mw := multipart.NewWriter(&buf)
|
||||
fw, err := mw.CreateFormFile("file", filepath.Base(path))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := io.Copy(fw, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
mw.WriteField("note", note)
|
||||
mw.WriteField("session", session)
|
||||
mw.Close()
|
||||
|
||||
req, err := http.NewRequest("POST", server+"/api/shares", &buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("servern svarade %s: %s", resp.Status, bytes.TrimSpace(body))
|
||||
}
|
||||
var out struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.ID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user