fix(cli): surface deviceId in devices list table (#114370)

* fix(cli): surface deviceId in devices list table

* docs(changelog): add devices list deviceId fix (#114279)

* fix(cli): keep paired device IDs copyable

Co-authored-by: WilShi <1638083992@qq.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
WilShi
2026-07-27 17:24:34 +08:00
committed by GitHub
parent db97a748f2
commit dafecc756a
2 changed files with 48 additions and 13 deletions

View File

@@ -928,32 +928,39 @@ export async function runDevicesListCommand(opts: DevicesRpcOpts): Promise<void>
}
if (list.paired?.length) {
const tableWidth = getTerminalTableWidth();
const rows = list.paired.map((device) => ({
Device: sanitizeForLog(
device.operatorLabel || device.displayName || device.clientId || device.deviceId,
),
"Device ID": sanitizeForLog(device.deviceId),
Roles: device.roles?.length
? device.roles.map((role) => sanitizeForLog(role)).join(", ")
: "",
Scopes: device.scopes?.length
? device.scopes.map((scope) => sanitizeForLog(scope)).join(", ")
: "",
Tokens: formatTokenSummary(device.tokens),
IP: device.remoteIp ? sanitizeForLog(device.remoteIp) : "",
}));
defaultRuntime.log(`${theme.heading("Paired")} ${theme.muted(`(${list.paired.length})`)}`);
defaultRuntime.log(
renderTable({
width: tableWidth,
columns: [
{ key: "Device", header: "Device", minWidth: 16, flex: true },
{ key: "Device ID", header: "Device ID", minWidth: 12, flex: true },
{ key: "Roles", header: "Roles", minWidth: 12, flex: true },
{ key: "Scopes", header: "Scopes", minWidth: 12, flex: true },
{ key: "Tokens", header: "Tokens", minWidth: 12, flex: true },
{ key: "IP", header: "IP", minWidth: 12 },
],
rows: list.paired.map((device) => ({
Device: sanitizeForLog(
device.operatorLabel || device.displayName || device.clientId || device.deviceId,
),
Roles: device.roles?.length
? device.roles.map((role) => sanitizeForLog(role)).join(", ")
: "",
Scopes: device.scopes?.length
? device.scopes.map((scope) => sanitizeForLog(scope)).join(", ")
: "",
Tokens: formatTokenSummary(device.tokens),
IP: device.remoteIp ? sanitizeForLog(device.remoteIp) : "",
})),
rows,
}).trimEnd(),
);
defaultRuntime.log(theme.muted("Full device IDs"));
for (const row of rows) {
defaultRuntime.log(` ${row["Device ID"]} ${row.Device}`);
}
const nodeApprovalNotices = await findPairedDevicePendingNodeApprovalNotices(opts, list.paired);
for (const notice of nodeApprovalNotices) {
defaultRuntime.log(theme.warn(formatNodeApprovalNotice(notice)));

View File

@@ -1248,6 +1248,34 @@ describe("devices cli list", () => {
expect(output).not.toContain("openclaw-macos");
expect(output).not.toContain("openclaw-ios");
});
it("shows a deviceId column so identical display names are distinguishable for remove", async () => {
const deviceIdA = "a".repeat(64);
const deviceIdB = "b".repeat(64);
callGateway.mockResolvedValueOnce({
pending: [],
paired: [
pairedDevice({
deviceId: deviceIdA,
displayName: "OpenClaw Desktop",
clientId: "openclaw-macos",
}),
pairedDevice({
deviceId: deviceIdB,
displayName: "OpenClaw Desktop",
clientId: "openclaw-macos",
}),
],
});
await runDevicesCommand(["list"]);
const output = stripAnsi(readRuntimeOutput());
expect(output).toContain("Device ID");
expect(output).toContain("Full device IDs");
expect(output.split("\n")).toContain(` ${deviceIdA} OpenClaw Desktop`);
expect(output.split("\n")).toContain(` ${deviceIdB} OpenClaw Desktop`);
});
});
describe("devices cli rename", () => {