// The betterbird module's Go tools bundle (novox/hq ADR 0188, ADR 0193, ADR 0208): Betterbird, the // mail and calendar client, in the operator's session, served by the node's runtime as the operator // account. The module holds no seat, so every tool is its own. // // The operator's mail, calendars, accounts and credentials are never read: a tool reads processes, // profiles.ini, and of the profile only prefs.js, user.js and extensions.json, and answers no // preference that can hold a credential or names an account. package main import ( "fmt" "os" "time" stdio "git.novox.be/novox/mesh-sdk/go" ) func main() { if err := stdio.Serve("", tools()); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } var machine = NewMachine() func tools() []stdio.Tool { return []stdio.Tool{ { Name: "betterbird_status", Description: "Betterbird: whether it runs (the main process's pid, since, and the scope it runs in; how " + "many child processes), the installed package and version and whether it is from the AUR, where its " + "output goes, the profile it uses and how that was found, the module's user.js (declared, placed, in " + "effect), the add-ons the operator installed, and the preferences that decide how its windows and " + "notifications look (reminder dialog, theme, renderer, system notifications). (r)", Run: func(map[string]any) (any, error) { return machine.Status() }, }, { Name: "betterbird_prefs", Description: "Named preferences of Betterbird's profile: each one's value and where it comes from (user.js, " + "which wins at the start; prefs.js; or Betterbird's default). A name ending in . or * is a prefix " + "(at most 200 answered). A preference whose name can hold a credential, or that names an account, a " + "server, a calendar or a person, is answered without its value. (r)", Input: map[string]any{ "names": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "preference names or prefixes, e.g. [\"calendar.alarms.\", \"extensions.activeThemeID\"]"}, }, Run: func(args map[string]any) (any, error) { names, err := texts(args, "names") if err != nil { return nil, err } return machine.Prefs(names) }, }, { Name: "betterbird_user_js", Description: "The module's user.js — the preferences the module declares, from its setting prefs, written by " + "the mesh to ~/.config/betterbird/user.js — against the profile's user.js: whether it is placed, and " + "whether the running Betterbird started after it was. With apply, place it in the profile (only where " + "the profile has no user.js or the module's own; an operator's file is never overwritten). Betterbird " + "reads it at its next start: betterbird_restart. (r; a with apply)", Input: map[string]any{ "apply": map[string]any{"type": "boolean", "description": "place the declared user.js in the profile (default false)"}, }, Run: func(args map[string]any) (any, error) { apply, err := flag(args, "apply", false) if err != nil { return nil, err } return machine.userJS(apply) }, }, { Name: "betterbird_log", Description: "The last lines Betterbird printed, from the journal of the unit the module starts it as " + "(mesh-betterbird; or mesh-betterbird-reminder-test). With problems, only lines that say error, " + "warning, assertion, crash or failure. Mail addresses and credentials are replaced by . A " + "Betterbird the module did not start prints elsewhere, and the answer says so. (r)", Input: map[string]any{ "lines": map[string]any{"type": "integer", "description": "how many lines (default 100, at most 2000)"}, "problems": map[string]any{"type": "boolean", "description": "only problem lines (default false)"}, "unit": map[string]any{"type": "string", "enum": []string{restartAs, testUnit}, "description": "which start's journal (default " + restartAs + ")"}, }, Run: func(args map[string]any) (any, error) { n, err := whole(args, "lines", 100, 1, 2000) if err != nil { return nil, err } problems, err := flag(args, "problems", false) if err != nil { return nil, err } unit, err := text(args, "unit", false) if err != nil { return nil, err } return machine.Log(n, problems, unit) }, }, { Name: "betterbird_restart", Description: "End Betterbird (asked to quit, forced after 10 s), place the module's user.js when the profile " + "allows it, and start Betterbird again in the operator's desktop session under the account's service " + "manager (unit mesh-betterbird), its output in the journal. Refused while a message is being written " + "(a composer window is open), because the unsent message would be lost, unless discard_unsent is true — " + "ask the operator first. Answers the pids ended and the new main process. (a)", Input: map[string]any{ "discard_unsent": map[string]any{"type": "boolean", "description": "restart even with a composer open, losing what it holds (default false)"}, }, Run: func(args map[string]any) (any, error) { discard, err := flag(args, "discard_unsent", false) if err != nil { return nil, err } return machine.Restart(discard) }, }, { Name: "betterbird_check", Description: "Check what the module promises: Betterbird is installed; one runs; its profile is found; the " + "module's user.js is on the machine, valid, placed in the profile and in effect; its output is read " + "(a pipe nobody reads loses what it prints). Answers ok and each finding with what to do. (r)", Run: func(map[string]any) (any, error) { return machine.Check() }, }, { Name: "betterbird_reminder_test", Description: "Show a calendar reminder without touching the operator's calendar: a second Betterbird in a " + "throw-away profile under the runtime directory (--no-remote, unit mesh-betterbird-reminder-test, at most " + "10 minutes), with the module's user.js and one local calendar file holding one event whose reminder is " + "due after `after` seconds. carry copies named preferences of the operator's profile (a theme, a " + "colour switch) into it, never a credential or an account. Then xorg_windows (class Calendar) and " + "xorg_screenshot show the reminder window. stop ends it and removes the profile. (a)", Input: map[string]any{ "after": map[string]any{"type": "integer", "description": "seconds until the reminder is due (default 40, 15 to 300)"}, "carry": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "preference names to copy from the operator's profile, e.g. [\"extensions.activeThemeID\"]"}, "stop": map[string]any{"type": "boolean", "description": "end the test Betterbird and remove its profile (default false)"}, }, Run: func(args map[string]any) (any, error) { after, err := whole(args, "after", 40, 15, 300) if err != nil { return nil, err } carry, err := texts(args, "carry") if err != nil { return nil, err } stop, err := flag(args, "stop", false) if err != nil { return nil, err } return machine.ReminderTest(time.Duration(after)*time.Second, carry, stop) }, }, } }