#!/usr/bin/env node /** * `mesh` — the mesh's tools from a workstation, for a person (novox/hq design 25 §7). * * Three verbs and nothing else. What tools are there, call one, and serve the same two to an agent * over MCP. Deliberately thin: everything that could be a decision is one the mesh already made, and a * client that grew opinions would be a second place the mesh's behaviour is defined. * * mesh tools what this credential may call * mesh call . [json] call one, arguments as JSON on the command line or on stdin * mesh mcp the same, as an MCP server over stdio * * The credential comes from MESH_CREDENTIAL, or --credential. It is the JSON `operator issue` printed. */ import { readFile } from "node:fs/promises"; import { callTool, connectAs, credentialFrom, toolsOn, whyItFailed, type Tool } from "./client.js"; import { serveMcp } from "./mcp.js"; const usage = `mesh tools mesh call . [json] mesh mcp --credential the JSON \`operator issue\` printed; default $MESH_CREDENTIAL`; async function main(argv: string[]): Promise { const args = [...argv]; let credentialPath = process.env.MESH_CREDENTIAL ?? ""; for (let i = 0; i < args.length; i++) { if (args[i] === "--credential") { credentialPath = args[i + 1] ?? ""; args.splice(i, 2); i--; } } const verb = args.shift(); if (!verb || verb === "help" || verb === "--help") { console.log(usage); return verb ? 0 : 1; } if (!credentialPath) { console.error( "no credential: set MESH_CREDENTIAL or pass --credential . It is the JSON " + "`operator issue` printed, saved verbatim.", ); return 1; } const held = await credentialFrom(credentialPath); const bus = await connectAs(held); try { switch (verb) { case "tools": return await listing(bus, held.person); case "call": return await calling(bus, args); case "mcp": // Serves until stdin closes, which is how an MCP host ends a session. await serveMcp(bus, held.person ?? held.user ?? "somebody"); return 0; default: console.error(`mesh has no "${verb}".\n\n${usage}`); return 1; } } finally { await bus.close(); } } async function listing(bus: Awaited>, who?: string): Promise { let tools: Tool[]; try { tools = await toolsOn(bus); } catch (e) { console.error(whyItFailed("mesh-catalog.catalog_tools", e)); return 1; } if (tools.length === 0) { console.log("the catalogue lists no tools; nothing on this mesh serves any"); return 0; } // **What the catalogue has, not what this credential may call.** The two differ and the difference // is the point: a person seeing only their own tools cannot tell "not installed" from "not yours", // and those need different people to fix them. for (const t of tools) { const name = `${t.module}.${t.name}`; console.log(t.description ? `${name.padEnd(36)} ${t.description}` : name); } if (who) { console.log(`\nthis is what the mesh has. What ${who} may call was fixed when the credential was issued.`); } return 0; } async function calling( bus: Awaited>, args: string[], ): Promise { const key = args.shift(); if (!key) { console.error("mesh call . [json]"); return 1; } const raw = args.length > 0 ? args.join(" ") : await maybeStdin(); let parsed: unknown = {}; if (raw.trim() !== "") { try { parsed = JSON.parse(raw); } catch (e) { console.error(`the arguments are not JSON: ${(e as Error).message}`); return 1; } } try { const answer = await callTool(bus, key, parsed); console.log(JSON.stringify(answer, null, 2)); return 0; } catch (e) { console.error(whyItFailed(key, e)); return 1; } } /** Arguments on stdin, for a call whose JSON is too long or too quoted to type. Empty when stdin is a * terminal, so `mesh call x.y` with no arguments does not hang waiting for something nobody is * typing. */ async function maybeStdin(): Promise { if (process.stdin.isTTY) return ""; const chunks: Buffer[] = []; for await (const chunk of process.stdin) chunks.push(chunk as Buffer); return Buffer.concat(chunks).toString("utf8"); } // Only when run, so a test can import the pieces. if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) { main(process.argv.slice(2)) .then((code) => process.exit(code)) .catch((e) => { console.error(e instanceof Error ? e.message : String(e)); process.exit(1); }); } export { main, usage }; export const _readFile = readFile;