Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "high-command",
"version": "1.2.1",
"version": "1.2.2",
"description": "See the messages your teammates, named subagents, and other Claude Code sessions send you, grouped by peer with unread counts: a pane, a band above the prompt, and /highcommand.",
"author": { "name": "dot-agi" },
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<img src="https://img.shields.io/badge/Claude_Code-2.1.288%2B-D97757" alt="Claude Code 2.1.288+">
<img src="https://img.shields.io/badge/type-mod-555555" alt="mod">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-555555" alt="License: MIT"></a>
<img src="https://img.shields.io/badge/version-1.2.1-555555" alt="Version: 1.2.1">
<img src="https://img.shields.io/badge/version-1.2.2-555555" alt="Version: 1.2.2">
</p>

<p align="center"><strong>One inbox for agent-team teammates, named subagents, and other Claude Code sessions.</strong></p>
Expand Down
4 changes: 2 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ A roster or a thread reads from the top down:
6. In a roster, the agent lanes.
7. The dim order note, `Order: observation time. Counts cover retained messages.`

The roster lists peers you received messages from or tried to message: peers with unread messages first, then the peer with the most recent message. In a pane of 90 columns or more, each row is one line of fixed columns under a dim header: the peer, the unread count, the time of the latest message, the sender level, the peer's last reported state with its time, and the latest message. Any `suspicious` and `(not delivered)` marks start the latest message. A narrower pane gives each row two lines (see **Narrow panes** in [Known limits](#known-limits)). `suspicious` and `unverified` show in your theme's warning color, and `(not delivered)` in its error color. The words stay, so a terminal without color loses nothing. On a row with nothing unread, the time, the state, and the message summary are dim. The arrow, the kind of message, and the marks are not. Press a peer, or its digit, to open its thread. The digits follow the roster order, so they move when a new message arrives. Rows past the ninth have no digit. A roster page shows up to 50 peers, with **Previous peers** and **More peers** on one row. When long names and messages would pass Claude Code's drawing limit, a page shows fewer, and a dim line says how many peers moved to the next page.
The roster lists peers you received messages from or tried to message: peers with unread messages first, then the peer with the most recent message. In a pane of 90 columns or more, each row is one line of fixed columns under a dim header: the peer, the unread count, the time of the latest message, the sender level, the peer's last reported state with its time, and the latest message. Any `suspicious` and `(not delivered)` marks start the latest message. A narrower pane gives each row two lines (see **Narrow panes** in [Known limits](#known-limits)). `suspicious` shows in your theme's warning color, and `(not delivered)` in its error color. Sender labels, `unverified sender` too, are plain text: in tmux, each teammate runs in its own pane, so every message can be `unverified sender`, and a color there would hide a real `suspicious` mark. The words stay, so a terminal without color loses nothing. On a row with nothing unread, the time, the state, and the message summary are dim. The arrow, the kind of message, and the marks are not. Press a peer, or its digit, to open its thread. The digits follow the roster order, so they move when a new message arrives. Rows past the ninth have no digit. A roster page shows up to 50 peers, with **Previous peers** and **More peers** on one row. When long names and messages would pass Claude Code's drawing limit, a page shows fewer, and a dim line says how many peers moved to the next page.

A thread starts with **Back**, the peer's last reported state with its source, and a facts line: the sender label, the count of kept messages, the count of new messages, and the page. **Show identity** and **Mark read** follow the facts, or move to a line of their own when the line does not fit. A thread shows up to 20 messages per page with that peer, newest first. When long messages would pass the drawing limit, a thread page shows fewer, and a dim line says how many moved to the next page. Each message starts with `new` while it is new, its time, `←` for a message to you or `→` for a send attempt, and its kind: `message`, `finished`, `failed`, or `send attempted`. The sender label and any `suspicious` mark follow. A dim day line comes before the first message of an earlier day. The summary shows only when the sender wrote one that differs from the first line of the message. A long message shows its first 2,000 characters and says how many more are in the transcript. **Newer** and **Older** page through the kept messages, on one row with the page number and the count of messages on other pages.
A thread starts with **Back**, the peer's last reported state with its source, and a facts line: the word Sender with the sender level, such as `Sender unverified`, the count of kept messages, the count of new messages, and the page. **Show identity** and **Mark read** follow the facts, or move to a line of their own when the line does not fit. A thread shows up to 20 messages per page with that peer, newest first. When long messages would pass the drawing limit, a thread page shows fewer, and a dim line says how many moved to the next page. Each message starts with `new` while it is new, its time, `←` for a message to you or `→` for a send attempt, and its kind: `message`, `finished`, `failed`, or `send attempted`. The sender label and any `suspicious` mark follow. A dim day line comes before the first message of an earlier day. The summary shows only when the sender wrote one that differs from the first line of the message. A long message shows its first 2,000 characters and says how many more are in the transcript. **Newer** and **Older** page through the kept messages, on one row with the page number and the count of messages on other pages.

