// zsh's tools: its implementation of node-login-shell's one verb, `execute`, and its own // `zsh_config` (novox/hq ADR 0176, ADR 0204). The node tools runtime launches this bundle as a // process of its own and serves what it registers (ADR 0188, ADR 0193); it runs as the operator // account, so the command runs as that account with nothing switched (shell.ts). import { userInfo } from "node:os"; import { join } from "node:path"; import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, commandEnv, execute, homeOf, timeoutOf, zshFile } from "../shell.js"; /** The operator account on this machine, as the mesh told the runtime. */ function account(env: NodeJS.ProcessEnv): string { return env.MESH_OPERATOR_ACCOUNT?.trim() || userInfo().username; } function seatVerbs(env: NodeJS.ProcessEnv): ToolDefinition[] { return [ { name: "execute", description: `Run one command on this machine as the operator account, in a zsh login shell in the account's home; answers with what it printed (each stream cut at 256 KiB, said in truncated) and how it exited. Ended, with everything it started, after timeout_seconds (default ${DEFAULT_TIMEOUT_SECONDS}, at most ${MAX_TIMEOUT_SECONDS}).`, input: { type: "object", properties: { command: { type: "string", description: "the command line, as you would type it" }, timeout_seconds: { type: "number", description: `give up after this long (default ${DEFAULT_TIMEOUT_SECONDS}, at most ${MAX_TIMEOUT_SECONDS})` }, }, required: ["command"], }, run: async (args) => { const command = String(args.command ?? "").trim(); if (!command) throw new Error("execute: a command is required"); const who = account(env); const self = userInfo().username; if (who !== self) { // The runtime is the account; anything else is a runtime this was not written for, and // a command run as the wrong user is worse than one not run. throw new Error(`execute runs as ${who}, and this runtime runs as ${self}`); } return execute(command, { cwd: homeOf(env), env: commandEnv(env), timeoutSeconds: timeoutOf(args.timeout_seconds), account: who }); }, }, ]; } function ownTools(env: NodeJS.ProcessEnv): ToolDefinition[] { return [ { name: "zsh_config", description: "The operator account's ~/.zshenv and ~/.zshrc on this machine as they are now: each file's lines, how many are the mesh's block, and the content.", input: { type: "object", properties: {} }, run: async () => { const home = homeOf(env); return { account: account(env), zshenv: await zshFile(join(home, ".zshenv")), zshrc: await zshFile(join(home, ".zshrc")) }; }, }, ]; } // The seat's verb is registered under the seat's name (what the runtime serves on the seat's // subject while this module holds it) and the module's own tool under the module's. registerModuleTools("node-login-shell", seatVerbs); registerModuleTools("zsh", ownTools);