Files
mesh-catalog/modules/betterbird/cmd/betterbird-tools/main.go
T
jschoubben a2a79d3907 betterbird: a module for the mail and calendar client, so its windows and preferences can be seen and declared (hq issue 344)
A calendar reminder showed as a transparent, empty window on shanks and no tool could say which
window it was or which preferences Betterbird ran with. The module owns one file, the user.js its
setting prefs fills, which its tools place whole into the profile Betterbird uses and never over an
operator's own; it reads named preferences without credentials or accounts, restarts Betterbird under
the user manager so its output reaches the journal, checks what it promises, and shows a reminder on
demand from a throw-away profile so a fix can be proved without the operator's calendar. No package:
betterbird-bin is the AUR's.
2026-10-09 12:05:56 +02:00

148 lines
6.8 KiB
Go

// 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 <hidden>. 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 20 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. An unsent message in a composer is lost: ask " +
"the operator first. Answers the pids ended and the new main process. (a)",
Run: func(map[string]any) (any, error) { return machine.Restart() },
},
{
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)
},
},
}
}