A send shows `send attempted` with the recipient and text that High Command observed. Its reported send result shows on a line of its own, in the error color when the send was not delivered. A dim note above the first send of a page says that sends show observed text and that a reported result is not a receipt. **Show send details** shows the refusal reason, up to 2,000 characters. When available, it also shows the core lines: the recipient, text, and queue outcome that Claude Code itself received for the latest call. A dim note above the core lines says that this evidence covers only the latest traced call. It does not prove that the recipient read the message. **Hide send details** closes them again. `/highcommand` labels sends as attempts.

Expand Down
17 changes: 7 additions & 10 deletions hooks/views.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BoxProps, ButtonProps, ElementConstructor, RenderElement, TextProps } from 'claude-code'

import type { HighCommandAgent, HighCommandCapture, HighCommandEntry, HighCommandTrust } from '../types'
import type { HighCommandAgent, HighCommandCapture, HighCommandEntry } from '../types'
import { isNewer, labelOf, unreadTotal, type RosterRow, type Thread } from './inbox'
import { LIMITS } from './limits'
import {
Expand Down Expand Up @@ -255,11 +255,6 @@ function laneTitle(model: PaneModel): string {
return `Agent observations: ${agentName(model.agents?.find(agent => agent.id === model.laneId), model.laneId)}`
}

/** Color `warning` for the weakest sender label, as for `suspicious`; the words stay, so color only adds a signal. */
function trustColor(trust: HighCommandTrust | undefined): TextProps {
return trust === 'unverified' ? { color: 'warning' } : {}
}

/**
* Row 1 of every view: Back where there is a level above, a dim prefix that truncates from its start, the current place
* in bold, and the dim key hint when the row and the hint fit `columns`, the band's rule. While the place fits the row
Expand Down Expand Up @@ -368,7 +363,9 @@ function twoLineRow(ui: HighCommandElements, args: RosterRowArgs, columns: numbe
const trust = trustLabel(row.trust)
const button = peerButton(ui, args)
const marks = dotted([
trust !== undefined && [styled(Text, trust, trustColor(row.trust))],
// Every sender label is plain text: in tmux each teammate's pane makes every message `unverified sender`, and a
// color there would hide a real `suspicious`.
trust !== undefined && [trust],
row.isSuspicious && [<Text color="warning">suspicious</Text>],
isRefused(row.last) && [<Text color="error">{NOT_DELIVERED}</Text>],
])
Expand Down Expand Up @@ -448,7 +445,7 @@ function columnsRow(ui: HighCommandElements, args: RosterRowArgs, stateWidth: nu
<Box width={CELLS.time} flexShrink={0} justifyContent="flex-end">
<Text {...dim}>{isSameDay(row.last.at, now) ? timeLabel(row.last.at) : dayLabel(row.last.at)}</Text>
</Box>
<Box width={CELLS.sender} flexShrink={0}>{row.trust !== undefined && <Text {...trustColor(row.trust)}>{row.trust}</Text>}</Box>
<Box width={CELLS.sender} flexShrink={0}>{row.trust !== undefined && <Text>{row.trust}</Text>}</Box>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<Box width={stateWidth} flexShrink={0}>{state !== undefined && <Text {...dim} wrap="truncate-end">{state}</Text>}</Box>
<Box flexGrow={1} flexShrink={1} minWidth={0}>
<Text {...(isRead ? {} : { bold: true })} wrap="truncate-end">
Expand Down Expand Up @@ -595,7 +592,7 @@ function entryView(ui: HighCommandElements, entry: HighCommandEntry, args: Entry
const weight: TextProps = isFresh ? { bold: true } : {}
const { arrow, word } = labelOf(entry)
const marks = dotted([
entry.trust !== undefined && [styled(Text, trustLabel(entry.trust)!, trustColor(entry.trust))],
entry.trust !== undefined && [trustLabel(entry.trust)!],
entry.isSuspicious === true && [<Text color="warning">suspicious</Text>],
])
const delivery = entry.delivery
Expand Down Expand Up @@ -656,7 +653,7 @@ function threadView(ui: HighCommandElements, model: PaneModel, thread: Thread, a
const { Box, Text, Button } = ui
const { row, since, shown, hidden, fresh } = thread
const facts = dotted([
row.trust !== undefined && ['Sender ', styled(Text, row.trust, trustColor(row.trust))],
row.trust !== undefined && [`Sender ${row.trust}`],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
[count(shown.length + hidden, 'retained message')],
fresh > 0 && [`${fresh} new`],
thread.pages > 1 && [`page ${thread.page + 1} of ${thread.pages}`],
Expand Down