import type { Command } from "commander"; import { defaultRuntime } from "../../runtime.js"; import { formatAge, parsePairingList } from "./format.js"; import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js"; import type { NodesRpcOpts } from "./types.js"; export function registerNodesPairingCommands(nodes: Command) { nodesCallOpts( nodes .command("pending") .description("List pending pairing requests") .action(async (opts: NodesRpcOpts) => { try { const result = (await callGatewayCli( "node.pair.list", opts, {}, )) as unknown; const { pending } = parsePairingList(result); if (opts.json) { defaultRuntime.log(JSON.stringify(pending, null, 2)); return; } if (pending.length === 0) { defaultRuntime.log("No pending pairing requests."); return; } for (const r of pending) { const name = r.displayName || r.nodeId; const repair = r.isRepair ? " (repair)" : ""; const ip = r.remoteIp ? ` · ${r.remoteIp}` : ""; const age = typeof r.ts === "number" ? ` · ${formatAge(Date.now() - r.ts)} ago` : ""; defaultRuntime.log(`- ${r.requestId}: ${name}${repair}${ip}${age}`); } } catch (err) { defaultRuntime.error(`nodes pending failed: ${String(err)}`); defaultRuntime.exit(1); } }), ); nodesCallOpts( nodes .command("approve") .description("Approve a pending pairing request") .argument("", "Pending request id") .action(async (requestId: string, opts: NodesRpcOpts) => { try { const result = await callGatewayCli("node.pair.approve", opts, { requestId, }); defaultRuntime.log(JSON.stringify(result, null, 2)); } catch (err) { defaultRuntime.error(`nodes approve failed: ${String(err)}`); defaultRuntime.exit(1); } }), ); nodesCallOpts( nodes .command("reject") .description("Reject a pending pairing request") .argument("", "Pending request id") .action(async (requestId: string, opts: NodesRpcOpts) => { try { const result = await callGatewayCli("node.pair.reject", opts, { requestId, }); defaultRuntime.log(JSON.stringify(result, null, 2)); } catch (err) { defaultRuntime.error(`nodes reject failed: ${String(err)}`); defaultRuntime.exit(1); } }), ); nodesCallOpts( nodes .command("rename") .description("Rename a paired node (display name override)") .requiredOption("--node ", "Node id, name, or IP") .requiredOption("--name ", "New display name") .action(async (opts: NodesRpcOpts) => { try { const nodeId = await resolveNodeId(opts, String(opts.node ?? "")); const name = String(opts.name ?? "").trim(); if (!nodeId || !name) { defaultRuntime.error("--node and --name required"); defaultRuntime.exit(1); return; } const result = await callGatewayCli("node.rename", opts, { nodeId, displayName: name, }); if (opts.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`node rename ok: ${nodeId} -> ${name}`); } catch (err) { defaultRuntime.error(`nodes rename failed: ${String(err)}`); defaultRuntime.exit(1); } }), ); }