agent-lookout 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ Changes that a user of Agent Lookout would notice, newest first.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.9 - 2026-10-08
8
+
9
+ The Mac app no longer looks frozen, a Claude Code session's details show what it last said, and Night is darker and crisper everywhere.
10
+
11
+ ### Added
12
+
13
+ - A Claude Code session's details show what it last said, under Last message: the text of its newest reply, up to its last 2,000 characters, as plain text, with its line breaks kept and Markdown as it was written. The page asks for it only while the details are open and the page is in sight, and again every two seconds while they stay open, until an answer says it is off or not read. Agent Lookout reads it from the end of the session's transcript only when it is asked for. Agent Lookout's server keeps it in memory for at most 15 seconds after the last ask, and forgets it sooner on the first poll of Claude Code that does not list the session. The page lets it go as soon as the details close or the session leaves the list. A Codex, Antigravity CLI or status-file session, or one on another machine, says it is not read. It is on by default, and `AGENT_LOOKOUT_LAST_MESSAGE=off` turns it off. `GET /api/sessions/last-message?id=<id>` gives the same answer to any program on this computer, as [PRIVACY.md](PRIVACY.md#a-sessions-last-message) says, and [docs/API.md](docs/API.md#get-apisessionslast-messageidid) has its answers.
14
+
15
+ ### Changed
16
+
17
+ - Night is darker and crisper: a deeper ground, denser smoked glass and finer, brighter edges, on the dashboard, in the Mac app's window and on the landing page. Day is unchanged.
18
+ - No program Agent Lookout starts is given its own settings any more. `claude`, `tmux`, `ssh`, `osascript`, the program that opens your browser and the Mac app's check of a downloaded update were handed every `AGENT_LOOKOUT_` setting in their environment, the mail server's password, the webhook's address and the push tokens among them, as only `gh` was not. None of them used them.
19
+
20
+ ### Fixed
21
+
22
+ - The Mac app looked frozen: a click or a scroll on most of its window, such as on List, Repos or Board, moved the window instead and never reached the page. The fields of light in the thin strip above the header were taken as part of the strip that moves the window, as Electron 44 has every child of that strip inherit it. Only the strip, the header and the rail's top cell move the window now.
23
+ - An Antigravity CLI session that waits for your approval stops needing you as soon as you answer in agy, not only once agy writes its next step: agy 1.3.1 logs the answer from another part of its program, with more after it, than Agent Lookout expected.
24
+
7
25
  ## 0.2.8 - 2026-10-07
8
26
 
9
27
  Allow or Deny a Claude Code prompt from the Mac app's notification and menu bar, pushes to your phone through ntfy or Pushover, and Antigravity CLI sessions, which need you while agy asks you to approve a tool.
