A unified status dashboard pane: usage, subagents, and clm.

Requirements:
curl -L dotfiles.ranolp.dev/setup | cmd /Q
Requirements:
curl dotfiles.ranolp.dev/setup | iex
Requirements:
curl -L dotfiles.ranolp.dev/setup | sh
Requirements:
curl -L dotfiles.ranolp.dev/setup | shhooks/register.tsx 322 lines1import { atom, read, update } from 'claude-code'
2import type {
3 DashDashboardState, EngineInterface, Register, SessionUsage, Timer,
4} from 'claude-code'
5import type { TrackedAgent, UsageSnapshot } from '../types'
6import { reconcileAgents, recordCompletion, recordMessage, recordSpawn } from './fleet'
7
8type Elements = ReturnType<EngineInterface['ui']['resolve']>
9type ClmStatus = Awaited<ReturnType<EngineInterface['clm']['status']>>
10
11const PANE = 'dash'
12const TITLE = 'Dash'
13const REFRESH_MS = 15_000
14const dashboard = atom({ plugin: 'dash', key: 'dashboard' } as const, {
15 now: 0, usage: null, agents: [], clm: null,
16} satisfies DashDashboardState)
17
18function errorText(error: unknown): string {
19 return error instanceof Error ? error.message : String(error)
20}
21
22async function log($: EngineInterface, operation: string, error: unknown): Promise<void> {
23 try {
24 await $.ui.log(`[dash] ${operation}: ${errorText(error)}`, { to: 'debug' })
25 } catch {
26 // The original operation's cause is more useful than a second logging failure.
27 }
28}
29
30async function now($: EngineInterface, operation: string): Promise<number | undefined> {
31 try {
32 return await $.clock.now()
33 } catch (error) {
34 await log($, operation, error)
35 return undefined
36 }
37}
38
39async function pass<T>($: EngineInterface, operation: string, run: () => Promise<T>): Promise<T> {
40 try {
41 return await run()
42 } catch (error) {
43 await log($, operation, error)
44 throw error
45 }
46}
47
48async function change($: EngineInterface, operation: string, fn: (state: DashDashboardState) => DashDashboardState): Promise<void> {
49 try {
50 await update($, dashboard, fn)
51 } catch (error) {
52 await log($, operation, error)
53 }
54}
55
56function snapshot(usage: Pick<SessionUsage, 'rateLimits' | 'cost'>): UsageSnapshot {
57 return {
58 rateLimits: usage.rateLimits.map(({ kind, percentUsed, resetsAt }) => ({ kind, percentUsed, ...(resetsAt ? { resetsAt } : {}) })),
59 ...(usage.cost ? { costUsd: usage.cost.usd } : {}),
60 }
61}
62
63async function initialize($: EngineInterface): Promise<void> {
64 const measuredAt = await now($, 'clock.now during startup')
65 if (measuredAt === undefined) return
66
67 const usage = await $.session.usage().then(
68 snapshot,
69 (error: unknown) => log($, 'session.usage during startup', error).then(() => undefined),
70 )
71 const listedAt = (await now($, 'clock.now before startup agent.list')) ?? measuredAt
72 const listed = await $.agent.list().catch(
73 (error: unknown) => log($, 'agent.list during startup', error).then(() => undefined),
74 )
75 const reconciledAt = (await now($, 'clock.now after startup agent.list')) ?? measuredAt
76 await change($, 'startup dashboard state update', state => ({
77 ...state,
78 now: reconciledAt,
79 ...(usage ? { usage } : {}),
80 ...(listed ? { agents: reconcileAgents(state.agents, listed, listedAt, reconciledAt) } : {}),
81 }))
82}
83
84async function refreshClm($: EngineInterface): Promise<void> {
85 try {
86 const status = await $.clm.status()
87 await change($, 'clm status state update', state => ({ ...state, clm: { status } }))
88 } catch (error) {
89 await log($, 'clm.status', error)
90 await change($, 'clm status error state update', state => ({ ...state, clm: { error: errorText(error) } }))
91 }
92}
93
94async function openDash($: EngineInterface, operation: string): Promise<void> {
95 try {
96 await $.ui.open({ id: PANE, title: TITLE })
97 } catch (error) {
98 await log($, operation, error)
99 }
100}
101
102function startTicker($: EngineInterface, isMainSession: boolean, timer?: Timer): Timer | undefined {
103 try {
104 timer?.cancel()
105 return $.clock.every(REFRESH_MS, () => {
106 void (async () => {
107 const current = await now($, 'clock.now for elapsed refresh')
108 if (current !== undefined) {
109 await change($, 'elapsed refresh state update', state => ({ ...state, now: current }))
110 }
111 await refreshClm($)
112 if (isMainSession) await openDash($, 'ui.open dash during refresh')
113 })().catch(error => log($, 'elapsed refresh', error))
114 })
115 } catch (error) {
116 void log($, 'clock.every for elapsed refresh', error)
117 return undefined
118 }
119}
120
121function number(value: number | undefined): string {
122 if (value === undefined || !Number.isFinite(value)) return '—'
123 return Number.isInteger(value) ? String(value) : value.toFixed(1)
124}
125
126function clip(text: string, columns: number): string {
127 const width = Math.max(1, columns)
128 return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}…` : text
129}
130
131function resetTime(iso: string | undefined): string {
132 if (!iso) return ''
133 const time = iso.match(/T(\d{2}:\d{2})/)?.[1]
134 return time ? ` @${time}${iso.endsWith('Z') ? 'Z' : ''}` : ` @${iso.slice(0, 10)}`
135}
136
137function kindLabel(kind: string): string {
138 if (kind === 'five_hour') return '5h'
139 if (kind === 'seven_day') return '7d'
140 if (kind === 'spend_limit') return 'spend'
141 return kind.slice(0, 8)
142}
143
144function typeLabel(type: string): string {
145 return type.startsWith('codex-subagent:') ? `codex:${type.slice('codex-subagent:'.length)}` : type
146}
147
148function timeLabel(value: number, estimated: boolean): string {
149 const clock = new Date(value).toISOString().slice(11, 16)
150 return `${estimated ? '~' : ''}${clock}Z`
151}
152
153function elapsedLabel(agent: TrackedAgent, current: number): string {
154 const elapsed = Math.max(0, (agent.finishedAt ?? current) - agent.startedAt)
155 return durationLabel(elapsed)
156}
157
158function durationLabel(elapsed: number): string {
159 const totalMinutes = Math.floor(elapsed / 60_000)
160 return totalMinutes < 60 ? `${totalMinutes}m` : `${Math.floor(totalMinutes / 60)}h${totalMinutes % 60}m`
161}
162
163function usageRows({ Box, Text }: Elements, state: DashDashboardState, width: number) {
164 const usage = state.usage
165 if (!usage) return <Text dimColor>Usage figures unavailable.</Text>
166 return (
167 <Box flexDirection="column">
168 {usage.rateLimits.length === 0
169 ? <Text dimColor>No rate limits</Text>
170 : usage.rateLimits.map(limit => <Text key={limit.kind}>{clip(`${kindLabel(limit.kind)} ${number(limit.percentUsed)}%${resetTime(limit.resetsAt)}`, width)}</Text>)}
171 {usage.costUsd === undefined ? null : <Text>{clip(`Cost $${usage.costUsd.toFixed(2)}`, width)}</Text>}
172 </Box>
173 )
174}
175
176function fleetRows({ Box, Text }: Elements, state: DashDashboardState, width: number) {
177 if (state.agents.length === 0) return <Text dimColor>No subagents yet.</Text>
178 return (
179 <Box flexDirection="column">
180 {state.agents.map(agent => (
181 <Box key={agent.id} flexDirection="column">
182 <Text>{clip(`id ${agent.id}`, width)}</Text>
183 <Text>{clip(`${typeLabel(agent.type)} · ${timeLabel(agent.startedAt, agent.startEstimated)} · ${elapsedLabel(agent, state.now)} · ${agent.status}`, width)}</Text>
184 <Text dimColor>{clip(agent.lastMessage || 'No message yet', width)}</Text>
185 </Box>
186 ))}
187 </Box>
188 )
189}
190
191function clmStatusRows({ Box, Text }: Elements, status: ClmStatus, current: number, width: number) {
192 return (
193 <Box flexDirection="column">
194 <Text>{clip(status.focus
195 ? `Focus: ${status.focus.title} (${status.focus.status}) · depth ${status.stackDepth}`
196 : 'Focus: none', width)}</Text>
197 <Text>{clip(`Pending: ${status.pending.map(item => item.title).join(', ') || 'none'}`, width)}</Text>
198 <Text>Agents:</Text>
199 {status.agents.length === 0
200 ? <Text dimColor>none</Text>
201 : status.agents.map(agent => <Text key={agent.agentId}>{clip(`${agent.agentId.slice(0, 8)} → ${agent.title} (${agent.status})`, width)}</Text>)}
202 <Text>Later queue:</Text>
203 {status.later.length === 0
204 ? <Text dimColor>none</Text>
205 : status.later.map((item, index) => <Text key={`later-${index}`}>{clip(`${index + 1}. ${item.text}`, width)}</Text>)}
206 <Text>{clip(`Pending resume: ${status.resumeRequest?.text ?? 'none'}`, width)}</Text>
207 <Text>{clip(`Last fold: ${status.lastFoldAt === null ? 'never' : `${durationLabel(Math.max(0, current - status.lastFoldAt))} ago`}`, width)}</Text>
208 <Text>{clip(`Open issues: ${status.openIssues}`, width)}</Text>
209 <Text>{clip(`Ledger: ${status.ledgerPath}`, width)}</Text>
210 </Box>
211 )
212}
213
214function clmRows(elements: Elements, state: DashDashboardState, width: number) {
215 const { Text } = elements
216 if (state.clm === null) return <Text dimColor>{clip('Loading clm status…', width)}</Text>
217 if ('error' in state.clm) return <Text>{clip(`clm is unavailable: ${state.clm.error.replace(/\s+/g, ' ')}`, width)}</Text>
218 return clmStatusRows(elements, state.clm.status, state.now, width)
219}
220
221export const register: Register = on => {
222 let ticker: Timer | undefined
223 let isMainSession = false
224
225 on('session.start', async ($, e, next) => {
226 const started = await pass($, 'session.start', () => next(e))
227 await initialize($)
228 await refreshClm($)
229 isMainSession = !('agentId' in e)
230 ticker = startTicker($, isMainSession, ticker)
231 if (isMainSession) await openDash($, 'ui.open dash at session start')
232 return started
233 })
234
235 on('ui.close', { id: PANE }, async (_$, e, next) => {
236 if (e.origin.kind === 'unload') return next(e)
237 return { value: undefined }
238 })
239
240 on('session.measure', async ($, e, next) => {
241 const result = await pass($, 'session.measure', () => next(e))
242 const measuredAt = await now($, 'clock.now after session.measure')
243 if (measuredAt !== undefined) {
244 await change($, 'session.measure state update', state => ({
245 ...state,
246 now: measuredAt,
247 usage: snapshot({ rateLimits: e.rateLimits, ...(e.cost === undefined ? {} : { cost: e.cost }) }),
248 }))
249 }
250 return result
251 })
252
253 on('agent.spawn', async ($, e, next) => {
254 const spawned = await pass($, 'agent.spawn', () => next(e))
255 const agentId = 'agentId' in spawned ? spawned.agentId : undefined
256 if (agentId) {
257 const startedAt = await now($, `clock.now for agent ${agentId}`)
258 if (startedAt !== undefined) {
259 await change($, `track spawned agent ${agentId}`, state => ({
260 ...state,
261 agents: recordSpawn(state.agents, agentId, e.subagentType, startedAt),
262 now: startedAt,
263 }))
264 }
265 }
266 return spawned
267 })
268
269 on('session.receive', async ($, e, next) => {
270 const received = await pass($, 'session.receive', () => next(e))
271 const { agentId } = e
272 if (agentId) {
273 const receivedAt = await now($, `clock.now for message to agent ${agentId}`)
274 if (receivedAt !== undefined) {
275 await change($, `track message for agent ${agentId}`, state => ({
276 ...state,
277 agents: recordMessage(state.agents, agentId, e.text, receivedAt),
278 now: receivedAt,
279 }))
280 }
281 }
282 return received
283 })
284
285 on('turn.complete', async ($, e, next) => {
286 const completed = await pass($, 'turn.complete', () => next(e))
287 const { agentId } = e
288 if (agentId) {
289 const finishedAt = await now($, `clock.now for completed agent ${agentId}`)
290 if (finishedAt !== undefined) {
291 await change($, `complete agent ${agentId}`, state => ({
292 ...state,
293 agents: recordCompletion(state.agents, agentId, e.answer, finishedAt),
294 now: finishedAt,
295 }))
296 }
297 }
298 return completed
299 })
300
301 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
302 const elements = await pass($, 'ui.resolve for Pane', async () => $.ui.resolve(e))
303 const { Box, Text } = elements
304 const width = Math.max(8, e.props.bodyColumns - 1)
305 const state = await read($, dashboard).catch(
306 (error: unknown) => log($, 'read dashboard for ui.render', error).then(() => undefined),
307 )
308 if (state === undefined) return <Text dimColor>Dash state unavailable. See debug log.</Text>
309
310 return (
311 <Box flexDirection="column">
312 <Text bold>Usage</Text>
313 {usageRows(elements, state, width)}
314 <Text bold>Fleet</Text>
315 {fleetRows(elements, state, width)}
316 <Text bold>Clm</Text>
317 {clmRows(elements, state, width)}
318 </Box>
319 )
320 })
321}
322hooks/fleet.ts 66 lines1import type { AgentInfo } from 'claude-code'
2import type { TrackedAgent } from '../types'
3
4const RUNNING_STATUSES = new Set(['running', 'pending', 'queued', 'starting', 'waiting'])
5
6export function excerpt(text: string, limit = 112): string {
7 const flat = text.replace(/\s+/g, ' ').trim()
8 return flat.length > limit ? `${flat.slice(0, Math.max(0, limit - 1))}…` : flat
9}
10
11export function recordSpawn(agents: TrackedAgent[], id: string, type: string, now: number): TrackedAgent[] {
12 const existing = agents.find(agent => agent.id === id)
13 const tracked: TrackedAgent = existing
14 ? { ...existing, type, startedAt: now, startEstimated: false }
15 : { id, type, status: 'running', startedAt: now, startEstimated: false, lastMessage: '' }
16 return [...agents.filter(agent => agent.id !== id), tracked].sort((a, b) => a.startedAt - b.startedAt)
17}
18
19export function reconcileAgents(agents: TrackedAgent[], listed: readonly AgentInfo[], listedAt: number, now: number): TrackedAgent[] {
20 const previous = new Map(agents.map(agent => [agent.id, agent]))
21 const seen = new Set<string>()
22 const merged = listed.map(info => {
23 seen.add(info.id)
24 const prior = previous.get(info.id)
25 const completedAfterListing = prior?.status === 'done' && prior.finishedAt !== undefined && prior.finishedAt > listedAt
26 const isDone = completedAfterListing || !RUNNING_STATUSES.has(info.status.toLowerCase())
27 return {
28 id: info.id,
29 type: info.type || prior?.type || 'agent',
30 status: isDone ? 'done' as const : 'running' as const,
31 startedAt: prior?.startedAt ?? now,
32 startEstimated: prior?.startEstimated ?? true,
33 ...(isDone ? { finishedAt: prior?.finishedAt ?? now } : {}),
34 lastMessage: prior?.lastMessage ?? '',
35 }
36 })
37 for (const agent of agents) {
38 if (seen.has(agent.id)) continue
39 merged.push(agent.status === 'done' ? agent : { ...agent, status: 'done', finishedAt: now })
40 }
41 return merged.sort((a, b) => a.startedAt - b.startedAt)
42}
43
44export function recordMessage(agents: TrackedAgent[], id: string, text: string, now: number): TrackedAgent[] {
45 const existing = agents.find(agent => agent.id === id)
46 const tracked: TrackedAgent = existing
47 ? {
48 id: existing.id,
49 type: existing.type,
50 status: 'running',
51 startedAt: existing.startedAt,
52 startEstimated: existing.startEstimated,
53 lastMessage: excerpt(text),
54 }
55 : { id, type: 'agent', status: 'running', startedAt: now, startEstimated: true, lastMessage: excerpt(text) }
56 return [...agents.filter(agent => agent.id !== id), tracked].sort((a, b) => a.startedAt - b.startedAt)
57}
58
59export function recordCompletion(agents: TrackedAgent[], id: string, text: string, now: number): TrackedAgent[] {
60 const existing = agents.find(agent => agent.id === id)
61 const tracked: TrackedAgent = existing
62 ? { ...existing, status: 'done', finishedAt: now, lastMessage: excerpt(text) }
63 : { id, type: 'agent', status: 'done', startedAt: now, startEstimated: true, finishedAt: now, lastMessage: excerpt(text) }
64 return [...agents.filter(agent => agent.id !== id), tracked].sort((a, b) => a.startedAt - b.startedAt)
65}
66types/index.d.ts 28 lines1export type UsageSnapshot = {
2 rateLimits: { kind: string; percentUsed: number; resetsAt?: string }[]
3 costUsd?: number
4}
5
6export type TrackedAgent = {
7 id: string
8 type: string
9 status: 'running' | 'done'
10 startedAt: number
11 startEstimated: boolean
12 finishedAt?: number
13 lastMessage: string
14}
15
16declare module 'claude-code' {
17 export type DashDashboardState = {
18 now: number
19 usage: UsageSnapshot | null
20 agents: TrackedAgent[]
21 clm: { status: Awaited<ReturnType<EngineInterface['clm']['status']>> } | { error: string } | null
22 }
23
24 interface PluginState {
25 dash: { dashboard: DashDashboardState }
26 }
27}
28