feat(cli): 1.31.4 — peer list shows profile.role and groups
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) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,28 @@
|
|||||||
# Changelog
|
# 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.3 (2026-05-04) — clean rebuild of 1.31.2
|
||||||
|
|
||||||
1.31.2 published with the right code change but a stale baked-in
|
1.31.2 published with the right code change but a stale baked-in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudemesh-cli",
|
"name": "claudemesh-cli",
|
||||||
"version": "1.31.3",
|
"version": "1.31.4",
|
||||||
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
|
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"claude-code",
|
"claude-code",
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ interface PeerRecord {
|
|||||||
channel?: string;
|
channel?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
cwd?: 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.
|
/** 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
|
* Set in the cli (not the broker) by comparing memberPubkey against
|
||||||
* the caller's stable JoinedMesh.pubkey. */
|
* the caller's stable JoinedMesh.pubkey. */
|
||||||
@@ -168,13 +180,6 @@ export async function runPeers(flags: PeersFlags): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const p of peers) {
|
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 statusDot = p.status === "working" ? yellow("●") : green("●");
|
||||||
const name = bold(p.displayName);
|
const name = bold(p.displayName);
|
||||||
const meta: string[] = [];
|
const meta: string[] = [];
|
||||||
@@ -189,10 +194,35 @@ export async function runPeers(flags: PeersFlags): Promise<void> {
|
|||||||
: p.isSelf
|
: p.isSelf
|
||||||
? dim(" ") + yellow("(your other session)")
|
? 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(
|
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 (p.cwd) render.info(dim(` cwd: ${p.cwd}`));
|
||||||
|
if (!peerRole && p.groups.length === 0) {
|
||||||
|
render.info(dim(" role: (none) groups: (none)"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user