// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // A host diagnostic tool for external Tentacles, independent of the running reader. package main import ( "context" "encoding/json" "errors" "flag" "fmt" "os" "time" "git.stormux.org/storm/cthulhu/internal/tentacle" ) func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run() (runError error) { options := flag.NewFlagSet("host", flag.ContinueOnError) cgroupParent := options.String("cgroup-parent", "", "delegated cgroup v2 parent for resource containment") presentation := options.Bool("presentation", false, "grant command speech/braille result capability (prints JSON only)") interactive := options.Bool("interaction", false, "grant internal form/clipboard/focused-item capability (prints initial JSON only)") command := options.String("command", "echo", "registered command to invoke") if err := options.Parse(os.Args[1:]); err != nil { return err } if options.NArg() < 1 { return fmt.Errorf("usage: host [-cgroup-parent /delegated/parent] /absolute/program [arguments...]") } var manager *tentacle.Manager var err error if *cgroupParent == "" { manager, err = tentacle.NewManager(tentacle.DefaultLimits(), nil) } else { manager, err = tentacle.NewContainedManager(tentacle.DefaultLimits(), nil, *cgroupParent, tentacle.DefaultResourceLimits()) } if err != nil { return err } defer func() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() runError = errors.Join(runError, manager.Shutdown(ctx)) }() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() session, err := manager.Start(ctx, tentacle.Spec{ID: "local-example", Program: options.Args(), Presentation: *presentation, Interaction: *interactive}) if err != nil { return err } select { case <-session.Ready(): case <-ctx.Done(): return ctx.Err() } if state, err := session.Status(); state != tentacle.Ready { return fmt.Errorf("startup %s: %v", state, err) } if err := session.Activate(); err != nil { return err } pending, err := session.Invoke(ctx, *command, json.RawMessage(`{"text":"Hello, é猫😀é"}`)) if err != nil { return err } select { case result := <-pending: if result.Err != nil { return result.Err } fmt.Println(string(result.Value)) return nil case <-ctx.Done(): return ctx.Err() } }