CLI: add nodes status
This commit is contained in:
@@ -32,6 +32,9 @@ type NodeListNode = {
|
|||||||
platform?: string;
|
platform?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
remoteIp?: string;
|
remoteIp?: string;
|
||||||
|
deviceFamily?: string;
|
||||||
|
modelIdentifier?: string;
|
||||||
|
caps?: string[];
|
||||||
connected?: boolean;
|
connected?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,6 +180,44 @@ export function registerNodesCli(program: Command) {
|
|||||||
.command("nodes")
|
.command("nodes")
|
||||||
.description("Manage gateway-owned node pairing");
|
.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(
|
nodesCallOpts(
|
||||||
nodes
|
nodes
|
||||||
.command("list")
|
.command("list")
|
||||||
|
|||||||
@@ -62,6 +62,37 @@ describe("cli program", () => {
|
|||||||
expect(runtime.log).toHaveBeenCalledWith("Pending: 0 · Paired: 0");
|
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 () => {
|
it("runs nodes approve and calls node.pair.approve", async () => {
|
||||||
callGateway.mockResolvedValue({
|
callGateway.mockResolvedValue({
|
||||||
requestId: "r1",
|
requestId: "r1",
|
||||||
|
|||||||
Reference in New Issue
Block a user