Each sensitive vault verb judges its own caller, and nox reaches the vault as its machine's runtime, never as a person, so it could use none of them. Before rotate, retire approve, cleanup delete and secret export nox records the act through mesh-hand-acts (the vault verb as its verb, what it acts on as its what) and names it as act; a refused recording calls the vault not at all (ADR 0590 §8, issue 1126 review 1). Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
246 lines
8.2 KiB
Go
246 lines
8.2 KiB
Go
// Command mesh-cli is the operator's command on every node: `mesh-cli <command line>`, in interactive zsh also `nox`.
|
|
//
|
|
// Each line calls the seat verb or the module tool that owns its job (novox/hq issue 1126: the controller that ran
|
|
// these lines before is unassigned), through this machine's console, the mesh MCP server on loopback (console.go).
|
|
// A line no seat or module owns says so and names what to use instead. `enrol open` runs here and asks nothing.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"runtime/debug"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// standardInput is mesh-cli's own standard input, and whether it is a terminal (or a device such as /dev/null); a
|
|
// variable so a test can say.
|
|
var standardInput = func() (io.Reader, bool) {
|
|
info, err := os.Stdin.Stat()
|
|
if err != nil {
|
|
return os.Stdin, true
|
|
}
|
|
return os.Stdin, info.Mode()&os.ModeCharDevice != 0
|
|
}
|
|
|
|
func main() {
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
code := run(ctx, os.Args[1:], os.Stdout, os.Stderr)
|
|
stop()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|
if len(args) == 0 || args[0] == "-h" || args[0] == "--help" || args[0] == "help" {
|
|
fmt.Fprint(stderr, usage())
|
|
if len(args) == 0 {
|
|
return 2
|
|
}
|
|
return 0
|
|
}
|
|
// A verb's own process carries the verb it runs for; a verb is never a person.
|
|
if verb := os.Getenv("MESH_VERB"); verb != "" {
|
|
fmt.Fprintf(stderr, "mesh-cli: this process runs for the verb %q, and a verb is never the operator. "+
|
|
"Nothing was called\n", verb)
|
|
return 1
|
|
}
|
|
switch args[0] {
|
|
case "version":
|
|
fmt.Fprintln(stdout, version())
|
|
return 0
|
|
case "search":
|
|
if len(args) < 2 {
|
|
fmt.Fprintln(stderr, "mesh-cli: give the words: nox search <words>")
|
|
return 2
|
|
}
|
|
return show(ctx, "mesh_search", map[string]any{"query": strings.Join(args[1:], " ")}, nil, stdout, stderr)
|
|
case "describe":
|
|
if len(args) != 2 {
|
|
fmt.Fprintln(stderr, "mesh-cli: give one address: nox describe <address>")
|
|
return 2
|
|
}
|
|
return show(ctx, "mesh_describe", map[string]any{"address": args[1]}, nil, stdout, stderr)
|
|
}
|
|
// `enrol open` is local: it opens a sealed enrolment token with the operator's private key and asks nothing of the
|
|
// mesh (novox/hq ADR 0587 §8).
|
|
if len(args) >= 2 && args[0] == "enrol" && args[1] == "open" {
|
|
in, terminal := standardInput()
|
|
return enrolOpen(args[2:], in, terminal, stdout, stderr)
|
|
}
|
|
c, rest := find(args)
|
|
if c == nil {
|
|
if instead, ok := unowned[args[0]]; ok {
|
|
return noOwner(args[0], instead, stderr)
|
|
}
|
|
fmt.Fprintf(stderr, "mesh-cli: %q is not a command. nox help lists them; nox search <words> finds a seat verb "+
|
|
"or a tool, and nox call <address> calls it\n", args[0])
|
|
return 2
|
|
}
|
|
if c.build == nil {
|
|
return noOwner(strings.Join(c.words, " "), c.instead, stderr)
|
|
}
|
|
k, err := c.read(rest)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "mesh-cli: %v. Nothing was called\n nox %s %s\n", err, strings.Join(c.words, " "), c.usage)
|
|
return 2
|
|
}
|
|
if h, ok := k.Args["act"].(*handAct); ok {
|
|
id, code := recordAct(ctx, *h, stderr)
|
|
if code != 0 {
|
|
return code
|
|
}
|
|
k.Args["act"] = id
|
|
}
|
|
if k.Address == "mesh-delivery-view.view_status" {
|
|
return status(ctx, stdout, stderr)
|
|
}
|
|
return show(ctx, "mesh_call", map[string]any{"address": k.Address, "arguments": k.Args}, c.render, stdout, stderr)
|
|
}
|
|
|
|
// recordAct records the hand act a sensitive vault verb is warranted by, through mesh-hand-acts, and answers its id;
|
|
// a recording refused or unread calls the vault not at all (novox/hq ADR 0590 §8).
|
|
func recordAct(ctx context.Context, h handAct, stderr io.Writer) (string, int) {
|
|
a, err := meshCall(ctx, "mesh-hand-acts.hand-act", map[string]any{"verb": h.Verb, "what": h.What, "why": h.Why,
|
|
"cause": "operator"})
|
|
if err != nil {
|
|
return "", failed(err, stderr)
|
|
}
|
|
if a.IsError {
|
|
fmt.Fprintln(stderr, "mesh-cli: the hand act was not recorded, so "+h.Verb+" was not called: "+strings.TrimRight(a.Text, "\n"))
|
|
return "", 1
|
|
}
|
|
var rec struct {
|
|
Act struct {
|
|
ID string `json:"id"`
|
|
} `json:"act"`
|
|
}
|
|
if err := json.NewDecoder(strings.NewReader(a.Text)).Decode(&rec); err != nil || rec.Act.ID == "" {
|
|
fmt.Fprintf(stderr, "mesh-cli: mesh-hand-acts answered no act's id (%q), so %s was not called\n", a.Text, h.Verb)
|
|
return "", 1
|
|
}
|
|
return rec.Act.ID, 0
|
|
}
|
|
|
|
// noOwner says a line has no owner since the controller went, and what to use.
|
|
func noOwner(what, instead string, stderr io.Writer) int {
|
|
fmt.Fprintf(stderr, "mesh-cli: nox %s: %s.\n", what, gone)
|
|
if instead != "" {
|
|
fmt.Fprintf(stderr, " instead: %s\n", instead)
|
|
} else {
|
|
fmt.Fprintln(stderr, " no seat or module does this now; nox search <words> finds what does nearby")
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// show calls one of the console's tools and prints what it answered: an answer marked an error on standard error, with
|
|
// exit 1.
|
|
func show(ctx context.Context, tool string, args map[string]any, render func(string) string, stdout, stderr io.Writer) int {
|
|
a, err := consoleTool(ctx, tool, args)
|
|
if err != nil {
|
|
return failed(err, stderr)
|
|
}
|
|
if a.IsError {
|
|
fmt.Fprintln(stderr, "mesh-cli: "+strings.TrimRight(a.Text, "\n"))
|
|
return 1
|
|
}
|
|
text := a.Text
|
|
if render != nil {
|
|
text = render(text)
|
|
}
|
|
fmt.Fprintln(stdout, strings.TrimRight(text, "\n"))
|
|
return 0
|
|
}
|
|
|
|
// failed says why a call did not come back, and never as nothing having run unless nothing answered.
|
|
func failed(err error, stderr io.Writer) int {
|
|
switch {
|
|
case errors.Is(err, context.Canceled):
|
|
fmt.Fprintln(stderr, "mesh-cli: stopped waiting; the call may still run")
|
|
return 130
|
|
case errors.Is(err, context.DeadlineExceeded):
|
|
fmt.Fprintln(stderr, "mesh-cli: no answer in time; the call may have run. Check before repeating a change")
|
|
return 1
|
|
}
|
|
fmt.Fprintf(stderr, "mesh-cli: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
// status is the delivery view's status. When the view does not answer, that is said first and loudly, and what the
|
|
// condition keeper and mesh-delivery hold is shown in its place, each under its address, with exit 1: never shown as
|
|
// the status.
|
|
func status(ctx context.Context, stdout, stderr io.Writer) int {
|
|
a, err := meshCall(ctx, "mesh-delivery-view.view_status", nil)
|
|
if err == nil && !a.IsError {
|
|
fmt.Fprintln(stdout, strings.TrimRight(a.Text, "\n"))
|
|
return 0
|
|
}
|
|
if errors.Is(err, errNoConsole) || errors.Is(err, context.Canceled) {
|
|
return failed(err, stderr)
|
|
}
|
|
why := a.Text
|
|
if err != nil {
|
|
why = err.Error()
|
|
}
|
|
fmt.Fprintf(stderr, "mesh-cli: the status (mesh-delivery-view.view_status) did not answer: %s\n"+
|
|
"mesh-cli: what follows is not the status: the open conditions and the deliveries, read on their own\n",
|
|
strings.TrimRight(why, "\n"))
|
|
for _, address := range []string{"mesh-conditions.conditions", "mesh-delivery.deliveries"} {
|
|
fmt.Fprintf(stdout, "== %s\n", address)
|
|
show(ctx, "mesh_call", map[string]any{"address": address, "arguments": map[string]any{}}, nil, stdout, stderr)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// version is what this binary was built from.
|
|
func version() string {
|
|
info, ok := debug.ReadBuildInfo()
|
|
if !ok {
|
|
return "mesh-cli (unknown build)"
|
|
}
|
|
v := "mesh-cli " + info.Main.Version
|
|
for _, s := range info.Settings {
|
|
if s.Key == "vcs.revision" {
|
|
v += " " + s.Value
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// usage is every line nox takes, grouped by its first word.
|
|
func usage() string {
|
|
var b strings.Builder
|
|
b.WriteString(`mesh-cli: the operator's command (nox in interactive zsh)
|
|
|
|
Each line calls the seat verb or module tool that owns the job, through this machine's console (the mesh MCP
|
|
server on loopback). The controller that ran these lines before is unassigned (novox/hq issue 1126).
|
|
|
|
`)
|
|
lines := make([]string, 0, len(commands))
|
|
for _, c := range commands {
|
|
if c.build == nil {
|
|
continue
|
|
}
|
|
lines = append(lines, fmt.Sprintf(" nox %s %s\n %s", strings.Join(c.words, " "), c.usage, c.does))
|
|
}
|
|
sort.Strings(lines)
|
|
b.WriteString(strings.Join(lines, "\n"))
|
|
b.WriteString(`
|
|
nox search <words>
|
|
find a seat verb or a module tool
|
|
nox describe <address>
|
|
one address's arguments
|
|
nox enrol open --key <file>
|
|
open an enrolment token sealed to the operator key, here; asks nothing of the mesh
|
|
nox version
|
|
|
|
Every answer is the owner's own, as JSON. A line the controller ran that no seat owns says so and what to use.
|
|
`)
|
|
return b.String()
|
|
}
|