mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-08 05:23:28 -05:00
Chats and agents can check a PiKVM (kvmd) device and, when a person confirms each call, press the power buttons of the machine it controls and use its keyboard. Read tools: get-info (model, versions, no serial or host name), get-power-state (ATX LEDs) and get-keyboard-state. Write tools: power-action (on, off, reset; off_hard, reset_hard and long_press only with a per-device switch), type-text (bounded, no control characters, refuses key, token and password-list shapes) and send-key (a short allowlist, no Windows or Meta chords). All three declare approval always. The preview reads the power state; apply reads it again and rejects a stale operation. The add-on journals before it sends, never retries and reports unknown when the connection drops. One connection is one device: the address is the single resource of the connection and the one sealed credential holds user:password, split at the first colon. A one-time code is not supported. The add-on speaks only to that address, refuses redirects, uses no proxy and accepts a self-signed certificate only through a #sha256= pin in the address. Typing, the text length and hard power actions are per-device switches that an administrator sets on the add-on page; all start off. Snapshots, mass storage, GPIO and the mouse are left out. The add-on ships a skill, a connection page with a Test button and approval cards, the package files, the library entry and the docs, and joins the package workflow and the case inventory. PiKVM API reference: https://docs.pikvm.org/api/ Assisted-by: Claude:claude-sonnet-5-5 go node
98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
// SPDX-License-Identifier: MIT
|
|
package tools
|
|
|
|
import "net/url"
|
|
|
|
// atxState is GET /api/atx.
|
|
type atxState struct {
|
|
Enabled bool `json:"enabled"`
|
|
Busy bool `json:"busy"`
|
|
Leds struct {
|
|
Power bool `json:"power"`
|
|
HDD bool `json:"hdd"`
|
|
} `json:"leds"`
|
|
}
|
|
|
|
func (s atxState) power() string {
|
|
if s.Leds.Power {
|
|
return "on"
|
|
}
|
|
return "off"
|
|
}
|
|
|
|
func (s atxState) view() map[string]any {
|
|
return map[string]any{"enabled": s.Enabled, "busy": s.Busy, "power": s.power(), "diskActivity": s.Leds.HDD}
|
|
}
|
|
|
|
func (c *call) atx() (atxState, error) {
|
|
var s atxState
|
|
return s, c.r.API.GetJSON(c.ctx, c.dev, c.cred, []string{"api", "atx"}, nil, &s)
|
|
}
|
|
|
|
// hidState is GET /api/hid.
|
|
type hidState struct {
|
|
Enabled bool `json:"enabled"`
|
|
Online bool `json:"online"`
|
|
Busy bool `json:"busy"`
|
|
Keyboard struct {
|
|
Online bool `json:"online"`
|
|
Leds struct {
|
|
Caps bool `json:"caps"`
|
|
Num bool `json:"num"`
|
|
Scroll bool `json:"scroll"`
|
|
} `json:"leds"`
|
|
} `json:"keyboard"`
|
|
}
|
|
|
|
func (s hidState) view() map[string]any {
|
|
return map[string]any{"enabled": s.Enabled, "online": s.Online, "busy": s.Busy, "keyboardOnline": s.Keyboard.Online,
|
|
"leds": map[string]bool{"caps": s.Keyboard.Leds.Caps, "num": s.Keyboard.Leds.Num, "scroll": s.Keyboard.Leds.Scroll}}
|
|
}
|
|
|
|
func (c *call) hid() (hidState, error) {
|
|
var s hidState
|
|
return s, c.r.API.GetJSON(c.ctx, c.dev, c.cred, []string{"api", "hid"}, nil, &s)
|
|
}
|
|
|
|
func str(m map[string]any, path ...string) string {
|
|
var cur any = m
|
|
for _, p := range path {
|
|
mm, ok := cur.(map[string]any)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
cur = mm[p]
|
|
}
|
|
s, _ := cur.(string)
|
|
return short(s, 60)
|
|
}
|
|
|
|
// info returns what identifies the device: model, platform and versions. The
|
|
// serial number, the host name and the network are left out.
|
|
func (c *call) info() (map[string]any, error) {
|
|
var raw map[string]any
|
|
if err := c.r.API.GetJSON(c.ctx, c.dev, c.cred, []string{"api", "info"}, url.Values{"fields": {"hw,system"}}, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]any{
|
|
"model": str(raw, "hw", "platform", "model"),
|
|
"base": str(raw, "hw", "platform", "base"),
|
|
"type": str(raw, "hw", "platform", "type"),
|
|
"board": str(raw, "hw", "platform", "board"),
|
|
"video": str(raw, "hw", "platform", "video"),
|
|
"version": str(raw, "system", "kvmd", "version"),
|
|
"streamer": str(raw, "system", "streamer", "version"),
|
|
"kernel": str(raw, "system", "kernel", "release"),
|
|
}
|
|
if hw, ok := raw["hw"].(map[string]any); ok {
|
|
if h, ok := hw["health"].(map[string]any); ok {
|
|
if t, ok := h["temp"].(map[string]any); ok {
|
|
if cpu, ok := t["cpu"].(float64); ok {
|
|
out["cpuTempC"] = cpu
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|