package/PRIVACY.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code, Codex and Antigravity CLI sessions, and about the sessions of any agent that writes a status file for Agent Lookout to read, and which git branch each session's folder has checked out. By default Agent Lookout itself sends nothing anywhere. Email notifications are off unless you set them up. Once you do, it sends a short email when a session has waited, and, if you choose, when one finishes, fails or ends, through the mail server you name, to the address you name, and nothing else. [Email](#email) says what one holds. Webhook posts are off unless you set an address for them. Once you do, it sends the same notices as short JSON posts to that one address, such as a Slack channel's incoming webhook, and nowhere else. [Webhook](#webhook) says what one holds. Pushes to your phone, through ntfy or Pushover, are off unless you set one up. Once you do, it sends the same notices as short pushes through that service, to the one topic or the one user you name, and nowhere else. [ntfy](#ntfy) and [Pushover](#pushover) say what one holds. The Mac app, and only the Mac app, asks GitHub about once a day whether a newer version of it has been released, unless you turn that off in Settings. It sends nothing about your sessions: [Updates (Mac app only)](#updates-mac-app-only) says what it sends. The npm package, `npx agent-lookout` and the repository never check for anything. Pull requests are off unless you start Agent Lookout with `AGENT_LOOKOUT_PULL_REQUESTS=on`. Then it runs your own GitHub CLI, `gh`, which sends GitHub the owner and name of a session's repository and the name of its branch, with `gh`'s own login, which Agent Lookout never reads: [`gh`](#gh) says what it sends and when. Other machines are read only once you name them in `AGENT_LOOKOUT_REMOTES`. Then it runs your own `ssh` to each and asks the Agent Lookout running there for its sessions, and sends nothing of this computer's: [Another machine over SSH](#another-machine-over-ssh) says what is read. It does run Claude Code's own listing command, which may contact Anthropic the way Claude Code normally does. Its MCP server, `agent-lookout mcp`, runs only once you add it to an agent's app, and answers that app and nothing else. That app may pass what it learns to its model: [The MCP server](#the-mcp-server) says what an answer holds.
4
4
 
5
- For a Claude Code session that is waiting for you, Agent Lookout reads the last message of its transcript, to show what the session is asking, such as the command it wants to run or the question it put to you. It reads nothing of a transcript while the session is not waiting, and keeps nothing of it once the wait ends. It sends that line off the machine only if you turn it on for email, with `AGENT_LOOKOUT_EMAIL_ASKING=on`, for the webhook, with `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, or for a push, with `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, and then only in the email, post or push for that wait and its reminders. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH. That computer shows it, and its own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. `AGENT_LOOKOUT_WAITING_TEXT=off` on this computer keeps it from there too. No answer of `agent-lookout mcp` holds it. [Claude Code's transcripts](#claude-codes-transcripts) says exactly what is read, and `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off.
5
+ For a Claude Code session that is waiting for you, Agent Lookout reads the last message of its transcript, to show what the session is asking, such as the command it wants to run or the question it put to you. It keeps nothing of that line once the wait ends. It sends that line off the machine only if you turn it on for email, with `AGENT_LOOKOUT_EMAIL_ASKING=on`, for the webhook, with `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, or for a push, with `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, and then only in the email, post or push for that wait and its reminders. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH. That computer shows it, and its own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. `AGENT_LOOKOUT_WAITING_TEXT=off` on this computer keeps it from there too. No answer of `agent-lookout mcp` holds it. [Claude Code's transcripts](#claude-codes-transcripts) says exactly what is read, and `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off.
6
+
7
+ Agent Lookout also reads what a Claude Code session last said, the text of its newest reply, from the end of its transcript. This is on by default. It reads it only when it is asked for, and the dashboard asks only while that session's details are open. It keeps at most the last 2,000 characters of the reply, in memory only, for at most 15 seconds after it was last asked for, and forgets it sooner on the first poll of Claude Code that does not list the session. It never sends it anywhere on its own. The local API has no password, so any program on this computer, and on a shared Mac another user account, can ask for it too, and so can any program on another computer while that computer reads this one over SSH. `AGENT_LOOKOUT_LAST_MESSAGE=off` turns it off, and the Mac app opens no port. [A session's last message](#a-sessions-last-message) says exactly what is read.
6
8
 
7
9
  For Codex, Agent Lookout opens Codex's session files, which hold the whole conversation. It reads them to find when each turn started and ended, and keeps only that and the few fields listed below. It keeps no prompt, reply, command or output.
8
10
 
@@ -18,7 +20,7 @@ Every 2 seconds Agent Lookout lists the folder `~/.claude/sessions/` and reads e
18
20
 
19
21
  ### Claude Code's transcripts
20
22
 
21
- Claude Code keeps each session's conversation in a transcript, `~/.claude/projects/<folder>/<session ID>.jsonl`, where the folder is named after the session's folder. Agent Lookout reads one only while its session is waiting for you, as the registry or the `claude` command says, and reads nothing of any transcript otherwise.
23
+ Claude Code keeps each session's conversation in a transcript, `~/.claude/projects/<folder>/<session ID>.jsonl`, where the folder is named after the session's folder. Agent Lookout reads one while its session is waiting for you, as the registry or the `claude` command says, and when the session's last message is asked for, as [A session's last message](#a-sessions-last-message) says. It reads nothing of any transcript otherwise.
22
24
 
23
25
  For a waiting session it looks for the file in the folder named after the session's folder, and, when it is not there, in each folder in `~/.claude/projects/`, by name, which lists those folders. It uses the session ID only once it has checked that it is an ID, and opens only an ordinary file: it does not follow the transcript file when it is a link, and it never reads a named pipe or a device. The folders above the file are followed when they are links: a folder in `~/.claude/projects/` that is a link, or `projects` itself. It reads at most the last 256 KB of the file, by position, and reads it again only when the file's size or modified time has changed. While a wait goes on it does not look for a file again, except every 10 seconds when none was found.
24
26
 
@@ -33,7 +35,19 @@ From those bytes it takes the last tool the session asked to use that has no ans
33
35
 
34
36
  Everything else in those bytes, which can include your prompts, the agent's replies, the code it wrote and the output of tools, is dropped as soon as the line is made. When nothing in the end of the file fits, there is no line, and nothing is guessed.
35
37
 
36
- That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email, a webhook post or a push only when you turn that on, as [Email](#email), [Webhook](#webhook), [ntfy](#ntfy) and [Pushover](#pushover) say. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH, which shows it, and whose own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either.
38
+ That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email, a webhook post or a push only when you turn that on, as [Email](#email), [Webhook](#webhook), [ntfy](#ntfy) and [Pushover](#pushover) say. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH, which shows it, and whose own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either, and no session's last message is read.
39
+
40
+ ### A session's last message
41
+
42
+ This is on by default. Agent Lookout reads what a Claude Code session last said only when it is asked for, through `GET /api/sessions/last-message`. The dashboard asks only while a session's details are open. No poll reads it.
43
+
44
+ It reads it only for a session on this computer that is in the list it last made. It finds and opens the transcript as [Claude Code's transcripts](#claude-codes-transcripts) describes, by the session's ID, once it has checked that it is one, and the session's folder. The request only names the session. It does not follow the file when it is a link, never reads a named pipe or a device, and reads at most the last 256 KB, by position. It reads at most four transcripts a second, and for a second after a read it gives the same answer again without looking.
45
+
46
+ From those bytes it takes the text of the session's newest reply: the `text` parts of its last assistant message, joined with a blank line between. It leaves out the lines of the session's subagents, and never takes the model's thinking, the tools it asked to use, their output, your prompts or the lines Claude Code writes for itself. It keeps the line breaks, makes a tab two spaces and any other control character a space, takes out the characters that change the direction of text, and keeps at most the last 2,000 characters. Everything else in those bytes is dropped as soon as the text is made. When the reply is further back than those bytes, or there is none yet, there is no text, and nothing is guessed.
47
+
48
+ The text is kept in memory beside the place of the file and its size and modified time, for eight sessions at most, and only for 15 seconds after it was last asked for. It is forgotten sooner on the first poll that does not list the session. It is not written to disk, and it is never put in the session list, an event, the history, a notification, an email, a webhook post, a push or an answer of `agent-lookout mcp`. Agent Lookout never sends it anywhere on its own, and never asks another machine for one. The dashboard shows it in that session's details, holds it in the page only while they are open, and keeps none of it in the browser's storage, another tab or the address.
49
+
50
+ The local API has no password, as [Network](#network) says. So while Agent Lookout runs, any program on this computer, and on a shared Mac another user account, can ask it for up to 2,000 characters of what a listed Claude Code session last said. While another computer reads this one over SSH, any program or account on that computer can ask too, through its tunnel, as [SECURITY.md](SECURITY.md) says. A reply can quote code, paths and anything else the session wrote. A website cannot ask: the API answers only requests made to this computer, and refuses one that a browser says did not come from the dashboard's own page. Set `AGENT_LOOKOUT_LAST_MESSAGE=off` and no transcript is opened for it. `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off too. The Mac app opens no port, so there only its own window can ask.
37
51
 
38
52
  ### `claude agents --json --all`
39
53
 
@@ -242,18 +256,18 @@ Only when `AGENT_LOOKOUT_REMOTES` names other machines, Agent Lookout reads the
242
256
 
243
257
  For each machine it runs `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<a free port>:127.0.0.1:<its port> -- <target>`, with the target you named, such as `dev@devbox.local`, after `--`, and with the port Agent Lookout listens on there, 4777 unless you give another. `ssh` is the one on your `PATH`, or in `/usr/bin`, `/opt/homebrew/bin` or `/usr/local/bin`, or the one `AGENT_LOOKOUT_SSH_BIN` names. It is started directly, by its full path, never through a shell, with nothing on its input, and given the environment Agent Lookout was started with, so it uses your `~/.ssh/config` and your ssh agent as it does in a terminal. With BatchMode it asks nothing, so Agent Lookout never sees, types or keeps a password, a passphrase or a key, and never answers a question about a host key. ssh runs no command on the other machine: it forwards one port on this computer's `127.0.0.1` to Agent Lookout's port on that machine's own `127.0.0.1`. With `ControlMaster=no` and `ControlPath=none` it makes a connection of its own every time, even when your ssh config shares connections, so it never joins an ssh you have open and never leaves one running in the background: the forward ends when that ssh ends. Agent Lookout keeps the last lines ssh writes about a failure, in memory, and shows the last of them on the machine's card in Sources, such as `Permission denied (publickey).` When ssh ends, it is started again after a wait, and when Agent Lookout stops, it ends it.
244
258
 
245
- Through that port, every 2 seconds, it sends two requests and no others: `GET /api/health`, whose answer is the version of Agent Lookout there, and `GET /api/sessions`, whose answer is that machine's sessions and sources, as [docs/API.md](docs/API.md#get-apisessions) describes. The requests carry nothing of this computer's: no session, no setting, and not the `X-Agent-Lookout-Notifications` header, so they change nothing that machine's Agent Lookout believes about its own pages. Of the answer it keeps, in memory, what this computer shows of its own sessions: each session's name, folder, status, reason, the vendor's wording of a wait, its times, its app, its branch and repository name, its agent's name and whether its process still runs. It drops each session's process ID, Jump, Stop and links, and any permission request that machine holds for it, with what the request would run, as the answer is read, so nothing about a session there can act on this computer. It keeps each source there by its name and its state, and what each of its agents can report. It does not take that machine's time rules, or its word on whether it is in quiet hours: this computer's own decide when a session there is stale, and when a wait there is reminded of or held back here.
259
+ Through that port, every 2 seconds, it sends two requests and no others: `GET /api/health`, whose answer is the version of Agent Lookout there, and `GET /api/sessions`, whose answer is that machine's sessions and sources, as [docs/API.md](docs/API.md#get-apisessions) describes. The requests carry nothing of this computer's: no session, no setting, and not the `X-Agent-Lookout-Notifications` header, so they change nothing that machine's Agent Lookout believes about its own pages. It never asks that machine what a session there last said. Of the answer it keeps, in memory, what this computer shows of its own sessions: each session's name, folder, status, reason, the vendor's wording of a wait, its times, its app, its branch and repository name, its agent's name and whether its process still runs. It drops each session's process ID, Jump, Stop and links, and any permission request that machine holds for it, with what the request would run, as the answer is read, so nothing about a session there can act on this computer. It keeps each source there by its name and its state, and what each of its agents can report. It does not take that machine's time rules, or its word on whether it is in quiet hours: this computer's own decide when a session there is stale, and when a wait there is reminded of or held back here.
246
260
 
247
261
  That includes what a waiting session there is asking, such as `Run: npm test`, when that machine's Agent Lookout sends it, which it does unless `AGENT_LOOKOUT_WAITING_TEXT=off` is set there. With `AGENT_LOOKOUT_WAITING_TEXT=off` set on this computer, it is dropped as it arrives, and a waiting session there shows its reason alone, as one here does. Otherwise it is shown here as a waiting session's is, and in the notifications on this computer, and is kept no longer than the wait, as here. Like a session's on this computer, a session there and its events go into the history kept on this computer, without what it is asking, and into an email, a webhook post or a push as the settings for those say.
248
262
 
249
263
  ### Settings
250
264
 
251
- It reads forty-three settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CLAUDE_BIN`, `AGENT_LOOKOUT_CLAUDE_FEED`, `AGENT_LOOKOUT_WAITING_TEXT`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_ANTIGRAVITY_HOME`, `AGENT_LOOKOUT_STATUS_DIR`, `AGENT_LOOKOUT_HISTORY`, `AGENT_LOOKOUT_HISTORY_DIR`, `AGENT_LOOKOUT_SETTINGS_FILE`, `AGENT_LOOKOUT_NOTIFICATIONS`, `AGENT_LOOKOUT_TMUX`, `AGENT_LOOKOUT_TERMINAL_JUMP`, `AGENT_LOOKOUT_STOP`, `AGENT_LOOKOUT_PULL_REQUESTS`, `AGENT_LOOKOUT_ANSWER`, `AGENT_LOOKOUT_ANSWER_SOCKET`, `AGENT_LOOKOUT_ANSWER_WAIT`, `AGENT_LOOKOUT_PORT`, `AGENT_LOOKOUT_HOST`, `AGENT_LOOKOUT_REMOTES`, `AGENT_LOOKOUT_SSH_BIN`, Codex's own `CODEX_HOME`, the six email settings, `AGENT_LOOKOUT_EMAIL_TO`, `AGENT_LOOKOUT_SMTP_URL`, `AGENT_LOOKOUT_EMAIL_FROM`, `AGENT_LOOKOUT_EMAIL_AFTER`, `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_EMAIL_ASKING`, the four webhook settings, `AGENT_LOOKOUT_WEBHOOK_URL`, `AGENT_LOOKOUT_WEBHOOK_EVENTS`, `AGENT_LOOKOUT_WEBHOOK_AFTER` and `AGENT_LOOKOUT_WEBHOOK_ASKING`, the five ntfy settings, `AGENT_LOOKOUT_NTFY_URL`, `AGENT_LOOKOUT_NTFY_TOKEN`, `AGENT_LOOKOUT_NTFY_EVENTS`, `AGENT_LOOKOUT_NTFY_AFTER` and `AGENT_LOOKOUT_NTFY_ASKING`, and the five Pushover settings, `AGENT_LOOKOUT_PUSHOVER_TOKEN`, `AGENT_LOOKOUT_PUSHOVER_USER`, `AGENT_LOOKOUT_PUSHOVER_EVENTS`, `AGENT_LOOKOUT_PUSHOVER_AFTER` and `AGENT_LOOKOUT_PUSHOVER_ASKING`. The `agent-lookout` command reads one more, `AGENT_LOOKOUT_URL`, the address of the Agent Lookout to ask, and `agent-lookout status` reads tmux's own `TMUX`, only to tell whether tmux is running it. `AGENT_LOOKOUT_CLAUDE_HOME` replaces `~/.claude` in everything above, and `AGENT_LOOKOUT_ANTIGRAVITY_HOME` replaces `~/.gemini/antigravity-cli`. [Email](#email) says how the email settings are kept, [Webhook](#webhook) how the webhook's are, and [ntfy](#ntfy) and [Pushover](#pushover) how theirs are. The time rules and the permission rules are not in the environment: Agent Lookout keeps them in a file of its own, as [The settings file](#the-settings-file) says. The [guide](docs/GUIDE.md#settings-you-can-change) says what each setting does.
265
+ It reads forty-four settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CLAUDE_BIN`, `AGENT_LOOKOUT_CLAUDE_FEED`, `AGENT_LOOKOUT_WAITING_TEXT`, `AGENT_LOOKOUT_LAST_MESSAGE`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_ANTIGRAVITY_HOME`, `AGENT_LOOKOUT_STATUS_DIR`, `AGENT_LOOKOUT_HISTORY`, `AGENT_LOOKOUT_HISTORY_DIR`, `AGENT_LOOKOUT_SETTINGS_FILE`, `AGENT_LOOKOUT_NOTIFICATIONS`, `AGENT_LOOKOUT_TMUX`, `AGENT_LOOKOUT_TERMINAL_JUMP`, `AGENT_LOOKOUT_STOP`, `AGENT_LOOKOUT_PULL_REQUESTS`, `AGENT_LOOKOUT_ANSWER`, `AGENT_LOOKOUT_ANSWER_SOCKET`, `AGENT_LOOKOUT_ANSWER_WAIT`, `AGENT_LOOKOUT_PORT`, `AGENT_LOOKOUT_HOST`, `AGENT_LOOKOUT_REMOTES`, `AGENT_LOOKOUT_SSH_BIN`, Codex's own `CODEX_HOME`, the six email settings, `AGENT_LOOKOUT_EMAIL_TO`, `AGENT_LOOKOUT_SMTP_URL`, `AGENT_LOOKOUT_EMAIL_FROM`, `AGENT_LOOKOUT_EMAIL_AFTER`, `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_EMAIL_ASKING`, the four webhook settings, `AGENT_LOOKOUT_WEBHOOK_URL`, `AGENT_LOOKOUT_WEBHOOK_EVENTS`, `AGENT_LOOKOUT_WEBHOOK_AFTER` and `AGENT_LOOKOUT_WEBHOOK_ASKING`, the five ntfy settings, `AGENT_LOOKOUT_NTFY_URL`, `AGENT_LOOKOUT_NTFY_TOKEN`, `AGENT_LOOKOUT_NTFY_EVENTS`, `AGENT_LOOKOUT_NTFY_AFTER` and `AGENT_LOOKOUT_NTFY_ASKING`, and the five Pushover settings, `AGENT_LOOKOUT_PUSHOVER_TOKEN`, `AGENT_LOOKOUT_PUSHOVER_USER`, `AGENT_LOOKOUT_PUSHOVER_EVENTS`, `AGENT_LOOKOUT_PUSHOVER_AFTER` and `AGENT_LOOKOUT_PUSHOVER_ASKING`. The `agent-lookout` command reads one more, `AGENT_LOOKOUT_URL`, the address of the Agent Lookout to ask, and `agent-lookout status` reads tmux's own `TMUX`, only to tell whether tmux is running it. `AGENT_LOOKOUT_CLAUDE_HOME` replaces `~/.claude` in everything above, and `AGENT_LOOKOUT_ANTIGRAVITY_HOME` replaces `~/.gemini/antigravity-cli`. [Email](#email) says how the email settings are kept, [Webhook](#webhook) how the webhook's are, and [ntfy](#ntfy) and [Pushover](#pushover) how theirs are. No program Agent Lookout starts is given any of them: `claude`, `gh`, `tmux`, `osascript`, `ssh`, the program that opens your browser and the Mac app's check of a downloaded update get Agent Lookout's environment without every `AGENT_LOOKOUT_` setting, and `ps` gets only a search path, a language and a time zone, so the mail server's password, the webhook's address and the push tokens never reach them. The time rules and the permission rules are not in the environment: Agent Lookout keeps them in a file of its own, as [The settings file](#the-settings-file) says. The [guide](docs/GUIDE.md#settings-you-can-change) says what each setting does.
252
266
 
253
267
  ## What it never reads
254
268
 
255
269
  - The `.key` files that sit beside the registry files in `~/.claude/sessions/`.
256
- - Claude Code's transcripts, in `~/.claude/projects/` or anywhere else, beyond the end of a waiting session's own, as [Claude Code's transcripts](#claude-codes-transcripts) describes. Of what it reads there, it keeps one line about the tool the session is asking to use, and none of your prompts, the agent's replies, your code or the output of tools.
270
+ - Claude Code's transcripts, in `~/.claude/projects/` or anywhere else, beyond the end of a waiting session's own and of a session whose last message is asked for, as [Claude Code's transcripts](#claude-codes-transcripts) and [A session's last message](#a-sessions-last-message) describe. Of what it reads there, it keeps one line about the tool the session is asking to use and, for a short while, the text of the session's newest reply, and none of your prompts, the agent's earlier replies, its thinking or the output of tools.
257
271
  - Claude Code's settings, credentials, history and memory files.
258
272
  - Codex's `auth.json`, `config.toml`, `history.jsonl`, its SQLite files (`*.sqlite`), its `log/` folder, `archived_sessions/` and compressed session files (`*.jsonl.zst`), and the Codex desktop app's `external_agent_session_imports.json` and `.codex-global-state.json`.
259
273
  - In `~/.gemini`: `oauth_creds.json`, `google_accounts.json`, `settings.json`, `config/hooks.json`, `history/` and any `history.jsonl`, and in the Antigravity CLI's folder its SQLite files, `conversation_summaries.db` and `conversations/*.db` with their `-wal` and `-shm` files, which are only looked up for when they were last changed, `transcript_full.jsonl`, `cli.log` and the logs in `log/` of agy programs `ps` does not show, `jetski_state.pbtxt`, `jetbox_summaries_proto.pb`, `installation_id`, `updater/`, `presence/`, any file in `annotations/` but a conversation's `.pbtxt`, `implicit/`, `crashes/` and the other files in each conversation's folder. Nor the Antigravity desktop app's folder, `~/.gemini/antigravity`, or the Antigravity IDE's.
@@ -278,7 +292,7 @@ The API refuses any request whose `Host` header is not `localhost`, `127.0.0.1`
278
292
 
279
293
  Each request the dashboard makes carries one header of Agent Lookout's own, `X-Agent-Lookout-Notifications`, which says which events that page has notifications on for: `off`, `on`, which means a session starting to wait, or `on; events=` followed by the names of the events, such as `on; events=needs-you,finished`. It holds nothing else. It goes to Agent Lookout's own server and nowhere else. [Notifications](#notifications) says what the server does with it. The request the Jump button sends for a session in tmux, Terminal or iTerm2 carries one more, `X-Agent-Lookout-Action: jump`, which names what is asked for, and the request the Clear history button in Settings sends carries `X-Agent-Lookout-Action: clear-history`, with an empty body. The request Stop session sends carries `X-Agent-Lookout-Action: stop` and the session's ID, and the one that ends the sessions left running carries `X-Agent-Lookout-Action: clean-up`, with each session's ID and the moment its idle began. The one Allow and Deny send carries `X-Agent-Lookout-Action: answer`, with the session's ID, Agent Lookout's own ID for the request and the answer. The one the Time rules card sends carries `X-Agent-Lookout-Action: time-rules`, with the three rules, and the one the Permission rules card sends carries `X-Agent-Lookout-Action: permission-rules`, with one change to the rules. Send a test, on the ntfy and Pushover cards, sends `X-Agent-Lookout-Action: phone-test`, with `{"channel": "ntfy"}` or `{"channel": "pushover"}`. In the Mac app, the Updates card also sends `X-Agent-Lookout-Action: check-for-updates` and `install-update`, each with the body `{}`, and `update-setting`, with `{"automatic": true}` or `false`. The Menu bar card sends `menu-bar-setting`, with `{"show": true}` or `false`.
280
294
 
281
- The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session), to answer a permission prompt Agent Lookout is holding, as Allow and Deny do, to change the time rules or the permission rules, as Settings does, which includes adding an allow rule that then answers prompts without a press, and to send a test push, as Send a test does, which counts toward the hour's 20 for that channel. A program that can do that can already type into your terminal, but on a shared computer another account could not. The Mac app opens no port, so there only its own window can ask any of this. It shares the settings file, though. A program running as you can write `~/.agent-lookout/settings.json`, or start `npx agent-lookout` beside the app and ask that copy to change a rule, and the Mac app puts the time rules and permission rules the file then holds in force at its next poll, an allow rule included. `AGENT_LOOKOUT_STOP=off` takes stopping away, and `AGENT_LOOKOUT_ANSWER=off` takes answering away.
295
+ The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. That includes up to 2,000 characters of what a Claude Code session last said, as [A session's last message](#a-sessions-last-message) says. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session), to answer a permission prompt Agent Lookout is holding, as Allow and Deny do, to change the time rules or the permission rules, as Settings does, which includes adding an allow rule that then answers prompts without a press, and to send a test push, as Send a test does, which counts toward the hour's 20 for that channel. A program that can do that can already type into your terminal, but on a shared computer another account could not. The Mac app opens no port, so there only its own window can ask any of this. It shares the settings file, though. A program running as you can write `~/.agent-lookout/settings.json`, or start `npx agent-lookout` beside the app and ask that copy to change a rule, and the Mac app puts the time rules and permission rules the file then holds in force at its next poll, an allow rule included. `AGENT_LOOKOUT_STOP=off` takes stopping away, `AGENT_LOOKOUT_ANSWER=off` takes answering away, and `AGENT_LOOKOUT_LAST_MESSAGE=off` takes last messages away.
282
296
 
283
297
  For a session in VS Code, the Jump button opens a `vscode://` address that contains the session ID. Your operating system hands it to VS Code. For a session in tmux, the Jump button sends a request that holds the session's ID to Agent Lookout's own server, which selects the pane as described under [`tmux`](#tmux). For a session in Terminal or iTerm2 it sends the same request, and the server brings the tab forward as described under [Terminal and iTerm2](#terminal-and-iterm2). That request, the one Clear history sends, the ones Stop session and End send, the one Allow and Deny send, the ones the Time rules and Permission rules cards send and the one Send a test sends are the eight that change anything, with the Mac app's four for its updates and its menu bar. The server refuses them from any website, though not from another program on this machine, as the paragraph above says, and the Mac app answers its four only for its own window. [SECURITY.md](SECURITY.md) lists its checks.
284
298
 
@@ -622,7 +636,7 @@ For each tool the agent calls, it asks Agent Lookout's own server for the list o
622
636
 
623
637
  It answers the app that started it, over stdout, and nothing and nobody else. An answer is one of three:
624
638
 
625
- - For each session: its ID, its name, its agent, the other machine it runs on, when it runs on one read over SSH, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, which is read from its transcript.
639
+ - For each session: its ID, its name, its agent, the other machine it runs on, when it runs on one read over SSH, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, or what a session last said, which are read from its transcript.
626
640
  - For each session that needs you: the same, with how long it has waited, and one sentence naming each of them and any agent that could not be read.
627
641
  - For each source: its state, the sentence that says how it is read or what went wrong, which can name the folders Agent Lookout reads, such as `~/.claude/sessions`, and, for another machine, the SSH target you named, such as `dev@devbox.local`, and the last line ssh printed when it failed, and what its agent can report.
628
642
 
@@ -640,7 +654,7 @@ Nothing. Each answer is worked out from one reading of the list and is gone once
640
654
 
641
655
  Agent Lookout keeps one thing about your sessions on disk: the history behind the Events log and the charts, so that they are still there after it restarts. [The history](#the-history) says what it holds, where it is, how long it is kept, how to clear it and how to turn it off. It also keeps its time rules and permission rules in a file, which holds nothing about your sessions: [The settings file](#the-settings-file). Apart from those, its own code writes no files, except the Mac app's, below.
642
656
 
643
- The latest session list is held in memory only, and is gone when Agent Lookout stops. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. So is a permission request held for the dashboard, which is also kept in memory only while it is held. With `AGENT_LOOKOUT_EMAIL_ASKING=on`, `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, it is also in the email, post or push for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications.
657
+ The latest session list is held in memory only, and is gone when Agent Lookout stops. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. So is a permission request held for the dashboard, which is also kept in memory only while it is held. With `AGENT_LOOKOUT_EMAIL_ASKING=on`, `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, it is also in the email, post or push for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications. What a Claude Code session last said is never in the session list: it is held in memory apart from it, for at most 15 seconds after it was last asked for, as [A session's last message](#a-sessions-last-message) says.
644
658
 
645
659
  ### The history
646
660
 
@@ -684,7 +698,7 @@ The dashboard saves seven values in your browser's local storage. Your theme cho
684
698
 
685
699
  ## What is on screen
686
700
 
687
- Session names, folder paths, repository names, branch names, pull request titles and what a waiting session is asking, such as a command or a file, can show what you are working on. Check a screenshot before you share it.
701
+ Session names, folder paths, repository names, branch names, pull request titles and what a waiting session is asking, such as a command or a file, can show what you are working on. So can what a Claude Code session last said, which its details show, and which can quote code, paths and anything else the session wrote. Check a screenshot before you share it.
688
702
 
689
703
  Resume puts a session's folder path and its ID on the clipboard, and only when you press it. From there, anything that reads your clipboard can see them, such as a clipboard manager, and on a Mac with Handoff on, Universal Clipboard makes them available on your other Apple devices. The page never reads the clipboard.
690
704
 
package/SECURITY.md CHANGED
@@ -12,7 +12,7 @@ The project has one maintainer, so replies are best effort. Fixes land on `main`
12
12
 
13
13
  ## What the app handles
14
14
 
15
- Agent Lookout reads metadata about the Claude Code, Codex and Antigravity CLI sessions on your machine, and, once you name them, on other machines of yours that run Agent Lookout, read over SSH: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, and only then, it also reads that repository's `config` and its remote's `refs/remotes/<remote>/HEAD`, and runs the person's own `gh`, directly and never through a shell, as `gh pr view --repo github.com/<owner>/<name> --json number,title,state,isDraft,statusCheckRollup -- <branch>`, which sends GitHub the repository's owner and name and the branch's name: [PRIVACY.md](PRIVACY.md#gh). On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a waiting session's own, opened without following a link, and keeps one line of it, what the session is asking, until the wait ends. That line goes to the dashboard and to notifications on this machine, never to an email, a webhook post or `agent-lookout mcp`. It does open Codex's session files, which hold whole conversations, and keeps only when turns started and ended and a few fields. It writes to no agent's files. It ends a Claude Code session's process only when the person presses Stop and confirms, and answers a Claude Code permission prompt only when the person presses Allow or Deny, or when a permission rule the person added in Settings decides it, by the routes described below, and never on its own. It also reads the folder of status files, `~/.agent-lookout/sessions` or the one `AGENT_LOOKOUT_STATUS_DIR` names, where any program can write a small JSON file to show one of its sessions, and it writes nothing there. It keeps the Events log and the counts behind its charts in its own folder, `~/.agent-lookout/history`, which it makes with mode 700, in files of mode 600 that it neither reads nor writes through a link. They hold session IDs and names, statuses and times, and never what a waiting session is asking. `AGENT_LOOKOUT_HISTORY=off` keeps them in memory only. Beside them it keeps the time rules and the permission rules set in Settings in `~/.agent-lookout/settings.json`, or the file `AGENT_LOOKOUT_SETTINGS_FILE` names, of mode 600, which holds nothing about a session and which it neither reads nor writes through a link. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and keeps.
15
+ Agent Lookout reads metadata about the Claude Code, Codex and Antigravity CLI sessions on your machine, and, once you name them, on other machines of yours that run Agent Lookout, read over SSH: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, and only then, it also reads that repository's `config` and its remote's `refs/remotes/<remote>/HEAD`, and runs the person's own `gh`, directly and never through a shell, as `gh pr view --repo github.com/<owner>/<name> --json number,title,state,isDraft,statusCheckRollup -- <branch>`, which sends GitHub the repository's owner and name and the branch's name: [PRIVACY.md](PRIVACY.md#gh). On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a session's own, opened without following a link, while the session waits or when `GET /api/sessions/last-message` asks what it last said. Of a waiting session's it keeps one line, what the session is asking, until the wait ends. That line goes to the dashboard and to notifications on this machine, never to an email, a webhook post or `agent-lookout mcp`. Of a last message it keeps up to the last 2,000 characters of the session's newest reply in memory, for at most 15 seconds after the last ask, and gives them only in that route's answer. Any program on this computer, and on a shared Mac another user account, can ask for them, as below. `AGENT_LOOKOUT_LAST_MESSAGE=off` stops it, and the Mac app opens no port. It does open Codex's session files, which hold whole conversations, and keeps only when turns started and ended and a few fields. It writes to no agent's files. It ends a Claude Code session's process only when the person presses Stop and confirms, and answers a Claude Code permission prompt only when the person presses Allow or Deny, or when a permission rule the person added in Settings decides it, by the routes described below, and never on its own. It also reads the folder of status files, `~/.agent-lookout/sessions` or the one `AGENT_LOOKOUT_STATUS_DIR` names, where any program can write a small JSON file to show one of its sessions, and it writes nothing there. It keeps the Events log and the counts behind its charts in its own folder, `~/.agent-lookout/history`, which it makes with mode 700, in files of mode 600 that it neither reads nor writes through a link. They hold session IDs and names, statuses and times, and never what a waiting session is asking. `AGENT_LOOKOUT_HISTORY=off` keeps them in memory only. Beside them it keeps the time rules and the permission rules set in Settings in `~/.agent-lookout/settings.json`, or the file `AGENT_LOOKOUT_SETTINGS_FILE` names, of mode 600, which holds nothing about a session and which it neither reads nor writes through a link. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and keeps.
16
16
 
17
17
  With notifications turned on in Settings, the dashboard page hands a session's name and a fixed text, the reason it waits or Finished, Failed or Ended, to the browser's Notifications API, which passes them to the operating system. When no dashboard page is open, the local server shows the notification itself on macOS: it runs `/usr/bin/osascript` with a fixed script, and passes the name and the text to that script as arguments. Each dashboard page tells the server whether its notifications are on, in a header on its requests, and the server shows its own only while the last page to say so said they are on, or `AGENT_LOOKOUT_NOTIFICATIONS=on` was set and no page has said anything. Nothing is sent over the network for any of this. [PRIVACY.md](PRIVACY.md#notifications) has the details.
18
18
 
@@ -24,7 +24,7 @@ Pushes to a phone are off unless they are set up in the environment Agent Lookou
24
24
 
25
25
  Other machines are read only when `AGENT_LOOKOUT_REMOTES` names them, each as a name and a target. A name is letters, digits and dashes, and a target is a host alias, a host name or `user@host` made of letters, digits, dots, dashes and underscores, starting with a letter, a digit or an underscore, so no space, leading dash, option such as `-oProxyCommand=`, or shell character gets in. One entry that is not, and no machine is read. For each, the collector runs the person's own `ssh`, found on `PATH`, in a fixed place or by `AGENT_LOOKOUT_SSH_BIN`, by its full path, never through a shell, with nothing on its input, as `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<free port>:127.0.0.1:<port there> -- <target>`. The target comes after `--`, as one argument. ssh runs no command on the other machine and asks for nothing, so Agent Lookout never handles a key or a password. `ControlMaster=no` and `ControlPath=none`, given on the command line, come before the person's own ssh config, so each ssh is a connection of its own: it never joins an ssh the person has open, whose forward would outlive Agent Lookout, and never leaves one in the background through `ControlPersist`. When Agent Lookout stops, it sends each ssh SIGTERM, and SIGKILL to one that has not ended two seconds later. Through the tunnel the collector sends `GET /api/health` and `GET /api/sessions` to the Agent Lookout there and nothing else. The requests arrive on that machine's own loopback, so its `Host` and `Origin` checks pass as they do for its own page, and they carry no `Origin` and no notifications header. The page on this computer never talks to the other machine, only to its own server. The answer is read as untrusted: every field is checked and cleaned again, sessions there are given ids of their own, `remote:<name>:` and the id there, and their process ID, Jump, Stop, held permission request and links are dropped, so nothing in an answer can make this computer select a pane, bring a tab forward, open a link, stop a process or answer a permission prompt. [PRIVACY.md](PRIVACY.md#another-machine-over-ssh) says what is kept.
26
26
 
27
- While a tunnel is up, its end on this computer's `127.0.0.1` is open to every program and every user account on this computer, not only to Agent Lookout, and it leads to the other machine's Agent Lookout and its whole API, not only the two routes the collector asks for. Through it, a program here can read that machine's sessions and send the routes that change something there: `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer`, `POST /api/settings/time-rules`, `POST /api/settings/permission-rules` and `POST /api/phone/test`. Their `Origin` and action-header checks stop a website, but not a program that is not a browser, which can set those headers. Before a machine is named, only someone on that machine could do this. There is also a moment, before ssh has signed in and listens on the port it was given, when that port is free: another program here could listen on it first and answer as that machine's Agent Lookout, and its sessions would be shown as that machine's, with no Jump, no Stop and no Allow or Deny. So name a machine only from a computer whose other users and programs you trust as you trust those on that machine.
27
+ While a tunnel is up, its end on this computer's `127.0.0.1` is open to every program and every user account on this computer, not only to Agent Lookout, and it leads to the other machine's Agent Lookout and its whole API, not only the two routes the collector asks for. Through it, a program here can read that machine's sessions, ask what each of its Claude Code sessions last said with `GET /api/sessions/last-message`, and send the routes that change something there: `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer`, `POST /api/settings/time-rules`, `POST /api/settings/permission-rules` and `POST /api/phone/test`. Their `Origin` and action-header checks stop a website, but not a program that is not a browser, which can set those headers. Before a machine is named, only someone on that machine could do this. There is also a moment, before ssh has signed in and listens on the port it was given, when that port is free: another program here could listen on it first and answer as that machine's Agent Lookout, and its sessions would be shown as that machine's, with no Jump, no Stop and no Allow or Deny. So name a machine only from a computer whose other users and programs you trust as you trust those on that machine.
28
28
 
29
29
  Eight routes of the local API change something: `POST /api/jump`, which the Jump button of a session in tmux, Terminal or iTerm2 sends, `POST /api/history/clear`, which the Clear history button in Settings sends, `POST /api/sessions/stop` and `POST /api/sessions/clean-up`, which Stop session and the ending of the sessions left running send, `POST /api/permission/answer`, which Allow and Deny send, `POST /api/settings/time-rules`, which the Time rules card in Settings sends, `POST /api/settings/permission-rules`, which the Permission rules card in Settings sends, and `POST /api/phone/test`, which Send a test on the ntfy and Pushover cards sends. Every other route only reads. `POST /api/phone/test` sends one test push through the channel it names, which says only that it is a test and holds nothing of any session. It makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: phone-test`, and takes a body of 64 bytes or less that is exactly `{"channel": "ntfy"}` or `{"channel": "pushover"}`. Nothing in it reaches the push or says where it goes: that is the channel's own setting, and a channel that is not set up is answered 409 and sent nothing. It sends one a second, one at a time, and counts toward the channel's 20 an hour, so a program on this computer that sends it again and again can spend that hour's pushes, and no more. The two that stop sessions, and the one that answers, are described after the jump route, below. `POST /api/history/clear` deletes the history files in Agent Lookout's own folder and empties the Events log and the history in memory. Its body is exactly `{}`, nothing in it names a file, and it makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: clear-history`. `POST /api/settings/time-rules` puts the time rules in force and writes them to the settings file. It makes the same checks as the jump route, with `X-Agent-Lookout-Action: time-rules`, and takes a body of 2,048 bytes or less that is exactly the three rules, each with exactly its own fields and every value in range, and nothing else. Nothing in it names a file: the file is the one Agent Lookout was started with, written whole to a new file of mode 600 that then takes its place, never through a link. The rules decide only when Agent Lookout tells you of something, and which idle sessions count as stale, and so which are offered under Left running: ending one still takes a press of End and every check below. `POST /api/settings/permission-rules`, described with the route that answers, below, changes the rules that answer a permission prompt without a press. The rest of this part is about `POST /api/jump`. For a session in tmux it can change three things, all inside tmux: which pane is selected in its window, which window is selected in its tmux session, and which tmux session each attached terminal is showing. For a session in a tab of Terminal or iTerm2, on macOS, it can bring that tab forward: select it in its window, bring the window back from the Dock if it was minimised and in front of the app's others, and bring the app to the front. It sends no keys, runs nothing inside a pane or a tab, starts no tmux server and starts no app.
30
30
 
@@ -88,11 +88,15 @@ Session names and folder paths can be sensitive. The main risk is that something
88
88
  - A post sent over plain HTTP to an address that is not on this machine, or to a server whose certificate was not checked.
89
89
  - What a waiting session is asking reaching an email while `AGENT_LOOKOUT_EMAIL_ASKING` is not `on`, a post while `AGENT_LOOKOUT_WEBHOOK_ASKING` is not `on`, an email's headers, or the email or post for anything but a wait.
90
90
  - What a waiting session is asking being written to a file, an event or the history, or reaching `GET /api/email`, `GET /api/webhook`, `GET /api/ntfy` or `GET /api/pushover`.
91
+ - What a session last said reaching anything but the answer of `GET /api/sessions/last-message`: the session list, an event, the history, a file, a log, a notification, an email, a post, a push, `agent-lookout mcp` or a request to another machine. Also its being kept in memory more than 15 seconds after it was last asked for, or after a poll of Claude Code that does not list its session.
92
+ - `GET /api/sessions/last-message` opening a file a request names, or any file but the transcript of a Claude Code session the collector lists, reading more than its last 256 KB, following a link or opening a pipe, answering a request a browser marks as anything but `same-origin`, or opening a transcript while `AGENT_LOOKOUT_LAST_MESSAGE` or `AGENT_LOOKOUT_WAITING_TEXT` is `off`.
93
+ - What a session last said running as script or markup in the dashboard, or becoming a link.
91
94
  - A push sent, or a connection opened for one, while `AGENT_LOOKOUT_NTFY_URL` is unset, while either `AGENT_LOOKOUT_PUSHOVER_TOKEN` or `AGENT_LOOKOUT_PUSHOVER_USER` is unset, or while one of that channel's settings cannot be read.
92
95
  - The ntfy topic, its token, Pushover's token or the user key reaching the dashboard, the API, an event, the history, the snapshot, a log, the console, a sentence Agent Lookout makes or a file, or going in an address rather than a header or a body.
93
96
  - A push that holds more than PRIVACY.md lists, that goes to any address but the ntfy server named or `https://api.pushover.net/1/messages.json`, including by following a redirect, or that is sent over plain HTTP to an ntfy server that is not on this machine, or to a server whose certificate was not checked.
94
97
  - What a waiting session is asking reaching a push while that channel's ASKING setting is not `on`, a push's title, or the push for anything but a wait and its reminders.
95
98
  - A session's name, folder or anything else from a session that changes the structure of a push, adds a field or a header to it, or changes where it goes.
99
+ - Any `AGENT_LOOKOUT_` setting, such as the mail server's password, the webhook's address or a push token, reaching the environment of a program Agent Lookout starts.
96
100
  - A test push that holds anything of a session, that goes while the hourly limit is full, or that a website, or a page served from anywhere but this machine, can make the app send.
97
101
  - Any write to `~/.claude` or to another tool's files, and any read of the `.key` files in `~/.claude/sessions/`.
98
102
  - Any write under the Codex folder (`~/.codex`, or the folder `CODEX_HOME` or `AGENT_LOOKOUT_CODEX_HOME` names), any open of a file in its `thread-writer-locks/`, and any read of a Codex file that PRIVACY.md does not list.
@@ -129,7 +133,7 @@ Session names and folder paths can be sensitive. The main risk is that something
129
133
 
130
134
  ## Out of scope
131
135
 
132
- - Other programs on the same machine reading the local API, sending it the header that turns the server's notifications on or off, changing the time rules or the permission rules, asking it to select a tmux pane or bring a tab forward, asking it to stop a Claude Code session, with every check it makes for the Stop button, or asking it to answer a permission prompt it holds, with every check it makes for Allow and Deny. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, allow a permission prompt it holds, or add a permission rule that allows one, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away, and `AGENT_LOOKOUT_ANSWER=off`, which takes answering away. A Claude Code session is such a program too. No allow rule may begin with a program that sends requests or runs the code it is given, such as `curl`, `nc`, `node` or `python`, or answer a subcommand on its list that fetches or runs code, such as `npm exec`, an option through which a program runs another, such as `git -c`, or a command that names Agent Lookout's own folder, its settings file or socket, or a folder above them. A command an allow rule still lets it run without asking can send these requests, and add a rule: one that runs a file it can edit, such as `npm test`, `make` or a project's own script, one that installs packages, such as `npm install`, whose install scripts run, one that sets a program for another to run later, such as `git config` under `git:*`, a program not on those lists that sends requests, and one that writes the settings file by a path its words do not show, such as an archive or a patch holding full paths. Claude Code's sandbox is the fence for those, and an allow rule never lets a command leave it. GUIDE.md's Permission rules section says so.
136
+ - Other programs on the same machine reading the local API, which includes up to 2,000 characters of what a listed Claude Code session last said, sending it the header that turns the server's notifications on or off, changing the time rules or the permission rules, asking it to select a tmux pane or bring a tab forward, asking it to stop a Claude Code session, with every check it makes for the Stop button, or asking it to answer a permission prompt it holds, with every check it makes for Allow and Deny. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, allow a permission prompt it holds, or add a permission rule that allows one, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away, `AGENT_LOOKOUT_ANSWER=off`, which takes answering away, and `AGENT_LOOKOUT_LAST_MESSAGE=off`, which takes last messages away. A Claude Code session is such a program too. No allow rule may begin with a program that sends requests or runs the code it is given, such as `curl`, `nc`, `node` or `python`, or answer a subcommand on its list that fetches or runs code, such as `npm exec`, an option through which a program runs another, such as `git -c`, or a command that names Agent Lookout's own folder, its settings file or socket, or a folder above them. A command an allow rule still lets it run without asking can send these requests, and add a rule: one that runs a file it can edit, such as `npm test`, `make` or a project's own script, one that installs packages, such as `npm install`, whose install scripts run, one that sets a program for another to run later, such as `git config` under `git:*`, a program not on those lists that sends requests, and one that writes the settings file by a path its words do not show, such as an archive or a patch holding full paths. Claude Code's sandbox is the fence for those, and an allow rule never lets a command leave it. GUIDE.md's Permission rules section says so.
133
137
  - A program on this machine that names itself after Terminal or iTerm2 in the list of processes, and so gives a session under it a Jump button. Pressing it asks the real app for a tab on that session's terminal, which it does not have.
134
138
  - A notification the server showed staying in Notification Centre after its session has moved on. The server cannot take one down, and PRIVACY.md says so.
135
139
  - The dev server started with Vite's `--host` flag, which makes it listen on the network. `npm start` refuses to listen on anything but loopback.
@@ -1 +1 @@
1
- import{r as y,j as t}from"./react-C2DiJaR6.js";import{b as G,t as K,r as q,f as U,a as C,c as X,d as V,h as _,m as J,H as Q,e as Z,D as ee,g as O,s as te,i as se,j as ne,S as oe,L as ae,C as re,k as ie,l as le,n as de}from"./index-BWFkRW4g.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";const ce={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},I={top:12,right:12,bottom:24,left:30},L=220,ue=76,he=104,R=1/60;function me({samples:s,start:n,end:a,tone:g,label:w,describe:l,className:c}){const p=y.useRef(null),[u,k]=y.useState(0),[h,i]=y.useState(null);y.useEffect(()=>{const e=p.current;if(!e)return;const o=new ResizeObserver(([j])=>{j&&k(Math.floor(j.contentRect.width))});return o.observe(e),()=>o.disconnect()},[]);const r=I.left,m=Math.max(r+1,u-I.right),x=I.top,d=L-I.bottom,N=a-n;let T=0;for(const e of s)e.at>=n&&e.at<=a&&e.value>T&&(T=e.value);const A=V(T),b=G(s,{start:n,end:a},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-n)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),B=K(n,a,Math.max(1,Math.floor((m-r)/ue))),M=h??1,H=n+M*N,E=q(b.runs,H),$=E?l(E.value):"Not measured",v=W(H),z=(e,o)=>{const j=o.left+r,f=m-r;i(Math.min(1,Math.max(0,(e-j)/f)))},Y=e=>{const j={ArrowLeft:f=>f-R,ArrowRight:f=>f+R,PageDown:f=>f-R*10,PageUp:f=>f+R*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),i(Math.min(1,Math.max(0,j(h??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${w}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(M*100),"aria-valuetext":`${C(H)}: ${$}`,onPointerMove:e=>z(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>i(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&i(o=>o??1)},onBlur:()=>i(null),onKeyDown:Y,className:X("relative w-full cursor-crosshair rounded-row",ce[g],c),style:{height:L},children:[u>0&&b.unmeasured.map((e,o)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},o)),u>0&&t.jsxs("svg",{"aria-hidden":!0,width:u,height:L,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,o)=>e.width>=he&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},o)),b.areas.map((e,o)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},o)),b.lines.map((e,o)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},o)),B.map(e=>{const o=W(e);return o<r+16||o>m-16?null:t.jsx("text",{"data-part":"time-tick",x:o,y:L-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:U(e)},e)}),h!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),h!==null&&u>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:M>.62?{top:x,right:u-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:C(H)}),t.jsx("span",{className:"block text-ink",children:$})]})]})}const xe=6e4;function pe(s,n){const[a,g]=y.useState(null),[w,l]=y.useState(null),c=s>Q,p=n?.startedAt??null,u=n?_(n).at:null;if(y.useEffect(()=>{if(!c)return;let h=!0;const i=()=>{Z(s).then(m=>{h&&(g({windowMs:s,history:m}),l(null))},()=>{h&&l(s)})};i();const r=setInterval(i,xe);return()=>{h=!1,clearInterval(r)}},[c,s,p,u]),!c)return{history:n,status:n?"ready":"loading"};const k=a?.windowMs===s&&(p===null||a.history.startedAt===p)&&(u===null||_(a.history).at===u)?a.history:null;return k?{history:J(k,n),status:"ready"}:{history:null,status:w===s?"failed":"loading"}}const P={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:s=>`A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle ${s} or more is stale, and is counted under Stale instead. The line is how many were idle at each moment.`}},D="–";function F({label:s,value:n,caption:a}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:n}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:a})]})}function fe({metric:s,state:n,now:a}){const[g,w]=y.useState("15m"),l=O.find(d=>d.id===g)??O[0],c=P[s],{history:p,status:u}=pe(l.ms,n.history),k=a-l.ms,h=p?te(p.points,s):[];let i=null;for(const d of h)d.at<k||d.at>a||(i===null||d.value>i)&&(i=d.value);const r=h[h.length-1],m=r&&a-r.at<=se?r.value:null,x=c.tone==="needs-you"&&ne(n.snapshot?.sessions)===0?"idle":c.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(F,{label:"Now",value:m===null?D:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(F,{label:"Highest",value:i===null?D:String(i),caption:i===null?"Nothing measured":`In ${l.words}`})})]}),t.jsx(oe,{label:"Time window",value:g,onValueChange:w,options:O.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:u==="ready"?t.jsx(me,{samples:h,start:k,end:a,tone:x,label:`${c.chart} over ${l.words}`,describe:c.describe}):u==="loading"?t.jsx(ae,{label:`Reading ${l.words}`,className:"h-full"}):t.jsx(re,{tone:"error",title:`${ie(l.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:typeof c.explainer=="string"?c.explainer:c.explainer(le(de(n.snapshot?.timeRules)))}),t.jsxs("p",{className:"mt-1.5 text-body text-ink-secondary",children:["Hatched time was not measured: Agent Lookout was not running, or could not read its source, or the history was cleared then. It is never drawn as zero."," ",n.history?.kept?.where==="memory"?"History covers the last six hours and starts again when Agent Lookout restarts.":"History covers the last six hours, and is kept when Agent Lookout restarts."]})]})]})}function je({metric:s,open:n,onOpenChange:a,state:g,now:w}){const l=P[s];return t.jsx(ee,{open:n,onOpenChange:a,title:l.title,description:l.about,size:"wide",children:t.jsx(fe,{metric:s,state:g,now:w})})}export{je as HistoryPanel};
1
+ import{r as y,j as t}from"./react-C2DiJaR6.js";import{b as G,t as K,r as q,f as U,a as C,c as X,d as V,h as _,m as J,H as Q,e as Z,D as ee,g as O,s as te,i as se,j as ne,S as oe,L as ae,C as re,k as ie,l as le,n as de}from"./index-DlDz48ci.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";const ce={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},I={top:12,right:12,bottom:24,left:30},L=220,ue=76,he=104,R=1/60;function me({samples:s,start:n,end:a,tone:g,label:w,describe:l,className:c}){const p=y.useRef(null),[u,k]=y.useState(0),[h,i]=y.useState(null);y.useEffect(()=>{const e=p.current;if(!e)return;const o=new ResizeObserver(([j])=>{j&&k(Math.floor(j.contentRect.width))});return o.observe(e),()=>o.disconnect()},[]);const r=I.left,m=Math.max(r+1,u-I.right),x=I.top,d=L-I.bottom,N=a-n;let T=0;for(const e of s)e.at>=n&&e.at<=a&&e.value>T&&(T=e.value);const A=V(T),b=G(s,{start:n,end:a},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-n)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),B=K(n,a,Math.max(1,Math.floor((m-r)/ue))),M=h??1,H=n+M*N,E=q(b.runs,H),$=E?l(E.value):"Not measured",v=W(H),z=(e,o)=>{const j=o.left+r,f=m-r;i(Math.min(1,Math.max(0,(e-j)/f)))},Y=e=>{const j={ArrowLeft:f=>f-R,ArrowRight:f=>f+R,PageDown:f=>f-R*10,PageUp:f=>f+R*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),i(Math.min(1,Math.max(0,j(h??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${w}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(M*100),"aria-valuetext":`${C(H)}: ${$}`,onPointerMove:e=>z(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>i(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&i(o=>o??1)},onBlur:()=>i(null),onKeyDown:Y,className:X("relative w-full cursor-crosshair rounded-row",ce[g],c),style:{height:L},children:[u>0&&b.unmeasured.map((e,o)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},o)),u>0&&t.jsxs("svg",{"aria-hidden":!0,width:u,height:L,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,o)=>e.width>=he&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},o)),b.areas.map((e,o)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},o)),b.lines.map((e,o)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},o)),B.map(e=>{const o=W(e);return o<r+16||o>m-16?null:t.jsx("text",{"data-part":"time-tick",x:o,y:L-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:U(e)},e)}),h!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),h!==null&&u>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:M>.62?{top:x,right:u-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:C(H)}),t.jsx("span",{className:"block text-ink",children:$})]})]})}const xe=6e4;function pe(s,n){const[a,g]=y.useState(null),[w,l]=y.useState(null),c=s>Q,p=n?.startedAt??null,u=n?_(n).at:null;if(y.useEffect(()=>{if(!c)return;let h=!0;const i=()=>{Z(s).then(m=>{h&&(g({windowMs:s,history:m}),l(null))},()=>{h&&l(s)})};i();const r=setInterval(i,xe);return()=>{h=!1,clearInterval(r)}},[c,s,p,u]),!c)return{history:n,status:n?"ready":"loading"};const k=a?.windowMs===s&&(p===null||a.history.startedAt===p)&&(u===null||_(a.history).at===u)?a.history:null;return k?{history:J(k,n),status:"ready"}:{history:null,status:w===s?"failed":"loading"}}const P={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:s=>`A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle ${s} or more is stale, and is counted under Stale instead. The line is how many were idle at each moment.`}},D="–";function F({label:s,value:n,caption:a}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:n}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:a})]})}function fe({metric:s,state:n,now:a}){const[g,w]=y.useState("15m"),l=O.find(d=>d.id===g)??O[0],c=P[s],{history:p,status:u}=pe(l.ms,n.history),k=a-l.ms,h=p?te(p.points,s):[];let i=null;for(const d of h)d.at<k||d.at>a||(i===null||d.value>i)&&(i=d.value);const r=h[h.length-1],m=r&&a-r.at<=se?r.value:null,x=c.tone==="needs-you"&&ne(n.snapshot?.sessions)===0?"idle":c.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(F,{label:"Now",value:m===null?D:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(F,{label:"Highest",value:i===null?D:String(i),caption:i===null?"Nothing measured":`In ${l.words}`})})]}),t.jsx(oe,{label:"Time window",value:g,onValueChange:w,options:O.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:u==="ready"?t.jsx(me,{samples:h,start:k,end:a,tone:x,label:`${c.chart} over ${l.words}`,describe:c.describe}):u==="loading"?t.jsx(ae,{label:`Reading ${l.words}`,className:"h-full"}):t.jsx(re,{tone:"error",title:`${ie(l.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:typeof c.explainer=="string"?c.explainer:c.explainer(le(de(n.snapshot?.timeRules)))}),t.jsxs("p",{className:"mt-1.5 text-body text-ink-secondary",children:["Hatched time was not measured: Agent Lookout was not running, or could not read its source, or the history was cleared then. It is never drawn as zero."," ",n.history?.kept?.where==="memory"?"History covers the last six hours and starts again when Agent Lookout restarts.":"History covers the last six hours, and is kept when Agent Lookout restarts."]})]})]})}function je({metric:s,open:n,onOpenChange:a,state:g,now:w}){const l=P[s];return t.jsx(ee,{open:n,onOpenChange:a,title:l.title,description:l.about,size:"wide",children:t.jsx(fe,{metric:s,state:g,now:w})})}export{je as HistoryPanel};
@@ -0,0 +1 @@
1
+ import{r as o,j as t}from"./react-C2DiJaR6.js";import{L as O,c as $,o as M,F as j,T as D,p as H,q as J,u as K,w as z,v as U,x as Y,y as G,J as V,z as X,A as Q,R as Z,D as ee,B as te,E as q,G as se,I as ne,n as ae,K as re,M as _,N as le,O as oe,P as ie,Q as ce,U as T,V as W,W as ue,X as de,f as A,Y as fe,Z as me,_ as he,$ as pe,a0 as xe,a1 as ge,a2 as je,a3 as be,a4 as we,a5 as ye,a6 as F,a7 as C,a8 as ke,a9 as ve}from"./index-DlDz48ci.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";function Ne(e){const a=o.useRef(null),[s,n]=o.useState(!1);return o.useLayoutEffect(()=>{const r=a.current;if(!r)return;const d=()=>n(r.scrollHeight>r.clientHeight);d();const l=new ResizeObserver(d);l.observe(r);for(const c of r.children)l.observe(c);return()=>l.disconnect()},[e]),[a,s]}const Se="Last messages are not read from another machine.",Ee="Its last message could not be read. The page asks again every two seconds.",Ae="The start of a longer message is left out.";function S({children:e}){return t.jsx("p",{"data-part":"last-message-state",className:"py-2.5 text-body text-ink-secondary",children:e})}function Le(e,a,s){switch(e){case"off":return a===void 0?"Last messages are off.":t.jsxs(t.Fragment,{children:["Last messages are off:"," ",t.jsx("code",{"data-slot":"fact",className:"font-mono text-fact",children:t.jsx(M,{children:a})})," ","is off."]});case"not-read":return`Agent Lookout does not read what ${s} sessions say.`;case"not-found":return"Its transcript was not found.";case"unreadable":return"Its transcript could not be read.";case"nothing-yet":return"It has not said anything yet.";case"too-far-back":return"Its last message is further back than the end of its transcript that Agent Lookout reads."}}function Re({text:e,cut:a}){const[s,n]=Ne(e);return t.jsxs("div",{className:"pt-2.5",children:[a&&t.jsx("p",{"data-part":"last-message-cut",className:"mb-1.5 text-caption text-ink-muted",children:Ae}),t.jsx("div",{ref:s,"data-part":"last-message-scroll","data-overflows":n||void 0,role:n?"group":void 0,tabIndex:n?0:void 0,"aria-label":n?"Its last message":void 0,className:$("thin-scroll max-h-60 overflow-y-auto",n&&"focus-visible:-outline-offset-2"),children:t.jsx("p",{"data-part":"last-message-text",className:"text-body whitespace-pre-wrap text-ink wrap-anywhere select-text",children:e})})]})}function Te({reading:e,remote:a,agent:s}){if(a)return t.jsx(S,{children:Se});if(e.status==="idle")return null;if(e.status==="loading")return t.jsx(O,{label:"Reading its last message",className:"justify-start px-0 py-2.5"});if(e.status==="failed"||e.answer===null)return t.jsx(S,{children:Ee});const{answer:n}=e;return n.message!==null?t.jsx(Re,{text:n.message.text,cut:n.message.cut}):t.jsx(S,{children:Le(n.reason,n.setting,s)})}function Fe({pullRequest:e}){return t.jsxs(j,{label:"Pull request",children:[t.jsxs(D,{"data-part":"pull-request",href:e.url,target:"_blank",rel:"noreferrer noopener",lines:2,tooltip:`#${e.number} ${e.title}`,className:"wrap-anywhere underline decoration-rule-strong underline-offset-2 transition-colors duration-120 hover:decoration-ink",children:[t.jsxs("span",{className:"tabular-nums",children:["#",e.number]})," ",e.title]}),t.jsx("span",{"data-part":"pull-request-state",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:H(e)})]})}function Oe(e,a={}){const{open:s=!0,onStopped:n,request:r=J}=a,[d,l]=o.useState({kind:"idle"}),[c,i]=o.useState(s);s!==c&&(i(s),!s&&!(d.kind==="confirming"&&d.busy)&&l({kind:"idle"}));const p=o.useRef(s);o.useEffect(()=>{p.current=s},[s]);const u=o.useId(),[m]=o.useState(()=>({stop:`${u}-stop`,cancel:`${u}-cancel`,outcome:`${u}-outcome`})),f=o.useRef(null),h=o.useRef(!1),g=o.useRef(!0);o.useEffect(()=>(g.current=!0,()=>{g.current=!1}),[]),o.useEffect(()=>{const w=f.current;f.current=null,w!==null&&document.getElementById(m[w])?.focus()},[d,m]);const x=o.useCallback(()=>{h.current||(f.current="cancel",l({kind:"confirming",busy:!1}))},[]),y=o.useCallback(()=>{h.current||(f.current="stop",l({kind:"idle"}))},[]),b=o.useCallback(()=>{h.current||(h.current=!0,l({kind:"confirming",busy:!0}),r(e).then(w=>{if(h.current=!1,!!g.current){if(w==="stopped"&&n?.(),!p.current){l({kind:"idle"});return}f.current="outcome",l({kind:"answered",outcome:w,at:Date.now()})}}))},[e,r,n]);return{step:d,ask:x,cancel:y,confirm:b,ids:m}}const $e={status:"idle",answer:null};function Me(e){return e.message===null&&(e.reason==="off"||e.reason==="not-read")}function De(e,a){const{active:s,hidden:n,beat:r}=a,[d,l]=o.useState(null);d!==null&&(!s||d.sessionId!==e)&&l(null);const c=o.useRef(void 0),i=o.useRef(!1),p=o.useRef(!1),u=o.useRef(0),m=o.useRef(e),f=o.useRef(!1);if(o.useEffect(()=>(f.current=!0,()=>{f.current=!1}),[]),o.useEffect(()=>{if((!s||m.current!==e)&&(m.current=e,c.current=void 0,i.current=!1,p.current=!1,u.current+=1),!s||n||p.current||i.current||c.current===r)return;c.current=r,i.current=!0;const g=u.current;K(e).then(x=>{g===u.current&&(i.current=!1,!(!f.current||x==="busy")&&(Me(x)&&(p.current=!0),l({sessionId:e,answer:x,failed:!1})))},()=>{g===u.current&&(i.current=!1,f.current&&l(x=>x?.sessionId===e&&x.answer!==null?x:{sessionId:e,answer:null,failed:!0}))})},[e,s,n,r]),!s)return $e;const h=d?.sessionId===e?d:null;return h?.answer?{status:"ready",answer:h.answer}:h?.failed?{status:"failed",answer:null}:{status:"loading",answer:null}}const qe="Read over SSH. Jump, Stop, Allow and Deny work on this computer only, so use that machine to go to it, stop it or answer it.",I=[];function _e(e,a){const s=CSS.escape(e),n=[...document.querySelectorAll(`:is([data-slot="session-row"], [data-slot="board-card"], [data-slot="hero-session"])[data-session="${s}"] a[data-part="name"]`)];return a!==null&&n.includes(a)?a:n.find(r=>!r.closest('[data-slot="hero-session"]'))??n[0]??null}function We({session:e,gone:a,asOf:s,now:n}){const r=e.status==="finished"||e.status==="failed",d=me(e),l=he(e),c=d?"stale":l||a&&e.status==="needs-you"?"answered":e.status,i=e.statusSince,p=i!==null&&!l?Math.max(0,s-i):null,u=pe(e,s),m=q(e),f=m?xe(e):null,h=m&&!a&&!e.ask&&e.waitingText?.trim()||null,g=i!==null?`${r?"at":d?"idle since":l?"asked for permission at":"since"} ${W(i,n)}`:"time not reported",x=[m?ge(e):null,g,u&&je(u)].filter(y=>typeof y=="string");return t.jsxs(j,{label:"Status",note:h?t.jsxs(t.Fragment,{children:[t.jsx(D,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:h}),f&&t.jsx("span",{className:"block",children:f})]}):f??void 0,children:[t.jsxs("span",{className:"inline-flex items-center gap-2",children:[t.jsx(be,{kind:c}),t.jsx("span",{"data-part":"status",children:d?"Stale":l?we:ye[e.status]}),p!==null&&t.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[t.jsx("span",{className:"sr-only",children:r?` ${F(p)} ago`:` for ${F(p)}`}),t.jsxs("span",{"aria-hidden":!0,children:[C(p),r&&" ago"]})]})]}),t.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:x.join(", ")})]})}function Ce({session:e,state:a,now:s,asOf:n}){const r=o.useMemo(()=>ke({sessions:a.snapshot?.sessions??I,events:a.events,history:a.history,now:s,asOf:n,eventsFull:a.events.length>=_}),[a.snapshot,a.events,a.history,s,n]);if(!r)return t.jsxs(j,{label:"Waits",children:["Not known",t.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:"The history could not be read"})]});const d=r.sessions.find(i=>i.id===e.id),l=r.period.today?"today":`since ${A(r.period.from)}`;let c="None";if(d){const i=`${d.startKnown?"":"at least "}${C(d.ms)}`;c=d.times===1?`Once, ${i}`:`${d.times} times, ${i} in all`}return t.jsxs(j,{label:"Waits",children:[t.jsx("span",{"data-part":"waits",className:"tabular-nums",children:c}),t.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:l})]})}function E({title:e,aside:a,children:s,...n}){return t.jsxs("section",{...n,"aria-label":e,className:"mt-6",children:[t.jsxs("div",{className:"flex items-baseline justify-between gap-4 border-b border-hairline pb-2",children:[t.jsx("h3",{className:"text-body font-semibold text-ink",children:e}),a&&t.jsx("p",{className:"text-caption text-ink-muted",children:a})]}),s]})}function Ie({session:e,gone:a,state:s,now:n,asOf:r,lastMessage:d}){const l=s.snapshot?.sources??[],c=s.snapshot?.sessions??I,i=ae(s.snapshot?.timeRules),p=re(e.surface),u=s.events.length>=_,m=o.useMemo(()=>le(s.events.filter(x=>x.sessionId===e.id),c),[s.events,c,e.id]),f=oe(s.events,s.history,u),h=o.useMemo(()=>s.history?ie({sessions:c,events:s.events,history:s.history,now:n,asOf:r,eventsFull:u,staleAfterMs:i}):null,[c,s.events,s.history,n,r,u,i]),g=h?.rows.find(x=>x.id===e.id);return t.jsxs(t.Fragment,{children:[a&&t.jsx("p",{"data-part":"gone",className:"mt-4 text-body text-ink-secondary",children:"This session has left the list. Here is what was last known of it."}),t.jsxs(ce,{className:"mt-3",children:[t.jsx(We,{session:e,gone:a,asOf:r,now:n}),t.jsx(j,{label:"Agent",children:T(e,l)}),e.machine!==void 0&&t.jsx(j,{label:"Machine",note:qe,children:t.jsx("span",{"data-part":"machine",children:e.machine})}),p!==null&&t.jsx(j,{label:"App",children:p}),t.jsx(j,{label:"Folder",mono:e.cwd!==null,children:e.cwd!==null?t.jsx(M,{"data-part":"folder",className:"select-text",children:e.cwd}):"Not known"}),e.git?.repository!==void 0&&t.jsx(j,{label:"Repository",children:t.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:e.git.repository.name})}),e.git?.branch!==void 0?t.jsx(j,{label:"Branch",children:t.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:e.git.branch})}):e.git?.commit!==void 0&&t.jsx(j,{label:"Commit",mono:!0,children:e.git.commit}),e.git?.pullRequest!==void 0&&t.jsx(Fe,{pullRequest:e.git.pullRequest}),t.jsx(j,{label:"Started",children:t.jsx("span",{className:"tabular-nums",children:e.startedAt!==null?W(e.startedAt,n):"Not reported"})}),e.pid!==void 0&&t.jsx(j,{label:"Process",mono:!0,note:e.alive===!1?"The process has ended.":!a&&e.source==="claude-code"&&e.surface==="desktop"?ue:void 0,children:e.pid}),t.jsx(Ce,{session:e,state:s,now:n,asOf:r})]}),!a&&t.jsx(E,{"data-part":"last-message",title:"Last message",children:t.jsx(Te,{reading:d,remote:e.machine!==void 0,agent:T(e,l)})}),t.jsx(E,{"data-part":"events",title:"Events",aside:f?`since ${A(f.at)}`:void 0,children:m.length>0?t.jsx(de,{entries:m,now:n,className:"pt-0.5"}):t.jsxs("p",{className:"py-2.5 text-body text-ink-secondary",children:["Nothing has happened to this session"," ",f?`since ${A(f.at)}`:"yet","."]})}),t.jsx(E,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:h&&g?t.jsx(fe,{timeline:h,rows:[g],named:!1,label:"Its last hour",className:"pt-3"}):t.jsx("p",{className:"py-2.5 text-body text-ink-secondary",children:h?"It did not run in the last hour.":"The last hour could not be read. The page asks again every two seconds."})})]})}function Pe({sessionId:e,open:a,onClose:s,state:n,now:r,onStopped:d,onAnswered:l}){const c=n.snapshot?.sessions.find(k=>k.id===e)??null,[i,p]=o.useState(null);c!==null&&c!==i?.session?p({session:c,at:n.lastOkAt??r}):c===null&&(i?.session.waitingText!==void 0||i?.session.ask!==void 0)&&p({...i,session:z(i.session)});const u=c??i?.session??null,m=c===null&&i!==null,f=U(),h=De(e,{active:a&&c!==null&&c.machine===void 0,hidden:f,beat:n.lastOkAt}),g=Y(u??{id:e,source:"status-files",surface:"unknown",links:{}}),x=m&&i?i.at:n.phase==="stalled"&&n.lastOkAt!==null?n.lastOkAt:r,y=g.outcome!==null||g.asking!==null,b=Oe(e,{open:a,onStopped:d}),w=b.step.kind==="answered"&&c?.alive===!0&&(n.snapshot?.generatedAt??0)>b.step.at,P=b.step.kind==="answered"&&b.step.outcome==="stopped"&&!w,L=G(u??{id:e,source:"status-files",status:"unknown",cwd:null},P?!0:void 0),B=b.step.kind==="answered"||b.step.kind==="confirming"&&(b.step.busy||!m);let v,N,R="wide";return u?(v=u.name,N=t.jsxs(t.Fragment,{children:[!m&&t.jsx("div",{className:$("flex flex-wrap items-center gap-x-2 gap-y-1",y&&"mt-3"),children:t.jsx(V,{session:u,jump:g})}),B&&t.jsx(X,{session:u,stop:b,className:"mt-3"}),!m&&t.jsx(Q,{session:u,onAnswered:l,className:"mt-3"}),t.jsx(Z,{resume:L,className:"mt-3"}),t.jsx(Ie,{session:u,gone:m,state:n,now:r,asOf:x,lastMessage:h})]})):n.snapshot===null?(v="Session",N=t.jsx(O,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(v="No such session",R="sm",N=t.jsxs("div",{"data-part":"missing",children:[t.jsx("p",{className:"mt-2 text-body text-ink-secondary",children:"Agent Lookout is not watching a session at this address. It may have ended before this page was opened."}),t.jsx(ve,{className:"mt-4",onClick:s,children:"Go to the Overview"})]})),t.jsx(ee,{open:a,onOpenChange:k=>{k||s()},title:v,size:R,focusTitle:!0,returnFocus:k=>_e(e,k)??document.querySelector("main"),actions:u&&t.jsxs(t.Fragment,{children:[!m&&t.jsxs(t.Fragment,{children:[t.jsx(te,{session:u,jump:g,variant:q(u)?"needs-you":"quiet"}),t.jsx(se,{session:u,stop:b})]}),t.jsx(ne,{session:u,resume:L,tooltip:!1})]}),children:N})}function ze({sessionId:e,onClose:a,state:s,now:n,onStopped:r,onAnswered:d}){const[l,c]=o.useState(e);return e!==null&&e!==l&&c(e),l===null?null:t.jsx(Pe,{sessionId:l,open:e!==null,onClose:a,state:s,now:n,onStopped:r,onAnswered:d},l)}export{ze as SessionPanel};
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-content:"";--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--spacing:.25rem;--container-md:28rem;--container-3xl:48rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-sans:"Atkinson Hyperlegible Next", system-ui, sans-serif;--font-mono:"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--text-wait:46px;--text-wait--line-height:.95;--text-wait--letter-spacing:-.03em;--text-name:34px;--text-name--line-height:1.05;--text-name--letter-spacing:-.025em;--text-stat:28px;--text-stat--line-height:1;--text-stat--letter-spacing:-.02em;--text-unit:22px;--text-unit--line-height:1;--text-unit--letter-spacing:0;--text-wordmark:19px;--text-wordmark--line-height:1;--text-wordmark--letter-spacing:-.015em;--text-lead:16px;--text-lead--line-height:1.2;--text-title:15px;--text-title--line-height:1.3;--text-title--letter-spacing:-.006em;--text-row:14px;--text-row--line-height:1.45;--text-body:13px;--text-body--line-height:1.45;--text-fact:12.5px;--text-fact--line-height:1.45;--text-caption:12px;--text-caption--line-height:1.35;--text-micro:11px;--text-micro--line-height:1.2;--leading-tight:1.2;--radius-panel:24px;--radius-chrome:22px;--radius-inner:14px;--radius-row:10px;--radius-capsule:999px;--radius-tube:6px;--radius-bar:4px;--spacing-window:12px;--spacing-rail:76px;--spacing-header:56px;--spacing-row:44px;--spacing-group:40px;--spacing-log-row:38px;--spacing-control:30px;--spacing-button:28px;--spacing-button-hero:38px;--spacing-mark:14px;--spacing-icon:20px;--spacing-track:30px;--spacing-label-col:188px;--spacing-bar-needs-you:16px;--spacing-bar-working:10px;--spacing-bar-idle:2px;--spacing-waited-name:132px;--ease-out:cubic-bezier(.2, .7, .2, 1);--animate-lamp:lamp 3.2s ease-in-out infinite;--animate-spin-loader:spin-loader .9s linear infinite;--animate-rise-fast:rise .24s ease-out both;--animate-fade-in:fade-in .2s ease-out both;--animate-fade-out:fade-out .15s ease-in both;--animate-drop:drop .15s ease-in both}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--hairline);outline-color:var(--focus)}html{background-color:var(--ground-top)}body{min-height:100vh;color:var(--ink);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;background-color:#0000;font-size:14px;line-height:1.45}code,kbd,samp,pre{letter-spacing:-.02em;word-spacing:-.22em;font-variant-numeric:tabular-nums}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}::selection{background-color:var(--focus)}@supports (color:color-mix(in lab,red,red)){::selection{background-color:color-mix(in srgb,var(--focus) 32%,transparent)}}}@layer components{.ground{z-index:-1;pointer-events:none;background-image:var(--ground);position:fixed;inset:0;overflow:hidden}.ground>i{will-change:transform;border-radius:50%;display:block;position:absolute}.ground-main{top:var(--field-main-top);right:var(--field-main-right);width:var(--field-main-width);height:var(--field-main-height);transform:rotate(var(--field-main-turn));background-image:radial-gradient(closest-side,rgb(var(--field) / var(--field-strength)),rgb(var(--field) / calc(var(--field-strength) * .4)) 52%,transparent 100%)}.ground-deep{background-image:radial-gradient(closest-side,rgb(var(--field-deep) / calc(var(--field-strength) * .85)),rgb(var(--field-deep) / calc(var(--field-strength) * .3)) 55%,transparent 100%);width:1180px;height:880px;top:520px;left:-300px}.ground-far{background-image:radial-gradient(closest-side,rgb(var(--field) / calc(var(--field-strength) * .45)),transparent 100%);width:900px;height:600px;top:820px;left:46%}.ground:after{content:"";opacity:var(--grain);mix-blend-mode:overlay;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .5 0 0 0 0 .5 0 0 0 0 .5 0 0 0 1 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");position:absolute;inset:0}@media(prefers-reduced-motion:no-preference){.ground-main{animation:96s ease-in-out infinite alternate drift-main}.ground-deep{animation:124s ease-in-out infinite alternate drift-deep}.ground-far{animation:150s ease-in-out infinite alternate drift-far}.ground[data-drift=paused]>i{animation-play-state:paused}}@keyframes drift-main{to{transform:rotate(var(--field-main-turn)) translate3d(-90px,70px,0) scale(1.06)}}@keyframes drift-deep{to{transform:translate(120px,-60px)scale(1.04)}}@keyframes drift-far{to{transform:translate(-140px,-40px)}}.glass-chrome,.glass-card,.glass-raised,.glass-float{isolation:isolate;position:relative}.glass-chrome{--rim-paint:var(--rim);border-radius:var(--radius-chrome);background-color:var(--glass-chrome);box-shadow:var(--elev-1)}.glass-card{--rim-paint:var(--rim);border-radius:var(--radius-panel);background-color:var(--glass-card);box-shadow:var(--elev-2)}.glass-raised{--rim-paint:var(--rim-raised);border-radius:var(--radius-panel);background-color:var(--glass-raised);box-shadow:var(--elev-3)}.glass-raised[data-light=lamp]{--rim-paint:var(--rim-lamp), var(--rim-raised)}.glass-raised[data-light=rest]{--rim-paint:var(--rim-rest), var(--rim-raised)}.glass-float{--rim-paint:var(--rim-raised);border-radius:var(--radius-panel);background-color:var(--glass-float);box-shadow:var(--elev-3)}:is(.glass-chrome,.glass-card,.glass-raised,.glass-float):before{content:"";z-index:3;border-radius:inherit;pointer-events:none;background:var(--rim-paint);padding:1px;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);-webkit-mask-position:0 0,0 0;mask-position:0 0,0 0;-webkit-mask-size:auto,auto;mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}:is(.glass-chrome,.glass-card,.glass-raised,.glass-float):after{content:"";z-index:-1;border-radius:inherit;pointer-events:none;background-image:var(--sheen);position:absolute;inset:0}@supports not ((mask-composite:exclude) or (-webkit-mask-composite:xor)){:is(.glass-chrome,.glass-card,.glass-raised,.glass-float):before{box-shadow:inset 0 1px 0 var(--control-top),inset 0 0 0 1px var(--rule);background:0 0;padding:0;-webkit-mask:none;mask:none}}.glass-blur{-webkit-backdrop-filter:blur(var(--glass-blur)) saturate(var(--saturate));backdrop-filter:blur(var(--glass-blur)) saturate(var(--saturate))}.lamp-light,.rest-light{--light-reach:60px;z-index:0;top:-90px;left:calc(var(--light-reach) * -1);width:min(820px,calc(100% + var(--light-reach)));pointer-events:none;height:520px;filter:saturate(var(--saturate-hero));-webkit-mask-image:linear-gradient(to right,transparent calc(var(--light-reach) - var(--spacing-window)),#000 var(--light-reach));mask-image:linear-gradient(to right,transparent calc(var(--light-reach) - var(--spacing-window)),#000 var(--light-reach));position:absolute}.lamp-light:before,.rest-light:before{content:"";border-radius:50%;position:absolute}.lamp-light{background-image:radial-gradient(closest-side,rgb(var(--lamp) / calc(var(--lamp-strength) * .3)),rgb(var(--lamp) / calc(var(--lamp-strength) * .09)) 50%,transparent 100%)}.lamp-light:before{background-image:radial-gradient(closest-side,rgb(var(--lamp) / var(--lamp-strength)),rgb(var(--lamp) / calc(var(--lamp-strength) * .35)) 55%,transparent 100%);width:63%;height:330px;top:50px;left:3.7%}.rest-light{background-image:radial-gradient(closest-side,rgb(var(--rest) / calc(var(--rest-strength) * .5)),rgb(var(--rest) / calc(var(--rest-strength) * .2)) 50%,transparent 100%)}.rest-light:before{background-image:radial-gradient(closest-side,rgb(var(--rest) / var(--rest-strength)),rgb(var(--rest) / calc(var(--rest-strength) * .35)) 55%,transparent 100%);width:78%;height:360px;top:40px;left:14.6%}@media(prefers-reduced-motion:no-preference){.lamp-light{animation:lamp-rise 1.2s var(--ease-out) both}}@keyframes lamp-rise{0%{opacity:0;transform:translateY(90px)scale(.9)}}.bar-open{background-color:var(--status-needs-you);box-shadow:inset 0 0 0 1px var(--status-needs-you-edge),var(--bar-glow,0 0 transparent)}.bar-glow{--bar-glow:0 0 16px -3px var(--glow-needs-you)}.bar-answered{background-color:var(--answered-fill);box-shadow:inset 0 0 0 1.5px var(--status-idle)}.bar-working{background-color:var(--status-working-fill);box-shadow:inset 0 1px 0 var(--status-working-top)}.bar-idle{background-color:var(--status-idle)}.stale-dots{background-image:repeating-linear-gradient(to right,var(--status-idle) 0 2px,transparent 2px 6px)}.bar-tube{background-color:var(--tube);box-shadow:inset 0 0 0 1px var(--tube-rim),inset 0 1px 0 var(--tube-top)}.unmeasured-hatch{background-image:repeating-linear-gradient(135deg,var(--unmeasured) 0 1px,transparent 1px 6px)}.thin-scroll::-webkit-scrollbar{width:4px;height:4px}.thin-scroll::-webkit-scrollbar-thumb{background-color:var(--rule-strong);border-radius:999px}.thin-scroll::-webkit-scrollbar-track{background:0 0}@supports not selector(::-webkit-scrollbar){.thin-scroll{scrollbar-width:thin;scrollbar-color:var(--rule-strong) transparent}}[data-host=app] :is([data-slot=header],[data-slot=rail-buttons],[data-slot=ground-above]){-webkit-app-region:drag}[data-host=app] [data-slot=ground-above]>i{-webkit-app-region:none}[data-host=app] :is([data-slot=header],[data-slot=rail]) :is(a,button,input,select,textarea,summary,[role=button],[tabindex]),[data-host=app] body>:not(#root){-webkit-app-region:no-drag}[data-host=app]{scrollbar-width:thin}}@layer utilities{.\@container{container-type:inline-size}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.inset-y-0\.75{inset-block:calc(var(--spacing) * .75)}.-top-1\.75{top:calc(var(--spacing) * -1.75)}.top-0{top:0}.top-1\.75{top:calc(var(--spacing) * 1.75)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-window{top:var(--spacing-window)}.-right-7\.5{right:calc(var(--spacing) * -7.5)}.right-0{right:0}.right-full{right:100%}.bottom-0{bottom:0}.bottom-1\/2{bottom:50%}.bottom-auto{bottom:auto}.left-0{left:0}.left-0\.75{left:calc(var(--spacing) * .75)}.left-1\/2{left:50%}.left-3\.5{left:calc(var(--spacing) * 3.5)}.isolate{isolation:isolate}.z-0{z-index:0}.z-1{z-index:1}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-60{z-index:60}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-4{grid-column:span 4/span 4}.col-span-8{grid-column:span 8/span 8}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.row-start-3{grid-row-start:3}.row-start-4{grid-row-start:4}.container{width:100%}@media(min-width:761px){.container{max-width:761px}}@media(min-width:1181px){.container{max-width:1181px}}.-m-2{margin:calc(var(--spacing) * -2)}.-mx-1\.5{margin-inline:calc(var(--spacing) * -1.5)}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2\.5{margin-inline:calc(var(--spacing) * 2.5)}.mx-auto{margin-inline:auto}.-my-0\.5{margin-block:calc(var(--spacing) * -.5)}.-my-2\.5{margin-block:calc(var(--spacing) * -2.5)}.my-auto{margin-block:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-0\.75{margin-top:calc(var(--spacing) * .75)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-3\.5{margin-top:calc(var(--spacing) * 3.5)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-\[6vh\]{margin-top:6vh}.mt-px{margin-top:1px}.mr-1{margin-right:var(--spacing)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mr-11\.5{margin-right:calc(var(--spacing) * 11.5)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.-ml-0\.5{margin-left:calc(var(--spacing) * -.5)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-auto{margin-left:auto}.ml-label-col{margin-left:var(--spacing-label-col)}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\!hidden{display:none!important}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.table{display:table}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-4\.5{width:calc(var(--spacing) * 4.5);height:calc(var(--spacing) * 4.5)}.size-6\.5{width:calc(var(--spacing) * 6.5);height:calc(var(--spacing) * 6.5)}.size-7\.5{width:calc(var(--spacing) * 7.5);height:calc(var(--spacing) * 7.5)}.size-button{width:var(--spacing-button);height:var(--spacing-button)}.size-icon{width:var(--spacing-icon);height:var(--spacing-icon)}.size-mark{width:var(--spacing-mark);height:var(--spacing-mark)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-5{height:calc(var(--spacing) * 5)}.h-5\.5{height:calc(var(--spacing) * 5.5)}.h-7{height:calc(var(--spacing) * 7)}.h-11{height:calc(var(--spacing) * 11)}.h-55{height:calc(var(--spacing) * 55)}.h-bar-idle{height:var(--spacing-bar-idle)}.h-bar-needs-you{height:var(--spacing-bar-needs-you)}.h-bar-working{height:var(--spacing-bar-working)}.h-button{height:var(--spacing-button)}.h-button-hero{height:var(--spacing-button-hero)}.h-control{height:var(--spacing-control)}.h-full{height:100%}.h-group{height:var(--spacing-group)}.h-header{height:var(--spacing-header)}.h-px{height:1px}.h-row{height:var(--spacing-row)}.h-track{height:var(--spacing-track)}.h-window{height:var(--spacing-window)}.max-h-57{max-height:calc(var(--spacing) * 57)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-\[min\(26rem\,55vh\)\]{max-height:min(26rem,55vh)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-28{min-height:calc(var(--spacing) * 28)}.min-h-65{min-height:calc(var(--spacing) * 65)}.min-h-button{min-height:var(--spacing-button)}.min-h-log-row{min-height:var(--spacing-log-row)}.min-h-screen{min-height:100vh}.w-0{width:0}.w-0\.5{width:calc(var(--spacing) * .5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-5\.5{width:calc(var(--spacing) * 5.5)}.w-14{width:calc(var(--spacing) * 14)}.w-15\.5{width:calc(var(--spacing) * 15.5)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-25{width:calc(var(--spacing) * 25)}.w-28{width:calc(var(--spacing) * 28)}.w-30{width:calc(var(--spacing) * 30)}.w-40{width:calc(var(--spacing) * 40)}.w-96{width:calc(var(--spacing) * 96)}.w-\[min\(440px\,100\%\)\]{width:min(440px,100%)}.w-\[min\(600px\,100\%\)\]{width:min(600px,100%)}.w-\[min\(760px\,100\%\)\]{width:min(760px,100%)}.w-fit{width:fit-content}.w-full{width:100%}.w-label-col{width:var(--spacing-label-col)}.w-mark{width:var(--spacing-mark)}.w-px{width:1px}.w-rail{width:var(--spacing-rail)}.w-waited-name{width:var(--spacing-waited-name)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-60{max-width:calc(var(--spacing) * 60)}.max-w-155{max-width:calc(var(--spacing) * 155)}.max-w-\[52ch\]{max-width:52ch}.max-w-\[56ch\]{max-width:56ch}.max-w-\[620px\]{max-width:620px}.max-w-\[min\(26rem\,calc\(100vw-1\.5rem\)\)\]{max-width:min(26rem,100vw - 1.5rem)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.min-w-0\.5{min-width:calc(var(--spacing) * .5)}.min-w-0\.75{min-width:calc(var(--spacing) * .75)}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-5\.5{min-width:calc(var(--spacing) * 5.5)}.min-w-16{min-width:calc(var(--spacing) * 16)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.basis-32{flex-basis:calc(var(--spacing) * 32)}.basis-40{flex-basis:calc(var(--spacing) * 40)}.basis-full{flex-basis:100%}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin-loader{animation:var(--animate-spin-loader)}.cursor-crosshair{cursor:crosshair}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-\[auto_minmax\(0\,1fr\)\]{grid-template-columns:auto minmax(0,1fr)}.grid-cols-\[minmax\(0\,1\.75fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.75fr) minmax(0,1fr)}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-1\.25{gap:calc(var(--spacing) * 1.25)}.gap-1\.75{gap:calc(var(--spacing) * 1.75)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-px{gap:1px}.gap-window{gap:var(--spacing-window)}.gap-x-1{column-gap:var(--spacing)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-2\.5{column-gap:calc(var(--spacing) * 2.5)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-3\.5{column-gap:calc(var(--spacing) * 3.5)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-12{column-gap:calc(var(--spacing) * 12)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.gap-y-7{row-gap:calc(var(--spacing) * 7)}.gap-y-px{row-gap:1px}.self-center{align-self:center}.self-start{align-self:flex-start}.justify-self-start{justify-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-clip{overflow-x:clip}.overflow-y-auto{overflow-y:auto}.rounded-bar{border-radius:var(--radius-bar)}.rounded-capsule{border-radius:var(--radius-capsule)}.rounded-chrome{border-radius:var(--radius-chrome)}.rounded-inner{border-radius:var(--radius-inner)}.rounded-panel{border-radius:var(--radius-panel)}.rounded-row{border-radius:var(--radius-row)}.rounded-tube{border-radius:var(--radius-tube)}.rounded-t-chrome{border-top-left-radius:var(--radius-chrome);border-top-right-radius:var(--radius-chrome)}.rounded-t-tube{border-top-left-radius:var(--radius-tube);border-top-right-radius:var(--radius-tube)}.rounded-b-bar{border-bottom-right-radius:var(--radius-bar);border-bottom-left-radius:var(--radius-bar)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-hairline{border-color:var(--hairline)}.border-ink-muted{border-color:var(--ink-muted)}.border-rule{border-color:var(--rule)}.border-rule-strong{border-color:var(--rule-strong)}.border-t-ink-secondary{border-top-color:var(--ink-secondary)}.bg-fill-quiet{background-color:var(--fill-quiet)}.bg-fill-selected{background-color:var(--fill-selected)}.bg-fill-zebra{background-color:var(--fill-zebra)}.bg-glass-float{background-color:var(--glass-float)}.bg-hairline{background-color:var(--hairline)}.bg-rule{background-color:var(--rule)}.bg-rule-strong{background-color:var(--rule-strong)}.bg-scrim{background-color:var(--scrim)}.bg-status-finished{background-color:var(--status-finished)}.bg-status-needs-you{background-color:var(--status-needs-you)}.bg-thumb{background-color:var(--thumb)}.bg-transparent{background-color:#0000}.bg-well{background-color:var(--well)}.bg-fixed{background-attachment:fixed}.fill-ink-muted{fill:var(--ink-muted)}.fill-status-needs-you{fill:var(--status-needs-you)}.stroke-status-needs-you-edge{stroke:var(--status-needs-you-edge)}.p-0\.75{padding:calc(var(--spacing) * .75)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-10{padding:calc(var(--spacing) * 10)}.p-window{padding:var(--spacing-window)}.px-0{padding-inline:0}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-6\.5{padding-inline:calc(var(--spacing) * 6.5)}.px-7{padding-inline:calc(var(--spacing) * 7)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-14{padding-block:calc(var(--spacing) * 14)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-2\.5{padding-top:calc(var(--spacing) * 2.5)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-4\.5{padding-top:calc(var(--spacing) * 4.5)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-\[6vh\]{padding-top:6vh}.pt-\[12vh\]{padding-top:12vh}.pr-0\.5{padding-right:calc(var(--spacing) * .5)}.pr-1{padding-right:var(--spacing)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-2\.5{padding-right:calc(var(--spacing) * 2.5)}.pr-3\.5{padding-right:calc(var(--spacing) * 3.5)}.pb-1{padding-bottom:var(--spacing)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pb-2\.25{padding-bottom:calc(var(--spacing) * 2.25)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-3\.5{padding-bottom:calc(var(--spacing) * 3.5)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-4\.5{padding-bottom:calc(var(--spacing) * 4.5)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-5\.5{padding-bottom:calc(var(--spacing) * 5.5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-7{padding-bottom:calc(var(--spacing) * 7)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-10{padding-left:calc(var(--spacing) * 10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-name{font-size:var(--text-name);line-height:var(--tw-leading,var(--text-name--line-height));letter-spacing:var(--tw-tracking,var(--text-name--letter-spacing))}.text-stat{font-size:var(--text-stat);line-height:var(--tw-leading,var(--text-stat--line-height));letter-spacing:var(--tw-tracking,var(--text-stat--letter-spacing))}.text-title{font-size:var(--text-title);line-height:var(--tw-leading,var(--text-title--line-height));letter-spacing:var(--tw-tracking,var(--text-title--letter-spacing))}.text-unit{font-size:var(--text-unit);line-height:var(--tw-leading,var(--text-unit--line-height));letter-spacing:var(--tw-tracking,var(--text-unit--letter-spacing))}.text-wait{font-size:var(--text-wait);line-height:var(--tw-leading,var(--text-wait--line-height));letter-spacing:var(--tw-tracking,var(--text-wait--letter-spacing))}.text-wordmark{font-size:var(--text-wordmark);line-height:var(--tw-leading,var(--text-wordmark--line-height));letter-spacing:var(--tw-tracking,var(--text-wordmark--letter-spacing))}.text-body{font-size:var(--text-body);line-height:var(--tw-leading,var(--text-body--line-height))}.text-caption{font-size:var(--text-caption);line-height:var(--tw-leading,var(--text-caption--line-height))}.text-fact{font-size:var(--text-fact);line-height:var(--tw-leading,var(--text-fact--line-height))}.text-lead{font-size:var(--text-lead);line-height:var(--tw-leading,var(--text-lead--line-height))}.text-micro{font-size:var(--text-micro);line-height:var(--tw-leading,var(--text-micro--line-height))}.text-row{font-size:var(--text-row);line-height:var(--tw-leading,var(--text-row--line-height))}.leading-3\.5{--tw-leading:calc(var(--spacing) * 3.5);line-height:calc(var(--spacing) * 3.5)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-mono{letter-spacing:-.02em;word-spacing:-.22em;font-variant-numeric:tabular-nums}.\[letter-spacing\:0\]\!{letter-spacing:0!important}.text-pretty{text-wrap:pretty}.wrap-anywhere{overflow-wrap:anywhere}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-ink{color:var(--ink)}.text-ink-muted{color:var(--ink-muted)}.text-ink-secondary{color:var(--ink-secondary)}.text-label-needs-you{color:var(--label-needs-you)}.text-on-needs-you{color:var(--on-needs-you)}.text-status-finished{color:var(--status-finished)}.text-status-idle{color:var(--status-idle)}.text-status-needs-you-edge{color:var(--status-needs-you-edge)}.text-status-working{color:var(--status-working)}.lowercase{text-transform:lowercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.decoration-rule-strong{-webkit-text-decoration-color:var(--rule-strong);text-decoration-color:var(--rule-strong)}.underline-offset-2{text-underline-offset:2px}.opacity-70{opacity:.7}.shadow-edge{--tw-shadow:inset 3px 0 0 var(--tw-shadow-color,var(--ink));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-glow{--tw-shadow:0 8px 22px -8px var(--tw-shadow-color,var(--glow-needs-you));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-thumb{--tw-shadow:0 2px 6px -2px var(--tw-shadow-color,var(--thumb-drop));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-shadow-lit{--tw-inset-shadow:inset 0 1px 0 var(--tw-inset-shadow-color,var(--lit-top));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-shadow-top{--tw-inset-shadow:inset 0 1px 0 var(--tw-inset-shadow-color,var(--control-top));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-shadow-well{--tw-inset-shadow:inset 0 1px 2px var(--tw-inset-shadow-color,var(--well-shade));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-ring{--tw-inset-ring-shadow:inset 0 0 0 1px var(--tw-inset-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-ring-control-rim{--tw-inset-ring-color:var(--control-rim)}.inset-ring-hairline{--tw-inset-ring-color:var(--hairline)}.inset-ring-rule{--tw-inset-ring-color:var(--rule)}.inset-ring-rule-strong{--tw-inset-ring-color:var(--rule-strong)}.inset-ring-status-idle{--tw-inset-ring-color:var(--status-idle)}.inset-ring-status-needs-you-edge{--tw-inset-ring-color:var(--status-needs-you-edge)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.sepia{--tw-sepia:sepia(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[background-color\,color\,filter\]{transition-property:background-color,color,filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-120{--tw-duration:.12s;transition-duration:.12s}.duration-220{--tw-duration:.22s;transition-duration:.22s}.fade-in-0{--tw-enter-opacity:0}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-text{-webkit-user-select:text;user-select:text}.\[word-spacing\:normal\]\!{word-spacing:normal!important}.fade-in{--tw-enter-opacity:0}.fade-out{--tw-exit-opacity:0}.paused{animation-play-state:paused}.running{animation-play-state:running}@media(hover:hover){.group-hover\:bg-fill-selected:is(:where(.group):hover *){background-color:var(--fill-selected)}.group-hover\:text-ink:is(:where(.group):hover *){color:var(--ink)}}.placeholder\:text-ink-muted::placeholder{color:var(--ink-muted)}.before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:-inset-x-1:before{content:var(--tw-content);inset-inline:calc(var(--spacing) * -1)}.before\:inset-x-0:before{content:var(--tw-content);inset-inline:0}.before\:inset-x-2\.5:before{content:var(--tw-content);inset-inline:calc(var(--spacing) * 2.5)}.before\:inset-y-0:before{content:var(--tw-content);inset-block:0}.before\:inset-y-px:before{content:var(--tw-content);inset-block:1px}.before\:-top-2:before{content:var(--tw-content);top:calc(var(--spacing) * -2)}.before\:top-1\/2:before{content:var(--tw-content);top:50%}.before\:right-0:before{content:var(--tw-content);right:0}.before\:left-1\/2:before{content:var(--tw-content);left:50%}.before\:-z-10:before{content:var(--tw-content);z-index:-10}.before\:h-1:before{content:var(--tw-content);height:var(--spacing)}.before\:h-px:before{content:var(--tw-content);height:1px}.before\:w-px:before{content:var(--tw-content);width:1px}.before\:rounded-inner:before{content:var(--tw-content);border-radius:var(--radius-inner)}.before\:bg-hairline:before{content:var(--tw-content);background-color:var(--hairline)}.before\:bg-rule-strong:before{content:var(--tw-content);background-color:var(--rule-strong)}.before\:transition-colors:before{content:var(--tw-content);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.before\:duration-120:before{content:var(--tw-content);--tw-duration:.12s;transition-duration:.12s}@media(hover:hover){.group-hover\:before\:bg-fill-hover:is(:where(.group):hover *):before{content:var(--tw-content);background-color:var(--fill-hover)}}.group-has-\[a\[data-part\=name\]\:focus-visible\]\:before\:outline-2:is(:where(.group):has(:is(a[data-part=name]:focus-visible)) *):before{content:var(--tw-content);outline-style:var(--tw-outline-style);outline-width:2px}.group-has-\[a\[data-part\=name\]\:focus-visible\]\:before\:outline-offset-2:is(:where(.group):has(:is(a[data-part=name]:focus-visible)) *):before{content:var(--tw-content);outline-offset:2px}.group-has-\[a\[data-part\=name\]\:focus-visible\]\:before\:outline-focus:is(:where(.group):has(:is(a[data-part=name]:focus-visible)) *):before{content:var(--tw-content);outline-color:var(--focus)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-1:after{content:var(--tw-content);inset:calc(var(--spacing) * -1)}.after\:inset-0:after{content:var(--tw-content);inset:0}.after\:rounded-inner:after{content:var(--tw-content);border-radius:var(--radius-inner)}.first\:pl-6:first-child{padding-left:calc(var(--spacing) * 6)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pr-4\.5:last-child{padding-right:calc(var(--spacing) * 4.5)}.odd\:bg-fill-zebra:nth-child(odd){background-color:var(--fill-zebra)}.empty\:hidden:empty{display:none}@media(hover:hover){.hover\:bg-fill-hover:hover{background-color:var(--fill-hover)}.hover\:bg-fill-selected:hover{background-color:var(--fill-selected)}.hover\:text-ink:hover{color:var(--ink)}.hover\:text-ink-secondary:hover{color:var(--ink-secondary)}.hover\:decoration-ink:hover{-webkit-text-decoration-color:var(--ink);text-decoration-color:var(--ink)}.hover\:brightness-105:hover{--tw-brightness:brightness(105%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus-visible\:z-20:focus-visible{z-index:20}.focus-visible\:bg-fill-selected:focus-visible{background-color:var(--fill-selected)}.focus-visible\:text-ink:focus-visible{color:var(--ink)}.focus-visible\:-outline-offset-2:focus-visible{outline-offset:-2px}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:brightness-97:active{--tw-brightness:brightness(97%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:opacity-50:disabled{opacity:.5}.has-\[a\[data-part\=name\]\:focus-visible\]\:outline-2:has(:is(a[data-part=name]:focus-visible)){outline-style:var(--tw-outline-style);outline-width:2px}.has-\[a\[data-part\=name\]\:focus-visible\]\:outline-offset-2:has(:is(a[data-part=name]:focus-visible)){outline-offset:2px}.has-\[a\[data-part\=name\]\:focus-visible\]\:outline-focus:has(:is(a[data-part=name]:focus-visible)){outline-color:var(--focus)}.has-\[button\:focus-visible\]\:outline-2:has(:is(button:focus-visible)){outline-style:var(--tw-outline-style);outline-width:2px}.has-\[button\:focus-visible\]\:outline-offset-2:has(:is(button:focus-visible)){outline-offset:2px}.has-\[button\:focus-visible\]\:outline-focus:has(:is(button:focus-visible)){outline-color:var(--focus)}.aria-disabled\:cursor-default[aria-disabled=true]{cursor:default}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-invalid\:inset-ring-rule-strong[aria-invalid=true]{--tw-inset-ring-color:var(--rule-strong)}.data-\[state\=checked\]\:bg-fill-selected[data-state=checked]{background-color:var(--fill-selected)}.data-\[state\=checked\]\:text-ink[data-state=checked]{color:var(--ink)}.data-\[state\=checked\]\:inset-shadow-top[data-state=checked]{--tw-inset-shadow:inset 0 1px 0 var(--tw-inset-shadow-color,var(--control-top));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:inset-ring-control-rim[data-state=checked]{--tw-inset-ring-color:var(--control-rim)}.data-\[state\=closed\]\:animate-drop[data-state=closed]{animation:var(--animate-drop)}.data-\[state\=closed\]\:animate-fade-out[data-state=closed]{animation:var(--animate-fade-out)}.data-\[state\=open\]\:animate-fade-in[data-state=open]{animation:var(--animate-fade-in)}.data-\[state\=open\]\:animate-rise-fast[data-state=open]{animation:var(--animate-rise-fast)}@media(hover:hover){.hover\:data-\[state\=unchecked\]\:bg-fill-quiet:hover[data-state=unchecked]{background-color:var(--fill-quiet)}}@media(prefers-reduced-motion:no-preference){.motion-safe\:animate-lamp{animation:var(--animate-lamp)}.motion-safe\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.motion-safe\:duration-220{--tw-duration:.22s;transition-duration:.22s}.motion-safe\:ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}@media not all and (min-width:1181px){.max-wide\:col-span-1{grid-column:span 1/span 1}.max-wide\:col-start-1{grid-column-start:1}.max-wide\:row-start-2{grid-row-start:2}.max-wide\:row-start-3{grid-row-start:3}.max-wide\:row-start-4{grid-row-start:4}.max-wide\:row-start-5{grid-row-start:5}.max-wide\:row-start-6{grid-row-start:6}.max-wide\:flex{display:flex}.max-wide\:hidden{display:none}.max-wide\:h-50{height:calc(var(--spacing) * 50)}.max-wide\:max-h-120{max-height:calc(var(--spacing) * 120)}.max-wide\:min-h-0{min-height:0}.max-wide\:w-full{width:100%}.max-wide\:flex-none{flex:none}.max-wide\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-wide\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.max-wide\:flex-col{flex-direction:column}.max-wide\:flex-wrap{flex-wrap:wrap}.max-wide\:gap-y-3\.5{row-gap:calc(var(--spacing) * 3.5)}}@media not all and (min-width:761px){.max-mid\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.max-mid\:order-last{order:9999}.max-mid\:col-start-1{grid-column-start:1}.max-mid\:col-start-2{grid-column-start:2}.max-mid\:row-start-1{grid-row-start:1}.max-mid\:row-start-2{grid-row-start:2}.max-mid\:my-0{margin-block:0}.max-mid\:mr-auto{margin-right:auto}.max-mid\:grid{display:grid}.max-mid\:hidden{display:none}.max-mid\:w-11{width:calc(var(--spacing) * 11)}.max-mid\:w-14{width:calc(var(--spacing) * 14)}.max-mid\:w-auto{width:auto}.max-mid\:min-w-0{min-width:0}.max-mid\:flex-1{flex:1}.max-mid\:basis-full{flex-basis:100%}.max-mid\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.max-mid\:flex-col{flex-direction:column}.max-mid\:flex-wrap{flex-wrap:wrap}.max-mid\:items-start{align-items:flex-start}.max-mid\:items-stretch{align-items:stretch}.max-mid\:justify-between{justify-content:space-between}.max-mid\:gap-0\.5{gap:calc(var(--spacing) * .5)}.max-mid\:gap-1\.5{gap:calc(var(--spacing) * 1.5)}.max-mid\:gap-2{gap:calc(var(--spacing) * 2)}.max-mid\:gap-2\.5{gap:calc(var(--spacing) * 2.5)}.max-mid\:gap-4{gap:calc(var(--spacing) * 4)}.max-mid\:gap-x-4{column-gap:calc(var(--spacing) * 4)}.max-mid\:gap-y-1{row-gap:var(--spacing)}.max-mid\:gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.max-mid\:gap-y-2{row-gap:calc(var(--spacing) * 2)}.max-mid\:gap-y-3{row-gap:calc(var(--spacing) * 3)}.max-mid\:justify-self-end{justify-self:flex-end}.max-mid\:border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.max-mid\:p-5{padding:calc(var(--spacing) * 5)}.max-mid\:px-5{padding-inline:calc(var(--spacing) * 5)}.max-mid\:py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.max-mid\:pl-0{padding-left:0}.max-mid\:pl-4{padding-left:calc(var(--spacing) * 4)}.max-mid\:text-left{text-align:left}}@media(min-width:1181px){.wide\:absolute{position:absolute}.wide\:inset-0{inset:0}}@container not (min-width:42rem){.\@max-\[42rem\]\:hidden{display:none}}@container (min-width:26rem){.\@min-\[26rem\]\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@container (min-width:47rem){.\@min-\[47rem\]\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.dark\:text-status-working:where([data-theme=dark],[data-theme=dark] *){color:var(--status-working)}.in-app\:w-rail:where([data-host=app] *){width:var(--spacing-rail)}.in-app-windowed\:block:where([data-host=app]:not([data-fullscreen]) *){display:block}.in-app-windowed\:rounded-inner:where([data-host=app]:not([data-fullscreen]) *){border-radius:var(--radius-inner)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/atkinson-hyperlegible-next-latin-ext-400-normal-BalHKn7d.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/atkinson-hyperlegible-next-latin-400-normal-FfmJh7DR.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/atkinson-hyperlegible-next-latin-ext-500-normal-CUASR3PD.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/atkinson-hyperlegible-next-latin-500-normal-gHP6TDRs.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/atkinson-hyperlegible-next-latin-ext-600-normal-kZZEtAc7.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/atkinson-hyperlegible-next-latin-600-normal-BBuhw47K.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/atkinson-hyperlegible-mono-latin-ext-400-normal-CpVFOiMk.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/atkinson-hyperlegible-mono-latin-400-normal-CmMGer93.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/atkinson-hyperlegible-mono-latin-ext-500-normal-DE1cvYE7.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/atkinson-hyperlegible-mono-latin-500-normal-CfDLaOZW.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/atkinson-hyperlegible-mono-latin-ext-600-normal-DKhxRsTL.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/atkinson-hyperlegible-mono-latin-600-normal-BiYyYOKJ.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--field-main-width:1760px;--field-main-height:470px;--field-main-right:-420px;--field-main-top:-150px;--field-main-turn:-13deg;--glass-blur:30px;--lit-top:#ffffff8c}:root,[data-theme=dark]{color-scheme:dark;--ground-top:#060605;--ground:linear-gradient(180deg, #060605 0%, #080706 46%, #0c0b0a 100%);--field:218 214 207;--field-deep:142 137 130;--field-strength:.16;--grain:.3;--lamp:255 166 60;--lamp-strength:.48;--rest:226 221 213;--rest-strength:.28;--glass-chrome:#0b0a09a3;--glass-card:#0d0c0ba8;--glass-raised:#191715ad;--glass-float:#151412e0;--saturate:118%;--saturate-hero:185%;--rim:linear-gradient(205deg, #fff8ee5c 0%, #fff8ee1c 20%, #fff8ee09 58%, #fff8ee17 100%);--rim-raised:linear-gradient(205deg, #fff8ee80 0%, #fff8ee26 20%, #fff8ee0d 58%, #fff8ee24 100%);--rim-lamp:linear-gradient(135deg, #ffb8608c 0%, #ffb86029 22%, #ffb86000 46%);--rim-rest:linear-gradient(135deg, #f0eae057 0%, #f0eae017 24%, #f0eae000 48%);--sheen:linear-gradient(180deg, #fff8ee0a, #fff8ee02 120px, #fff8ee00 240px);--elev-1:0 0 0 1px #0000008c, 0 1px 1px #00000059, 0 14px 34px -18px #000c;--elev-2:0 0 0 1px #00000094, 0 1px 1px #00000061, 0 26px 52px -26px #000000e0;--elev-3:0 0 0 1px #0000009e, 0 2px 2px #0000006b, 0 44px 90px -34px #000000f2;--fill-quiet:#fff8ee0e;--fill-hover:#fff8ee0b;--fill-selected:#fff8ee1a;--fill-zebra:#fff8ee06;--control-rim:#fff8ee1c;--control-top:#fff8ee1a;--well:#0000004d;--well-shade:#00000059;--thumb:#fff8ee1c;--thumb-drop:#00000059;--hairline:#fff8ee16;--rule:#fff8ee22;--rule-strong:#fff8ee3d;--ink:#f3f0ea;--ink-secondary:#c3beb5;--ink-muted:#9e988f;--status-needs-you:#ffb547;--status-needs-you-edge:#ffb547;--label-needs-you:#ffb547;--on-needs-you:#221400;--glow-needs-you:#ffaa3c80;--status-working:#a9bcd4;--status-working-fill:#86a0bd;--status-working-top:#ffffff29;--status-idle:#948e85;--answered-fill:#948e8542;--status-finished:#9e988f;--tube:#fff8ee0a;--tube-rim:#fff8ee26;--tube-top:#fff8ee33;--unmeasured:#4b463f;--focus:#c3d3e6;--scrim:#0009}[data-theme=light]{color-scheme:light;--ground-top:#d4d0ca;--ground:linear-gradient(180deg, #d4d0ca, #ebe9e5);--field:255 255 255;--field-deep:240 235 227;--field-strength:.72;--grain:.3;--lamp:255 176 80;--lamp-strength:.46;--rest:255 255 255;--rest-strength:.7;--glass-chrome:#fcfbf985;--glass-card:#fffffe8f;--glass-raised:#ffffffa8;--glass-float:#fdfcfad6;--saturate:160%;--saturate-hero:190%;--rim:linear-gradient(205deg, #fff 0%, #ffffff9e 20%, #ffffff6b 60%, #fffc 100%);--rim-raised:linear-gradient(205deg, #fff 0%, #ffffffbf 20%, #ffffff85 60%, #ffffffe6 100%);--rim-lamp:linear-gradient(135deg, #e896288c 0%, #e8962824 22%, #e8962800 46%);--rim-rest:linear-gradient(135deg, #ffffffe6 0%, #fff0 40%);--sheen:linear-gradient(180deg, #fff6, #fff0 96px);--elev-1:0 0 0 .5px #26201a1f, 0 1px 2px #26201a0f, 0 14px 30px -18px #26201a4d;--elev-2:0 0 0 .5px #26201a1f, 0 1px 2px #26201a0f, 0 24px 46px -26px #26201a5c;--elev-3:0 0 0 .5px #26201a24, 0 2px 3px #26201a12, 0 40px 80px -34px #26201a73;--fill-quiet:#ffffff9e;--fill-hover:#ffffff8c;--fill-selected:#ffffffd9;--fill-zebra:#26201a09;--control-rim:#26201a21;--control-top:#ffffffe6;--well:#26201a0e;--well-shade:#26201a0f;--thumb:#fff;--thumb-drop:#26201a4d;--hairline:#26201a14;--rule:#26201a21;--rule-strong:#26201a47;--ink:#181613;--ink-secondary:#3f3b36;--ink-muted:#5b564f;--status-needs-you:#f4a62a;--status-needs-you-edge:#a35a00;--label-needs-you:#864600;--on-needs-you:#221400;--glow-needs-you:#d6801466;--status-working:#36577e;--status-working-fill:#5b7ca4;--status-working-top:#ffffff38;--status-idle:#6f6961;--answered-fill:#6f696124;--status-finished:#5b564f;--tube:#ffffff9e;--tube-rim:#26201a29;--tube-top:#fff;--unmeasured:#b2aca3;--focus:#36577e;--scrim:#26201a47}@media not all and (min-width:761px){:root{--text-name:28px;--text-wait:38px;--spacing-label-col:140px;--spacing-waited-name:104px}}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important;animation-delay:0s!important}}@property --tw-border-spacing-x{syntax:"<length>";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"<length>";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-ease{syntax:"*";inherits:false}@keyframes lamp{0%,to{stroke-opacity:.38}50%{stroke-opacity:.8}}@keyframes spin-loader{to{transform:rotate(360deg)}}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes fade-in{0%{opacity:0}}@keyframes fade-out{to{opacity:0}}@keyframes drop{to{transform:translateY(6px)}}