// systemd's tools: the node-service-manager seat's eight verbs — the units on this machine in // both scopes, read and acted on by name — and the module's own reading of what has failed // (novox/hq ADR 0177). Served by the node tools runtime (ADR 0175); the host applies units, this // answers about them. import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { ServiceManager, type Scope } from "../client.js"; const scope = { type: "string", description: "\"system\" (the default) or \"user\": the operator account's own manager" }; const unit = { type: "string", description: "the unit's name, as the service manager knows it" }; function scopeOf(args: Readonly>): Scope { const s = String(args.scope ?? "system"); if (s !== "system" && s !== "user") throw new Error(`scope ${JSON.stringify(s)}: "system" or "user"`); return s; } function unitOf(args: Readonly>): string { const u = String(args.unit ?? "").trim(); if (!u) throw new Error("a unit is required"); return u; } export function getSeatVerbs(manager: ServiceManager): ToolDefinition[] { const act = (verb: "start" | "stop" | "restart" | "enable" | "disable", description: string): ToolDefinition => ({ name: verb, description, input: { type: "object", properties: { scope, unit }, required: ["unit"] }, run: async (args) => manager.act(scopeOf(args), verb, unitOf(args)), }); return [ { name: "units", description: "The units the service manager knows in a scope, each with its load, active and sub state; narrowed to a pattern when asked.", input: { type: "object", properties: { scope, pattern: { type: "string", description: "a glob the unit's name must match (optional)" } } }, run: async (args) => ({ scope: scopeOf(args), units: await manager.units(scopeOf(args), args.pattern ? String(args.pattern) : undefined) }), }, { name: "status", description: "One unit as the service manager sees it now: its states, whether it starts at boot, its main process, and whether the mesh declares it.", input: { type: "object", properties: { scope, unit }, required: ["unit"] }, run: async (args) => manager.status(scopeOf(args), unitOf(args)), }, act("start", "Start one unit. For a unit the mesh declares, the answer says the host will restore what its declaration says at the next apply."), act("stop", "Stop one unit; for a mesh-declared unit the answer says the host will restore its declared state."), act("restart", "Restart one unit."), act("enable", "Make one unit start at boot (or at the account's login, in user scope)."), act("disable", "Stop one unit starting at boot (or at login, in user scope)."), { name: "journal", description: "The last lines of one unit's journal.", input: { type: "object", properties: { scope, unit, lines: { type: "number", description: "how many lines from the end (default 100)" } }, required: ["unit"] }, run: async (args) => { const n = Number(args.lines ?? 100); return manager.journal(scopeOf(args), unitOf(args), Number.isFinite(n) && n > 0 ? Math.min(n, 5000) : 100); }, }, ]; } export function getOwnTools(manager: ServiceManager): ToolDefinition[] { return [ { name: "systemd_failed", description: "Every failed unit on this machine, in the system manager and in the operator account's.", input: { type: "object", properties: {} }, run: async () => manager.failed(), }, ]; } registerModuleTools("node-service-manager", (env) => getSeatVerbs(ServiceManager.fromEnv(env))); registerModuleTools("systemd", (env) => getOwnTools(ServiceManager.fromEnv(env)));