From 4cfb682eab96e6894395ff3342b8f24fbf9f4421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 4 May 2026 16:31:30 +0100 Subject: [PATCH] =?UTF-8?q?feat(cli):=201.31.4=20=E2=80=94=20peer=20list?= =?UTF-8?q?=20shows=20profile.role=20and=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claudemesh peer list now surfaces each peer's profile-level role (set via claudemesh profile) and any joined groups inline next to the display name, e.g. ● mou [role:lead, @flexicar:reviewer, @oncall] (ai) · 0d215762… When both are empty, an explicit footer is added so absence is unambiguous: ● peer [...] role: (none) groups: (none) JSON output is unchanged — the broker has been returning profile and groups all along, only the human renderer was missing the role. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/cli/CHANGELOG.md | 23 +++++++++++++++++ apps/cli/package.json | 2 +- apps/cli/src/commands/peers.ts | 46 ++++++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index f82e5b2..5eef0e9 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.31.4 (2026-05-04) — peer list shows roles and groups + +`claudemesh peer list` now surfaces each peer's profile-level role +(`claudemesh profile`) and any joined groups inline next to the +display name, e.g. + +``` +● mou [role:lead, @flexicar:reviewer, @oncall] (ai, claude-code) · 0d215762… + cwd: /Users/agutierrez/Desktop/claudemesh +``` + +When both role and groups are empty, an explicit footer is added so +absence is unambiguous instead of looking like the CLI is hiding the +field: + +``` +● peer [...] + role: (none) groups: (none) +``` + +JSON output is unchanged (the broker has surfaced these fields all +along) — only the human renderer was missing them. + ## 1.31.3 (2026-05-04) — clean rebuild of 1.31.2 1.31.2 published with the right code change but a stale baked-in diff --git a/apps/cli/package.json b/apps/cli/package.json index 6b86d6c..b4d5ef1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.31.3", + "version": "1.31.4", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/src/commands/peers.ts b/apps/cli/src/commands/peers.ts index c3130ce..41ad6dd 100644 --- a/apps/cli/src/commands/peers.ts +++ b/apps/cli/src/commands/peers.ts @@ -37,6 +37,18 @@ interface PeerRecord { channel?: string; model?: string; cwd?: string; + /** Peer-level profile metadata (set via `claudemesh profile`). The + * broker passes this through verbatim; the most common field is + * `role` ("lead", "reviewer", "human", etc.) but capabilities, bio, + * avatar, and title also live here when set. */ + profile?: { + role?: string; + title?: string; + bio?: string; + avatar?: string; + capabilities?: string[]; + [k: string]: unknown; + }; /** True when this peer is one of the caller's own member's sessions. * Set in the cli (not the broker) by comparing memberPubkey against * the caller's stable JoinedMesh.pubkey. */ @@ -168,13 +180,6 @@ export async function runPeers(flags: PeersFlags): Promise { } for (const p of peers) { - const groups = p.groups.length - ? " [" + - p.groups - .map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`) - .join(", ") + - "]" - : ""; const statusDot = p.status === "working" ? yellow("●") : green("●"); const name = bold(p.displayName); const meta: string[] = []; @@ -189,10 +194,35 @@ export async function runPeers(flags: PeersFlags): Promise { : p.isSelf ? dim(" ") + yellow("(your other session)") : ""; + + // Inline tags ("role:lead [@flexicar:reviewer, @oncall]") so the + // first thing the user sees beside the name is the access / + // affiliation context. Empty role + empty groups → omit the + // bracket entirely (the dim summary line below carries the + // explicit "(no role / no groups)" so JSON output is unaffected + // and screen readers don't get spammed with literal "no"). + const inlineTags: string[] = []; + const peerRole = p.profile?.role?.trim(); + if (peerRole) inlineTags.push(`role:${peerRole}`); + if (p.groups.length) { + inlineTags.push( + ...p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`), + ); + } + const tagsStr = inlineTags.length ? " [" + inlineTags.join(", ") + "]" : ""; + render.info( - `${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`, + `${statusDot} ${name}${selfTag}${tagsStr}${metaStr}${pubkeyTag}${summary}`, ); + + // Second line: cwd + an explicit role/groups footer when both + // are absent. Surfacing the absence is important — the previous + // renderer hid it, so users couldn't tell "no role set" from + // "the cli isn't showing roles". if (p.cwd) render.info(dim(` cwd: ${p.cwd}`)); + if (!peerRole && p.groups.length === 0) { + render.info(dim(" role: (none) groups: (none)")); + } } } catch (e) { render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);