CLI: add nodes status

This commit is contained in:
Peter Steinberger
2025-12-18 00:37:40 +00:00
parent 1a2d39bdf9
commit 460e170f7a
2 changed files with 72 additions and 0 deletions

View File

@@ -32,6 +32,9 @@ type NodeListNode = {
platform?: string;
version?: string;
remoteIp?: string;
deviceFamily?: string;
modelIdentifier?: string;
caps?: string[];
connected?: boolean;
};
@@ -177,6 +180,44 @@ export function registerNodesCli(program: Command) {
.command("nodes")
.description("Manage gateway-owned node pairing");
nodesCallOpts(
nodes
.command("status")
.description("List paired nodes with connection status and capabilities")
.action(async (opts: NodesRpcOpts) => {
try {
const result = (await callGatewayCli("node.list", opts, {})) as unknown;
if (opts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
const nodes = parseNodeList(result);
const connectedCount = nodes.filter((n) => Boolean(n.connected)).length;
defaultRuntime.log(
`Paired: ${nodes.length} · Connected: ${connectedCount}`,
);
for (const n of nodes) {
const name = n.displayName || n.nodeId;
const ip = n.remoteIp ? ` · ${n.remoteIp}` : "";
const device = n.deviceFamily ? ` · device: ${n.deviceFamily}` : "";
const hw = n.modelIdentifier ? ` · hw: ${n.modelIdentifier}` : "";
const caps =
Array.isArray(n.caps) && n.caps.length > 0
? `[${n.caps.map(String).filter(Boolean).sort().join(",")}]`
: Array.isArray(n.caps)
? "[]"
: "?";
defaultRuntime.log(
`- ${name} · ${n.nodeId}${ip}${device}${hw} · ${n.connected ? "connected" : "disconnected"} · caps: ${caps}`,
);
}
} catch (err) {
defaultRuntime.error(`nodes status failed: ${String(err)}`);
defaultRuntime.exit(1);
}
}),
);
nodesCallOpts(
nodes
.command("list")

View File

@@ -62,6 +62,37 @@ describe("cli program", () => {
expect(runtime.log).toHaveBeenCalledWith("Pending: 0 · Paired: 0");
});
it("runs nodes status and calls node.list", async () => {
callGateway.mockResolvedValue({
ts: Date.now(),
nodes: [
{
nodeId: "ios-node",
displayName: "iOS Node",
remoteIp: "192.168.0.88",
deviceFamily: "iPad",
modelIdentifier: "iPad16,6",
caps: ["canvas", "camera"],
connected: true,
},
],
});
const program = buildProgram();
runtime.log.mockClear();
await program.parseAsync(["nodes", "status"], { from: "user" });
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({ method: "node.list", params: {} }),
);
const output = runtime.log.mock.calls.map((c) => String(c[0] ?? "")).join("\n");
expect(output).toContain("Paired: 1 · Connected: 1");
expect(output).toContain("iOS Node");
expect(output).toContain("device: iPad");
expect(output).toContain("hw: iPad16,6");
expect(output).toContain("caps: [camera,canvas]");
});
it("runs nodes approve and calls node.pair.approve", async () => {
callGateway.mockResolvedValue({
requestId: "r1",