Files
mesh-catalog/modules/fonts/cmd/fonts-tools/main.go
T
jochen 86cf6d438d fonts: the five decided faces as packages, and what the generic families mean (hq to-be 42 phase 2.1)
JetBrains Mono Nerd Font for monospace, Inter for the interface, the Nerd
Fonts symbols and Noto Color Emoji as fallbacks, Noto for serif. One owned
fontconfig file in the account's conf.d maps monospace, sans-serif,
system-ui, serif and emoji, bound `same`: measured with fontconfig 2.18, a
weakly bound preference loses to Noto Sans Mono. Families today's configs
name but the laptop lacks (Iosevka, the old JetBrains name) stop falling
back to sans-serif.

Six Go tools: families, match, glyph, sources (which hand-copied files can
go and why), config, cache-rebuild. The README lists the hand-copied files
to remove and the modules that must name the new family.
2026-10-04 13:02:23 +02:00

118 lines
4.4 KiB
Go

// The fonts module's tools (novox/hq research 026/04, 026/05): what faces the account has, what the
// generic families resolve to, which face draws a character, which font files were copied by hand
// and may go, and rebuilding the font cache. A Go bundle the node's runtime launches and speaks MCP
// to over stdio (ADR 0188, ADR 0193); it runs as the operator account.
package main
import (
"fmt"
"os"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// providedBy names what installs a program the tools run, for a failure that says so.
var providedBy = map[string]string{
"fc-list": "the fontconfig package",
"fc-match": "the fontconfig package",
"fc-scan": "the fontconfig package",
"fc-cache": "the fontconfig package",
"fc-conflist": "the fontconfig package",
"pacman": "this is not an Arch machine",
}
func main() {
if err := stdio.Serve("", tools()); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func tools() []stdio.Tool {
return []stdio.Tool{
{
Name: "fonts_families",
Description: "The font families installed for the operator account, each with its styles, how many files, " +
"whether it is monospaced, and where it comes from: package (under /usr/share/fonts), account (copied into " +
"the home by hand) or other. (r)",
Input: map[string]any{
"contains": map[string]any{"type": "string", "description": "only families whose name contains this, any case"},
"limit": map[string]any{"type": "integer", "description": "at most this many families (default 500, at most 2000)"},
},
Run: func(args map[string]any) (any, error) {
contains, err := optText(args, "contains", "")
if err != nil {
return nil, err
}
limit, err := optWhole(args, "limit", 500, 1, 2000)
if err != nil {
return nil, err
}
return Families(contains, limit)
},
},
{
Name: "fonts_match",
Description: "What the generic families resolve to for this account: monospace, sans-serif, system-ui, serif and " +
"emoji by default, or the patterns given. Each answer names the face, its file, the face the fonts module " +
"decided on, and whether they agree. (r)",
Input: map[string]any{
"patterns": map[string]any{"type": "array", "items": map[string]any{"type": "string"},
"description": "fontconfig patterns to resolve instead, such as \"monospace:bold\" or \"Inter\""},
},
Run: func(args map[string]any) (any, error) {
patterns, err := optList(args, "patterns")
if err != nil {
return nil, err
}
return Match(patterns)
},
},
{
Name: "fonts_glyph",
Description: "Which installed fonts have a given character, and which face monospace and sans-serif would draw " +
"it with. The character is given as itself or as a code point (U+F120, 0xF120). (r)",
Input: map[string]any{
"character": map[string]any{"type": "string", "description": "one character, or its code point as U+XXXX or 0xXXXX"},
},
Run: func(args map[string]any) (any, error) {
ch, err := text(args, "character")
if err != nil {
return nil, err
}
return Glyph(ch)
},
},
{
Name: "fonts_sources",
Description: "Every font file copied into the account's font directories by hand, with its family, whether it " +
"duplicates another, and whether a package now provides that family; and the families installed by " +
"packages, with the package. Says which hand-copied files can be removed and why; removes nothing. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Sources() },
},
{
Name: "fonts_config",
Description: "The fontconfig file the fonts module owns: whether it is in place, whether fontconfig loads it, " +
"and the account's other fontconfig files beside it. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Config() },
},
{
Name: "fonts_cache_rebuild",
Description: "Rebuild the font cache: the account's (default), or the system's with system: true, which needs " +
"root and goes through sudo without a prompt. Answers how long it took. (a)",
Input: map[string]any{
"system": map[string]any{"type": "boolean", "description": "rebuild the system cache instead of the account's"},
},
Run: func(args map[string]any) (any, error) {
system, err := optFlag(args, "system", false)
if err != nil {
return nil, err
}
return CacheRebuild(system)
},
},
}
}