Status: how Noaam knows what a tool is doing
The dot on each item in the sidebar says what that tool is up to. This is where it comes from, and how to teach Noaam a tool it has never heard of.
What the dot says
| Dot | Meaning |
|---|---|
| orange with ? | needs you: a permission prompt or a question. Stays until it’s answered |
| orange | to read: a turn finished (or a notification came) while you were looking elsewhere. Looking at the item clears it |
| orange ring | maybe unread: it finished while Noaam couldn’t tell whether you were looking |
| blue | working |
| dashed blue | probably working: only a guess says so (a program running in a terminal) |
| faded blue | last heard working, but that report is old |
| green | open and idle |
| dashed green | open, but nothing reports its activity in this presentation |
| red | crashed: exited with an error or a signal, or its session’s process died |
| none | closed |
Why this status? in an item’s menu says which source decided each part, how long ago, from what. Mark as Read clears a dot by hand.
Where it comes from
Three kinds of source, in order of how much they count:
- The tool’s own word. A status file it keeps per session (Claude Code writes one), or the tool telling Noaam through a hook or notify setting (Codex does). Also a script you write.
- What it shows in its terminal. The window title (most tools put a spinner there while working), a progress bar, desktop notifications, the bell.
- A guess. A program other than the tool holding the terminal (a build in a shell).
A fresher report from a higher kind wins; a report tied to a process is dropped when the process dies; one with nothing behind it goes stale.
Free, for every tool in a terminal. Noaam runs the tool inside its own
terminal recorder, both in the embedded terminal and in whatever terminal
app the tool’s Own app command opens through {terminal}. From there it
always sees the tool start and exit (an exit code says closed or crashed)
and whether that window has focus, and it reads everything the tool prints
to the terminal. A tool started some other way gives Noaam only its files,
its hooks and your scripts.
Looking, and “to read”. A turn that ends while you are not looking at the item leaves something to read. Looking means: the item’s pane is shown (selected, or a visible tile) with Noaam in front; its docked window is shown; its own window is the focused one; or its terminal reports focus. A locked screen counts as not looking. When Noaam can’t tell, the dot is a hollow ring (or whatever you chose in Settings).
Presets
Where Noaam listens for a tool, and what it hears there means, is a preset: one JSON file per tool.
- Shipped:
Noaam.app/Contents/Resources/status-presets/*.json(Claude Code, Codex, a shell, an editor, generic). - Yours:
~/.config/noaam/status-presets/*.json. A file here with the sameidas a shipped one replaces it. Settings › Tools › Status › Edit preset file makes the copy and opens it; More › New preset for this tool starts one for a tool with no preset.
A tool uses the preset whose commands names the command it runs
(claude, codex, zsh…), or the one picked by hand in Settings › Tools ›
Status › More, else generic (the free basics). Changes to a file apply
within a second or two; no restart. Settings shows each source and the last
thing it heard, so you see your edit take.
The file
{
"id": "mytool",
"name": "My Tool",
"commands": ["mytool"],
"status": {
"adapters": [ …sources, see below… ],
"minWorkSeconds": 0,
"whenViewingUnknown": "soft",
"staleAfterSeconds": 600
}
}
commands: command names (the last path component) this preset is for.minWorkSeconds: work shorter than this leaves nothing to read (3 for a shell, solsnever lights up).whenViewingUnknown: when something finishes and Noaam can’t tell whether you were looking:soft(hollow ring),flag,skip.staleAfterSeconds: how long to believe “working” from a source with no process behind it.
These three can also be set per tool in Settings, over the preset.
Every source: {"id": "<unique name>", "kind": "<kind>", "enabled": true, "tier": …, …}.
tier lowers how much it counts, never raises: authoritative (the tool’s
own word), signal, heuristic (a guess). Each kind has a default.
The words
What a source may say about an item:
| Word | Meaning |
|---|---|
working |
mid-turn |
idle |
between turns |
ended |
a turn finished: something to read if you were elsewhere |
ended:quiet |
stopped (an interrupt): nothing to read |
needs-you |
a question or permission prompt; stays until answered |
answered |
the question was answered |
attention |
something to show you; clears when you look |
open, closed, crashed |
running or not |
ignore |
means nothing |
Kinds
terminalTitle — rules over the window title the tool sets.
{"id": "mytool.title", "kind": "terminalTitle", "rules": [
{"match": "^[⠋⠙⠹] |Thinking", "emit": "working"},
{"match": ".", "emit": "idle"}
]}
First match wins; patterns are POSIX extended regular expressions ([0-9],
not \d). Noaam reports only when the meaning changes, so a spinner
cycling through frames is one “working”. Codex: a braille spinner
(⠋ title) while working, the bare title when idle. Claude Code: ◐ /◑
while working, ✳ when idle.
terminalProgress — the progress bar (OSC 9;4) some tools show in the
title bar or tab: shown means working; removed means a turn ended. No
fields: {"id": "mytool.progress", "kind": "terminalProgress"}.
terminalNotify — rules over the text of desktop notifications the
tool sends through the terminal (OSC 9, 777, 99).
{"id": "mytool.notify", "kind": "terminalNotify", "rules": [
{"match": "permission|approve", "emit": "needs-you"},
{"match": ".", "emit": "attention"}
]}
terminalBell — {"id": "…", "kind": "terminalBell", "emit": "attention"}
(or needs-you, ignore).
foregroundJob — something other than the tool itself running in its
terminal (a build in a shell). A guess.
{"id": "…", "kind": "foregroundJob", "seconds": 3, "ignore": ["less", "man"]}
jsonFile — a file the tool keeps about each running session.
{"id": "mytool.sessionFile", "kind": "jsonFile",
"path": "{home}/.mytool/sessions/*.json",
"fields": {"id": "sessionId", "state": "status", "message": "waitingFor", "pid": "pid", "at": "updatedAt", "atUnit": "ms"},
"states": {"busy": "working", "idle": "idle", "waiting": "needs-you", "waiting/dialog open": "attention"},
"skip": {"kind": "bg"}}
fields says where things are in the JSON (dotted paths; numbers index
arrays). states maps the raw state, or state/message, to a word. pid
ties the report to a process: when it dies, the report goes. skip
ignores files where a field’s whole value matches a pattern. Placeholders:
{home}, {claudeHome}, {codexHome}.
hookEvents — the tool tells Noaam itself. Its hook or notify setting
runs noaam-status hook <id> with the tool’s JSON (last argument or stdin);
arguments are added to the tool’s command line each time Noaam starts it,
so the hook is set up without touching the tool’s own config.
{"id": "mytool.hooks", "kind": "hookEvents",
"arguments": ["--on-event", "{noaamStatus} hook mytool.hooks"],
"fields": {"event": "type", "subtype": "notification_type", "session": "session_id", "message": "text"},
"states": {"turn-start": "working", "turn-end": "ended", "ask": "needs-you", "Notification:idle": "ignore"},
"skip": {"input.0": "Generate a title.*"}}
states keys are event names, or event:subtype. Events naming a session
other than the item’s are ignored. {noaamStatus} is Noaam’s helper; from
inside any Noaam terminal a script can also just run
noaam-status needs-you "approve?" (also working, idle, ended,
attention).
discovery — whatever the tool’s Finding existing items source
reports about a session (mid-turn or not, when its last turn ended).
{"id": "mytool.index", "kind": "discovery"}
command — a script of yours, run every so often.
{"id": "mytool.script", "kind": "command", "run": "python3 {home}/.config/noaam/status-presets/mytool-status.py", "every": 5}
Any command line, run in your login shell (bash, Python, anything on your
PATH). It gets this tool’s items in NOAAM_ITEMS
([{"item": "…", "session": "…", "folder": "…"}]) and prints one JSON
object per line, or an array:
{"id": "<session>", "state": "working", "message": "…", "at": 1700000000}
{"item": "<item>", "state": "needs-you", "message": "approve?"}
at is epoch seconds, milliseconds or ISO 8601. Printing the same line
again is not a new event. Exit code and stderr show in Settings.
stream — the same, but started once and left running; it prints a
line the moment something happens (a log tail, an event stream). Restarted
with a growing pause if it exits.
{"id": "mytool.events", "kind": "stream", "run": "mytool events --json"}
Teaching Noaam a new tool, step by step
Say mytool, a coding agent CLI nobody has written a preset for.
- Settings › Tools › + › add it with its command. Its Status section says
no preset is for
mytooland lists the free basics. - More › New preset for this tool writes
~/.config/noaam/status-presets/mytool.jsonand opens it, with this document beside it. - Run the tool once in Noaam and look at Settings › Status while it works:
what the title row heard tells you whether a
terminalTitlerule is enough. Paste a title into the box there to try a rule. - If the tool keeps a status file, has hooks, or exposes its state some
other way, add the matching source from the list above. When only you
know how to read its state, a
commandscript in any language does. - Watch the item’s dot, and Why this status? when it doesn’t match what you expect: it names the source that decided.
The shipped claude.json and codex.json are complete examples. A preset
file can be shared; dropping one into the folder is all it takes.