Always-on band above the prompt listing every other open Claude Code session: name, ref, kind, status and age.

Always-on band listing every other open Claude Code session.
Reads the session registry in ~/.claude/sessions/, drops entries whose process has exited, and shows one dim line per live session above the prompt, newest first, refreshed every 5 seconds:
robin-mods-50 (ref b79115, interactive, idle, started a minute ago)
demo-35 (ref 579855, interactive, busy, started 12 minutes ago) · this one
The band only appears when at least two sessions are open. ref is the first 6 characters of the session id; use the name with SendMessage to reach a session.
/plugin install session-peers@mod-garage
Run /reload-plugins afterwards. Local try-out: claude --plugin-dir ./dev-mods/session-peers
hooks/register.tsx 51 lines1import type { Register } from 'claude-code'
2import { lines, parse, type Peer } from './lib'
3
4let rows: string[] = []
5
6async function refresh($: any) {
7 const home = (await $.process.run(['printenv', 'HOME']).catch(() => undefined))?.stdout.trim()
8 if (!home) return
9 const dir = `${home}/.claude/sessions`
10 const entries = await $.fs.list(dir).catch(() => [])
11 const peers: Peer[] = []
12 for (const f of entries) {
13 if (!f.name.endsWith('.json')) continue
14 const text = await $.fs.read(`${dir}/${f.name}`).catch(() => undefined)
15 const p = text && parse(text)
16 if (p) peers.push(p)
17 }
18 let live = peers
19 if (peers.length) {
20 const ps = await $.process.run(['ps', '-o', 'pid=', '-p', peers.map(p => p.pid).join(',')]).catch(() => undefined)
21 const alive = new Set((ps?.stdout ?? '').split(/\s+/).filter(Boolean).map(Number))
22 live = peers.filter(p => alive.has(p.pid))
23 }
24 const self = await $.session.id()
25 const next = live.length > 1 ? lines(live, Date.now(), self) : []
26 if (next.join('\n') !== rows.join('\n')) {
27 rows = next
28 $.ui.invalidate('ui.render')
29 }
30}
31
32export const register: Register = on => {
33 on('session.start', async ($, e, next) => {
34 await refresh($)
35 $.clock.every(5_000, () => refresh($))
36 return next(e)
37 })
38
39 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
40 const theirs = await next(e)
41 if (!rows.length || e.props.hasSurvey) return theirs
42 const { Box, Text } = $.ui.resolve(e)
43 return (
44 <Box flexDirection="column">
45 {theirs}
46 {rows.map(r => <Text dimColor>{r}</Text>)}
47 </Box>
48 )
49 })
50}
51hooks/lib.ts 48 lines1export type Peer = {
2 pid: number
3 sessionId: string
4 name: string
5 kind: string
6 status: string
7 startedAt: number
8}
9
10export function parse(text: string): Peer | undefined {
11 try {
12 const j = JSON.parse(text)
13 if (typeof j.pid !== 'number' || typeof j.sessionId !== 'string') return undefined
14 return {
15 pid: j.pid,
16 sessionId: j.sessionId,
17 name: typeof j.name === 'string' && j.name ? j.name : `pid-${j.pid}`,
18 kind: typeof j.kind === 'string' ? j.kind : 'unknown',
19 status: typeof j.status === 'string' ? j.status : 'unknown',
20 startedAt: typeof j.startedAt === 'number' ? j.startedAt : 0,
21 }
22 } catch {
23 return undefined
24 }
25}
26
27export function ago(ms: number): string {
28 const s = Math.max(0, Math.floor(ms / 1000))
29 if (s < 60) return 'just now'
30 const unit = (n: number, w: string) => `${n} ${w}${n === 1 ? '' : 's'} ago`
31 if (s < 3600) return unit(Math.floor(s / 60), 'minute')
32 if (s < 86400) return unit(Math.floor(s / 3600), 'hour')
33 return unit(Math.floor(s / 86400), 'day')
34}
35
36export function format(p: Peer, now: number, self?: string): string {
37 const mark = p.sessionId === self ? ' · this one' : ''
38 const age = p.startedAt ? `started ${ago(now - p.startedAt)}` : 'start unknown'
39 return `${p.name} (ref ${p.sessionId.replace(/-/g, '').slice(0, 6)}, ${p.kind}, ${p.status}, ${age})${mark}`
40}
41
42export function lines(peers: Peer[], now: number, self?: string, max = 6): string[] {
43 const sorted = [...peers].sort((a, b) => b.startedAt - a.startedAt)
44 const shown = sorted.slice(0, max).map(p => format(p, now, self))
45 if (sorted.length > max) shown.push(`+${sorted.length - max} more`)
46 return shown
47}
48