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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# The Claude Code plugin's hook (#131) is a batch file: cmd reads it line by
# line and is only reliable with CRLF, whatever the checkout's own setting is.
*.cmd text eol=crlf
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,31 @@ the owner's own call, #99.)
first idle title is the agent STARTING, not working. Output scoring (`termActivity`) is only the
fallback, and an agent's startup paint is not work (`markBorn` / `startupOutput`). The rules live in
`lib/useAgentIndicator.ts`; change them there, with Prism's reasoning in hand.
- **CLAUDE CODE'S HOOKS ARE ITS WORD, ABOVE THE TITLE** (#131; owner, 2026-10-05: "go ahead and build
that"; spec `docs/superpowers/specs/2026-10-05-agent-hooks-design.md`, evidence in the research
folder's `prism-terminal/2026-10-05-agent-hooks-inventory.md`). `core/claude-plugin` is a Claude Code
plugin: each hook in `hooks/hooks.json` runs `hook.cmd`, a static echo (blocking events wait for it;
MEASURED 20-100 ms, so no node), in EXEC FORM (`cmd.exe` + `args` `/d /c call <root>/hook.cmd`): a
command string runs through PowerShell where Git Bash is missing and is a ParserError there, and
without `call` a plugin folder with brackets fails (both MEASURED, review of #131). Its `terminalSequence` Claude writes into its OWN terminal as
`ESC]777;prism-agent;state=working|question|done|failed[;kind=<StopFailure error>]BEL`. In-band: no
listener, no tab ids, and `claude -p` / SDK runs never write it (MEASURED). SessionStart/End are left
out (their bytes reached the pty 1 of 3 and 0 of 3). Main hands a shell the plugin through
`CLAUDE_CODE_PLUGIN_DIRS` (`ptyEnv`, the user's value kept, an inherited copy of ours dropped:
`claudePlugin.ts`), only where the host passes `claudePluginDir` (this app: `resources\claude-plugin`,
extraResources; dev reads the core's copy) AND the page's "Exact status from Claude Code"
(`agent-hooks`, on) is on; a warm shell is adopted only with the same answer. Prism passes no dir, so
nothing changes there until it ships the files. The reader: `lib/agentHookSignal` (parse, our prefix
only, else `false` so other OSC 777 users are untouched), termBus, `lib/agentHookState` (the pure
rules), `useAgentIndicator`. A HOOKED session is never scored from output nor read for a question;
its title only says an Esc (no hook fires on one, MEASURED: idle title 72 ms after): an idle title
after `working` with no Stop is idle with NO Finished line, and a Stop that lands after it still
finishes. The poll still decides presence. **Failed** is a third line (`agent-failed-on`, on), the
theme's red apart from the accent by hue (`marksApart`), over Finished and under Question, counted by
the badge, its kind on the tab's tooltip ("Failed: rate limit"); it raises Finished under it, so
with the switch off the tab reads as before. Sessions that never send a signal (Codex, an older
claude, an untrusted folder, plugins blocked by policy) keep the old method whole. Codex's
`[ ! ] Action Required` title is its Question. The `agentHooks` e2e holds it (it fails on main).
- **FINISHED AND QUESTION ARE LINES, EACH OPTIONAL; EVERY WORKING TAB HAS ITS OWN BAR; THE TASKBAR
COUNTS** (owner, 2026-09-28; spec `docs/superpowers/specs/2026-09-28-attention-and-warm-dictation-design.md`).
A tab whose agent finished, or waits on you, while you were NOT LOOKING (another tab in front, or
Expand Down
9 changes: 9 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ Everything else happens **only when you ask for it**:
- **What you run:** the terminal runs your own shell, and whatever you run in it reaches the
network as you tell it to.

## What it adds to Claude Code

Prism Terminal adds a small local plugin to the Claude Code sessions started in its tabs, so the tab
can show what the agent is doing: working, waiting on you, finished or failed. The plugin's hooks
print one fixed line into that tab's own terminal; they send nothing over the network, read
nothing, and write no file. It is passed through an environment variable of the tab's shell, never
written into your Claude Code settings. Settings > Appearance > "Exact status from Claude Code"
turns it off for every terminal opened afterwards.

This file is the privacy statement the [code signing policy](README.md#code-signing-policy) refers
to. If the app ever gains a request that is not listed here, this file changes in the same pull
request.
4 changes: 4 additions & 0 deletions core/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# The Claude Code plugin's hook (#131) is a batch file: cmd reads it line by
# line and is only reliable with CRLF. Here as well as at the repo root so the
# rule travels with core-dist, the split Prism checks out.
*.cmd text eol=crlf
10 changes: 10 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ ask me."* And: *"why can't this repo be the core?"* It can, and this is it.
runs `core/tools/fetch-whisper.mjs <dir>` at build time and ships that folder
as `resources/bin/whisper`, and grants its own window the `media` permission
(audio only).
**Claude Code's hooks** (#131) are in here too: the plugin itself
(`claude-plugin/`), the env injection (`main/terminal.ts` `ptyEnv`,
`main/claudePlugin.ts`), the reader (`renderer/lib/agentHookSignal.ts`,
`agentHookState.ts`, the OSC 777 handler in the panel) and the Failed line's
state and switches. The indicator reads signals in any host. A host ships the
plugin by copying `core/claude-plugin` beside its app (real files, not in an
asar) and passing that folder as `claudePluginDir` to `registerTermIpc`, plus
the page's setting to `termPrewarm`; one that passes no folder changes no
shell's environment. Its strip draws `failedIds` as a third line, and its
`themedAgentColors` may name `failed` (else `FAILED_RED`).
**AND WHAT THE TWO APPS MUST SHOW IDENTICALLY, TERMINAL OR NOT** (#28, owner,
2026-09-19). This WIDENS the core, on purpose, from "the terminal" to "what
the two apps share". Asked to build the update window (*"when you click the
Expand Down
5 changes: 5 additions & 0 deletions core/claude-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "prism-terminal-status",
"version": "1.0.0",
"description": "Lets a Prism Terminal tab show what this Claude Code session is doing: working, waiting on you, finished or failed. Each hook prints one fixed terminal sequence; nothing leaves the PC."
}
15 changes: 15 additions & 0 deletions core/claude-plugin/hook.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@echo off
rem PRISM TERMINAL READS THE AGENT STATE FROM THIS (#131). Claude Code runs it for
rem the events in hooks\hooks.json and writes the terminalSequence it prints into
rem its OWN terminal, so the sequence reaches that tab and nothing else. A static
rem echo and no node: blocking events wait for it (MEASURED 20 to 100 ms).
rem The arguments are fixed words from hooks.json: a state, and the error kind
rem of a failure. Nothing from stdin is read or repeated.
rem hooks.json starts it as cmd.exe /d /c call <this file> (exec form, no
rem shell): a command STRING runs through PowerShell where Git Bash is
rem missing, which cannot parse a quoted path and an argument (MEASURED).
if "%~2"=="" (
echo {"terminalSequence":"\u001b]777;prism-agent;state=%~1\u0007"}
) else (
echo {"terminalSequence":"\u001b]777;prism-agent;state=%~1;kind=%~2\u0007"}
)
Loading
Loading