diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index 5eef0e9..795b026 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.31.5 (2026-05-04) — JSON peer list lifts profile.role to top-level + skill guides LLMs to render it + +Two follow-ups after 1.31.4 made the human renderer show role/groups +but a launched-session LLM still dropped them when it called +`peer list --json` and built its own table. + +- **Top-level `role` field on every peer record.** The broker has + always returned role nested under `profile.role`, but downstream + consumers (LLMs in launched sessions, jq pipelines, dashboards) kept + missing it. The CLI now lifts `profile.role` to a top-level `role` + field at parse time, so it's the second thing visible in JSON after + `displayName`. The original `profile.role` is preserved for + backward compatibility. +- **Updated SKILL.md peer-list section** with the full JSON shape + (including `memberPubkey`, `sessionId`, `role`, `profile`, `isSelf`, + `isThisSession`) and explicit guidance: when listing peers inside a + launched session, prefer the human renderer; if you do need JSON, + always include `role` and `groups` columns. The previous version of + the skill documented six fields and skipped role + identity entirely. + ## 1.31.4 (2026-05-04) — peer list shows roles and groups `claudemesh peer list` now surfaces each peer's profile-level role diff --git a/apps/cli/package.json b/apps/cli/package.json index b4d5ef1..d51f484 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.31.4", + "version": "1.31.5", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/skills/claudemesh/SKILL.md b/apps/cli/skills/claudemesh/SKILL.md index 4a0ee7d..a0c0b3c 100644 --- a/apps/cli/skills/claudemesh/SKILL.md +++ b/apps/cli/skills/claudemesh/SKILL.md @@ -328,22 +328,36 @@ claudemesh peer bans # list banned members claudemesh peer verify [peer] # 6×5-digit safety numbers ``` -JSON shape (per peer): +JSON shape (per peer) — **render `role` and `groups` whenever you build a table for the user**, they're the highest-signal fields after `displayName`: ```json { "displayName": "Mou", - "pubkey": "abc123...", + "pubkey": "abc123...", // session pubkey (rotates per claudemesh launch) + "memberPubkey": "def456...", // stable identity (same across all sibling sessions) + "sessionId": "uuid", "status": "idle | working | dnd", "summary": "string or null", + "role": "lead | reviewer | bot | ...", // 1.31.5+: top-level alias of profile.role "groups": [{ "name": "reviewers", "role": "lead" }], - "peerType": "claude | telegram | ...", + "profile": { + "role": "lead", + "title": "string or null", + "bio": "string or null", + "avatar": "emoji or null", + "capabilities": ["..."] + }, + "peerType": "claude | telegram | ai | human | connector | ...", "channel": "claude-code | api | ...", "model": "claude-opus-4-7 | ...", "cwd": "/path/to/working/dir or null", + "isSelf": true, // peer is one of the caller's own sessions + "isThisSession": false, // peer is the exact session running the cli "stats": { "messagesIn": 0, "messagesOut": 0, "toolCalls": 0, "errors": 0, "uptime": 1200 } } ``` +**When asked to "list peers" inside a launched session, prefer the human renderer (`claudemesh peer list`, no `--json`) — it already prints role + groups inline next to the name with an explicit `(none)` footer when both are absent. If you do need JSON for parsing, always include `role` and `groups` columns in any rendered table; the user's primary question is usually "who's in what role" and dropping those fields hides the answer.** + ### `message` — send and inspect messages ```bash diff --git a/apps/cli/src/commands/peers.ts b/apps/cli/src/commands/peers.ts index 41ad6dd..142df38 100644 --- a/apps/cli/src/commands/peers.ts +++ b/apps/cli/src/commands/peers.ts @@ -33,6 +33,10 @@ interface PeerRecord { status?: string; summary?: string; groups: Array<{ name: string; role?: string }>; + /** Top-level convenience alias for `profile.role`. Lifted by the + * CLI so JSON consumers see role at the shape's top level instead + * of nested under profile. Same value either way. */ + role?: string; peerType?: string; channel?: string; model?: string; @@ -110,6 +114,13 @@ async function listPeersForMesh(slug: string): Promise { * tell sender's own sessions from real peers. The broker has always * surfaced a sender's siblings as separate rows because they're separate * presence rows; the cli just hadn't been making that visible. + * + * Also lifts `profile.role` to a top-level `role` field. The broker has + * always returned role nested under `profile.role`, but downstream JSON + * consumers (LLMs in launched sessions, jq pipelines, dashboards) kept + * missing it because nothing pointed at the nesting. A dedicated + * top-level alias makes the intent unmissable without breaking the + * `profile` object's shape for callers that already drill into it. */ function annotateSelf( peer: PeerRecord, @@ -126,7 +137,8 @@ function annotateSelf( selfSessionPubkey && peer.pubkey === selfSessionPubkey ); - return { ...peer, isSelf, isThisSession }; + const role = peer.profile?.role?.trim() || undefined; + return { ...peer, ...(role ? { role } : {}), isSelf, isThisSession }; } export async function runPeers(flags: PeersFlags): Promise {