ccdeck 3.38.6 → 3.39.1

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/README.md CHANGED
@@ -13,7 +13,7 @@ Step-by-step guides: [ccdeck.dev/guides](https://ccdeck.dev/guides/)
13
13
 
14
14
  **Know which agent is waiting on you, and for how long.**
15
15
 
16
- **ccdeck keeps them in one queue** — every session stopped on a human, longest wait first, and the count in the topbar is one click to the oldest. That queue is Claude Code's, because the deck reads Codex from its rollout log and a rollout carries no such signal; the canvas under it is both, with every Claude Code subagent on a node of its own.
16
+ **ccdeck keeps them in one queue** — every session stopped on a human, longest wait first. The topbar names them, and its count is one click to the oldest. That queue is Claude Code's, because the deck reads Codex from its rollout log and a rollout carries no such signal; the canvas under it is both, with every Claude Code subagent on a node of its own.
17
17
 
18
18
  [![npm](https://img.shields.io/npm/v/ccdeck?color=cb3837&logo=npm&logoColor=white)](https://www.npmjs.com/package/ccdeck)
19
19
  [![npm downloads](https://img.shields.io/npm/dm/ccdeck?color=blue)](https://www.npmjs.com/package/ccdeck)
@@ -44,7 +44,7 @@ which Claude Code session is blocked on you · tool calls · one canvas · cost
44
44
 
45
45
  Four agents running, and the machine has been quiet for twenty minutes. One of them stopped to ask something and you did not see it go by. From the outside every terminal tab looks the same — the one that is working and the one that has been holding a permission prompt since the coffee — so you find it by clicking through them, and the agent that was closest to finished is the one that has been waiting longest.
46
46
 
47
- ccdeck answers that in one place: every session stopped on a human is at the top of the sidebar with the wait beside it, longest first, and the count in the topbar goes straight to the oldest one. The question stops being *which tab* and becomes *this one*.
47
+ ccdeck answers that in one place: every session stopped on a human is at the top of the sidebar with the wait beside it, longest first, and the topbar names them beside a count that goes straight to the oldest one. The question stops being *which tab* and becomes *this one*.
48
48
 
49
49
  That is the sharp end of a wider problem. An agent session is a tree, but a terminal shows it as a scroll: five subagents working in parallel arrive as one interleaved column of text, and the questions you actually have — *what is running right now, what did that subagent do, which one is stuck, what is this costing* — are the ones the scroll answers worst.
50
50
 
@@ -93,8 +93,8 @@ The deck opens on these eight pictures the first time it runs — they are the w
93
93
 
94
94
  | | |
95
95
  |---|---|
96
- | **Blocked on you** | A permission prompt, or a finished turn waiting for your next instruction, sorts that session to the top of the sidebar with how long it has been stuck — longest wait first, so the oldest block is the first row. A permission prompt also puts a count in the topbar that jumps straight to it. Claude Code only — the deck reads Codex from its rollout log, and a rollout carries no such signal. |
97
- | **What each session is doing** | The note tied to each session's card — and the line under its row in the sidebar — says it while it works: the sentence the newest reply wrote, or the description on the command it ran. Once the turn is over it keeps the last thing the session did, until Claude Code's recap takes its place. A background session (`claude --bg`, or one started from `claude agents`) shows Claude Code's own line instead, read from its job folder in `~/.claude/jobs`: the question it is stuck on and the reply Claude Code suggests, or the headline of what it got done. No model is called for any of it. Claude Code only. |
96
+ | **Blocked on you** | A permission prompt, or a finished turn waiting for your next instruction, sorts that session to the top of the sidebar with how long it has been stuck — longest wait first, so the oldest block is the first row. A permission prompt also puts a count in the topbar that jumps straight to it, and the session's name beside it, with how long it has waited and on what — one click from its card. Claude Code only — the deck reads Codex from its rollout log, and a rollout carries no such signal. |
97
+ | **What each session is doing** | The note tied to each session's card — and the line under its row in the sidebar — says it while it works: the sentence the newest reply wrote, or the description on the command it ran. Once the turn is over it keeps the last thing the session did, until Claude Code's recap takes its place. A background session (`claude --bg`, or one started from `claude agents`) shows Claude Code's own line instead, read from its job folder in `~/.claude/jobs`: the question it is stuck on and the reply Claude Code suggests, or the headline of what it got done. No model is called for any of it. A Codex session, read from its rollout log, has the note only while a turn runs, and it says the first line of the prompt that began the turn: no line is taken from a Codex reply, and no recap follows. |
98
98
  | **Live DAG** | Nodes are agents and edges are spawns; each agent's latest tool calls sit beside its node and light up while they run. In-flight edges animate, settled ones dim. |
99
99
  | **Both providers, one canvas** | Claude Code through hooks, Codex through its rollout log. The model chip (`Opus 5`, `GPT-5.5`) tells them apart. Subagent cards are Claude Code only: a Codex session is one node with its tool calls. |
100
100
  | **Cost and quota, live** | Spend per model and per session, plus Claude and Codex quota windows as they refill. |
@@ -120,7 +120,9 @@ log in. **`ccdeck --stop` is the off switch**; `Ctrl+C` only cancels a start tha
120
120
  is still printing. `ccdeck --status` says what is running, and `--foreground`
121
121
  holds the terminal the way every version before 3.20 did.
122
122
 
123
- Everything the page lets you set — the theme, the sounds, notifications, Claude FM and the minimap character — is in **Settings**, behind the gear at the right of the topbar, or `Cmd+,` (`Ctrl+,` on Windows and Linux). A change applies the moment you make it. The sounds and how loud each tone is are in **Settings › Sounds**, which `V` opens straight away; `M` turns sounds off or on from anywhere. `?` lists every key.
123
+ The panels open from the window's edges: the session list and the Claude accounts panel from the stripe on the left, Usage, This machine, Usage history and Browser Watch from the one on the right. Each shows or hides beside the canvas, and several can be open at once. Point at one, or Tab to it, and its hint names its key — `L`, `A`, `U`, `S`, `H`, `B` — while the single-key shortcuts are on. On a phone they are in a dock along the bottom.
124
+
125
+ Everything the page lets you set — the theme, the single-key shortcuts, the sounds, notifications, Claude FM and the minimap character — is in **Settings**, behind the gear at the right of the topbar, or `Cmd+,` (`Ctrl+,` on Windows and Linux). A change applies the moment you make it. The sounds and how loud each tone is are in **Settings › Sounds**, which `V` opens straight away; `M` turns sounds off or on from anywhere. `?` lists every key. If you use voice control or press letters by accident, turn off **Single-key shortcuts** in **Settings › General**: letters, `?` and Space then do nothing, while `Cmd+,`, `Esc`, `Enter`, `Tab`, `Delete` and the arrows keep working.
124
126
 
125
127
  No config file. No account. What your sessions contain — your prompts, the replies, the files they touch — is never reported anywhere. The deck does send usage reports, on by default: its version, your system, your IP address and a device fingerprint, rough counts such as how many sessions ran in a day, and the errors it hits. It has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start.
126
128
 
@@ -156,6 +158,63 @@ Step by step: [Install the ccdeck app on Mac, Windows or Linux](https://ccdeck.d
156
158
  - Optional: [claude-swap](https://github.com/realiti4/claude-swap) for the Accounts panel; the deck can install it for you
157
159
  - Nothing else. On Apple Silicon the deck fetches [`macmon`](https://github.com/vladkens/macmon) itself for the temperature rows; see below.
158
160
 
161
+ ### Claude Telemetry Radar (macOS)
162
+
163
+ Open **Telemetry Radar** from the right rail, or **More → Telemetry Radar** on a
164
+ phone. Three views keep configuration, observed traffic and saved files separate:
165
+
166
+ - **Monitor** lists decoded messages and unreadable connection observations.
167
+ Filter by session, destination or message type, then select a message for its
168
+ readable overview or decoded **JSON**. Session attribution comes from IDs in
169
+ the captured payload; absent IDs remain unidentified, and an export containing
170
+ multiple session IDs is shown under each matching session.
171
+ - **Configuration** shows configured destinations, telemetry/content flags and
172
+ their sources. Enabled flags are highlighted and recognized boolean values
173
+ are shown as written. These are settings-file evidence, not confirmation of a
174
+ running session's effective environment. Credentials and endpoint paths stay
175
+ hidden.
176
+ - **File** opens a local JSON or JSONL file, lists its records and shows the
177
+ original text. Files stay in the browser and are never uploaded. Search and
178
+ session filtering narrow the list. Inputs are bounded to 10 MB and 10,000
179
+ lines; the first 200 matching rows are displayed. File contents alone do not
180
+ prove that a record was sent.
181
+
182
+ Choose all configured IPv4 destinations, one destination or a custom IPv4
183
+ address and port, then press **Start monitoring**. Run each generated command
184
+ in a separate Terminal tab. Only `tcpdump` needs administrator permission;
185
+ ccdeck and the helper remain unprivileged. Each destination gets its own
186
+ network interface and decoder, up to eight destinations per capture. The UI
187
+ shows how many destinations have actually been activated. Preparing commands
188
+ alone does not start capture.
189
+
190
+ Passive decoding supports plaintext IPv4 OTLP/gRPC over HTTP/2, including gzip.
191
+ Encrypted or incomplete traffic appears as an observation without invented
192
+ JSON or session attribution. Existing connections may need to reconnect
193
+ naturally before decoding begins. Capture does not redirect traffic or change
194
+ Claude's settings. It is filtered by address, not process: other apps sending
195
+ there can also appear. Model API traffic and Enterprise server-side capture
196
+ are outside its coverage.
197
+
198
+ The inspector distinguishes observed transfer, collector acceptance, partial
199
+ success and rejection. Acceptance does not prove final backend storage. Contents
200
+ come from captured traffic, not local conversation transcripts. Messages remain
201
+ in local server memory: at most 100 exports for five minutes, with a bounded
202
+ payload budget; unreadable observations are also capped at 100 for five minutes.
203
+ No capture file is written. **Pause list** pauses display, not capture or retention;
204
+ **Clear messages** clears exports and unreadable observations.
205
+
206
+ Activation expires after ten minutes. Closing the modal does not stop capture.
207
+ Use **Stop monitoring**, then **Ctrl+C** in each Terminal tab to stop `tcpdump`.
208
+ Commands contain a temporary loopback token; do not share them. Capture contents
209
+ are available through the local loopback UI, not LAN endpoints. Local clients
210
+ of the same server share its capture.
211
+
212
+ Configuration and connection sampling runs only while Radar is open and the
213
+ browser page is visible. A connection does not prove an upload, and absence of
214
+ observed traffic does not mean telemetry is disabled. Native-process sampling
215
+ can miss short-lived connections and Node wrappers.
216
+
217
+
159
218
  ### Temperature, per machine
160
219
 
161
220
  The machine panel shows a **Thermal** section only where the machine actually answers, and it never invents a reading — no sensor means no row.
@@ -213,13 +272,13 @@ Quota is the one thing that is not just reading. It needs a live token, so when
213
272
 
214
273
  It never steers an agent or edits your code, but it is not read-only either — besides the hook entry and its own event log, it manages the two tools it leans on, and it refreshes the Codex token it reads quota with, rewriting `~/.codex/auth.json` the way `codex` itself does. It also reads your browser's history, because Browser Watch is on unless you switch it off; that is its own section below, because it is the one thing here that is about you rather than about an agent.
215
274
 
216
- What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for `macmon`, and, while the page is open or one of the two quota notifications is on, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (`status.claude.com`, `status.openai.com`), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. While the machine panel is open, on a machine with Claude Code, it times the way to Claude every thirty seconds — a DNS lookup of `api.anthropic.com` and a TCP handshake on port 443, with nothing sent over it — for the panel's latency reading. While the Browser Watch panel is open, it looks up the address of Claude's browser relay, `bridge.claudeusercontent.com`, about every thirty seconds — a DNS query to your resolver — to tell whether a running browser holds a connection to it. The [desktop app](#desktop-app) also asks this repository's GitHub releases for its own updates. With **Local network** on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see [Local network](#local-network). The deck also sends usage reports to `api.ccdeck.dev`: an install event (naming the ccdeck.dev page the command was copied from, when the command says so with `--ref`, which the site's copy buttons add; a page name and nothing else, sent once), an update event (saying how the new version arrived: the deck's own update, the desktop app's updater, npx, npm or a source checkout), one "active" a day, one "uninstall" when you run `ccdeck --uninstall` (which asks, at a terminal, one skippable question about why, from a short list), one "activated" when a new install's first session arrives (how long after the install, in rough buckets such as "under five minutes", and whether it was a Claude or a Codex session), one "rated" if you answer the one question the deck asks about itself after about a week of use — "How useful is ccdeck to you?", 0 to 10, which you can skip — with the number you picked and how many days you had used the deck, as a range (nothing is sent for a question you put off or leave), and the errors it runs into, with every path outside ccdeck's own code, project folder names, the machine name in every web address, every IP address and the machine name a network error carries (the loopback, such as `127.0.0.1:4317`, stays, so a local port error still reads as one), email addresses and keys scrubbed out (a line of ccdeck's own code keeps its place in the package, such as `ccdeck/src/server/tail.mjs:42`, or in the page, such as `<deck>/assets/index-abc.js:1:2345`, so the error still says where it happened). Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), the runtime, your IP address and a device fingerprint (a stable hashed machine id), and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions — and whether the Claude hooks went in and Codex is being watched, with the daily "active" also carrying how many sessions, subagents and projects you ran on the last day you used the deck, that day's date, and which of the deck's own features you used that day — fixed names such as "usage history" or "account switch", a yes for each and nothing about what you did with them — plus how the deck held up and what it runs with: how long this run took to start, that day's peak memory and how many events it took in, each as a rough bucket such as "under two seconds" or "256–512 MB", the Claude and Codex plan as its category only (such as "Max 5x" or "Plus", read from the file each CLI keeps beside its login, and nothing else from it), and how many Claude accounts and paired machines the deck holds, as counts. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. The deck has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now, and how many hours a day a deck stays open, can be seen. **Send feedback** (the topbar's Feedback button) sends what you write, and any screenshots you add, to the same place, where the people who make ccdeck read it and may open a public issue from it, never with the contact you gave or your screenshots. `AGENTS_DECK_NO_INSTALL=1` turns off everything but the quota reads; `AGENTS_DECK_NO_DOWNLOAD=1` is the narrower version — no `uv` binary is fetched, the managed installs stay.
275
+ What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for `macmon`, and, while the page is open or one of the two quota notifications is on, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (`status.claude.com`, `status.openai.com`), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. While the machine panel is open, on a machine with Claude Code, it times the way to Claude every thirty seconds — a DNS lookup of `api.anthropic.com` and a TCP handshake on port 443, with nothing sent over it — for the panel's latency reading. While the Browser Watch panel is open, it looks up the address of Claude's browser relay, `bridge.claudeusercontent.com`, about every thirty seconds — a DNS query to your resolver — to tell whether a running browser holds a connection to it. The [desktop app](#desktop-app) also asks this repository's GitHub releases for its own updates. With **Local network** on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see [Local network](#local-network). The deck also sends usage reports to `api.ccdeck.dev`: an install event (naming the ccdeck.dev page the command was copied from, when the command says so with `--ref`, which the site's copy buttons add; a page name and nothing else, sent once), an update event (saying how the new version arrived: the deck's own update, the desktop app's updater, npx, npm or a source checkout), one "active" a day, one "uninstall" when you run `ccdeck --uninstall` (which asks, at a terminal, one skippable question about why, from a short list), one "activated" when a new install's first session arrives (how long after the install, in rough buckets such as "under five minutes", and whether it was a Claude or a Codex session), one "rated" if you answer the one question the deck asks about itself after about a week of use — "How useful is ccdeck to you?", 0 to 10, which you can skip — with the number you picked and how many days you had used the deck, as a range (nothing is sent for a question you put off or leave), and the errors it runs into, with every path outside ccdeck's own code, project folder names, the machine name in every web address, every IP address and the machine name a network error carries (the loopback, such as `127.0.0.1:4317`, stays, so a local port error still reads as one), email addresses and keys scrubbed out (a line of ccdeck's own code keeps its place in the package, such as `ccdeck/src/server/tail.mjs:42`, or in the page, such as `<deck>/assets/index-abc.js:1:2345`, so the error still says where it happened). Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), the runtime, your IP address and a device fingerprint (a stable hashed machine id), and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions — and whether the Claude hooks went in and Codex is being watched, with the daily "active" also carrying how many sessions, subagents and projects you ran on the last day you used the deck, that day's date, and which of the deck's own features you used that day — fixed names such as "usage history" or "account switch", a yes for each and nothing about what you did with them — plus how the deck held up and what it runs with: how long this run took to start, that day's peak memory and how many events it took in, each as a rough bucket such as "under two seconds" or "256–512 MB", the Claude and Codex plan as its category only (such as "Max 5x" or "Plus", read from the file each CLI keeps beside its login, and nothing else from it), and how many Claude accounts and paired machines the deck holds, as counts. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. The deck has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now, and how many hours a day a deck stays open, can be seen. **Send feedback** (the topbar's Feedback button; on a phone, under More) sends what you write, and any screenshots you add, to the same place, where the people who make ccdeck read it and may open a public issue from it, never with the contact you gave or your screenshots. `AGENTS_DECK_NO_INSTALL=1` turns off everything but the quota reads; `AGENTS_DECK_NO_DOWNLOAD=1` is the narrower version — no `uv` binary is fetched, the managed installs stay.
217
276
 
218
277
  Local network, Claude accounts, Usage and the empty canvas each carry a small **Watch · 30 s** link to their 30-second video on [ccdeck.dev](https://ccdeck.dev/guides/). Opening one is your click, in your own browser; the deck itself sends nothing for it.
219
278
 
220
279
  ## Browser Watch
221
280
 
222
- The panel behind the eye icon answers one question: **did something drive your browser while you were away?** An agent with browser control opens pages through an API, and Chromium records that — `visits.transition` says `FROM_API` rather than `LINK` or `TYPED`. So a run of those, in a stretch when you were not touching the machine, is a finding worth showing.
281
+ The panel behind the eye on the window's right edge answers one question: **did something drive your browser while you were away?** An agent with browser control opens pages through an API, and Chromium records that — `visits.transition` says `FROM_API` rather than `LINK` or `TYPED`. So a run of those, in a stretch when you were not touching the machine, is a finding worth showing.
223
282
 
224
283
  To answer it the deck reads the browsers' own history. That means, on each poll: it finds every Chromium profile (Chrome, Brave, Edge, Vivaldi), **copies each profile's `History` database** to a temp file — a live database cannot be opened safely — queries the visits newer than the moment this deck started, and deletes the copy. None of what it reads leaves the machine, and the copy exists for as long as one query takes. The one thing the watch sends out is a DNS query: while the panel is open, it looks up the address of Claude's browser relay (`bridge.claudeusercontent.com`) about every thirty seconds, to show whether a running browser is connected to it. The background poll does not.
225
284
 
@@ -235,7 +294,7 @@ The Accounts panel reads the store [claude-swap](https://github.com/realiti4/cla
235
294
 
236
295
  The deck installs that package itself, so it installs it **bounded**: `claude-swap~=0.26`, which is `>= 0.26, == 0.*`, and when PyPI can be reached the exact version it resolved rather than the range. What `cswap --version` reports afterwards has to be that version, or the deck says so and leaves the panel dark rather than driving a copy it cannot account for — this is the tool that holds your Claude logins. The daily upgrade is bounded by the same specifier, and what it left behind is written to `~/.agents-deck/cswap-upgrade.json`.
237
296
 
238
- **Switch** on an account's row makes it the active one. New sessions start on it; sessions already running are not restarted, and pick it up on their next message — up to about 30 seconds later on macOS. **Auto-switch**, off until you turn it on, does the same without a press: about once a minute the deck runs claude-swap's own engine, which, once the active account passes your threshold (90% unless you choose another) of its 5-hour or 7-day window, moves Claude Code to the account with the most room left. Every account keeps its own limits; when no other account is under the threshold, nothing switches.
297
+ **Switch** on an account's row makes it the active one. New sessions start on it; sessions already running are not restarted, and pick it up on their next message — up to about 30 seconds later on macOS. **Auto-switch**, off until you turn it on, does the same without a press: about once a minute the deck runs claude-swap's own engine, which, once the active account passes your threshold (90% unless you choose another) of its 5-hour or 7-day window, moves Claude Code to the account with the most room left. Every account keeps its own limits; when no other account is under the threshold, nothing switches. **Custom…**, last in the threshold picker, takes any whole percent from 50 to 99 and refuses anything else under the field without saving it; the threshold is claude-swap's own setting, kept on this machine.
239
298
 
240
299
  **Account notifications** are three switches in **Settings › Notifications** (the gear in the topbar, or `Cmd+,`; `Ctrl+,` on Windows and Linux), each on its own and none of them tied to **Notifications while closed**: they are sent whether the deck is open or not, because nothing on the page says them. The desktop app shows them as ccdeck, without a sound.
241
300
 
@@ -500,7 +559,7 @@ Step by step: [Uninstall ccdeck and remove its Claude Code hook](https://ccdeck.
500
559
 
501
560
  ## Updating
502
561
 
503
- The deck checks npm for a newer release at most once an hour, plus once when it starts — a ~20-byte GET to `registry.npmjs.org`, asking about the package this copy would actually install (a deck started with `npx ccdeck` asks about `ccdeck`). When that names a version it has not seen before, one more request confirms the version is really there: npm moves the dist-tag before the version itself has propagated, and a banner shown inside that window ends in `ETARGET` instead of an upgrade. So a check is one request, or two when there is something new to confirm — and a version that is tagged but not yet installable is looked at again in five minutes rather than in an hour. Click the version chip in the topbar to ask immediately.
562
+ The deck checks npm for a newer release at most once an hour, plus once when it starts — a ~20-byte GET to `registry.npmjs.org`, asking about the package this copy would actually install (a deck started with `npx ccdeck` asks about `ccdeck`). When that names a version it has not seen before, one more request confirms the version is really there: npm moves the dist-tag before the version itself has propagated, and a banner shown inside that window ends in `ETARGET` instead of an upgrade. So a check is one request, or two when there is something new to confirm — and a version that is tagged but not yet installable is looked at again in five minutes rather than in an hour. To ask immediately, click the version chip in the topbar, or **Check for updates** in What's new (the dialog the chip opens), which runs the same check under the same limits and offers the same update as the banner below. In the desktop app, whose deck leaves updates to the app, What's new says where the app's own check stands instead.
504
563
 
505
564
  What the banner offers depends on how this copy was installed:
506
565
 
@@ -16,13 +16,22 @@ listed here are compiled into that bundle and are physically present in the
16
16
  published tarball — which is what makes these notices required rather than
17
17
  merely courteous.
18
18
 
19
- Two things are deliberately **not** listed:
19
+ These are deliberately **not** listed:
20
20
 
21
21
  - **Test and type tooling** (`vitest`, `typescript`, `@vitejs/plugin-react`) and
22
22
  **type-only packages** (`@types/*`). These are devDependencies that never reach
23
23
  the published bundle — `@types/*` erase at compile time.
24
- - **The server, launcher and hook** (`src/server`, `bin`, `hook`). They import
25
- nothing but Node built-ins, so they carry no third-party code at all.
24
+ - **The launcher and hook** (`bin`, `hook`). They import only Node built-ins.
25
+
26
+ The server additionally ships `src/server/traffic-radar-codec.mjs`, a bundled
27
+ HPACK and protobuf decoder. It needs no installed runtime dependencies. Its
28
+ complete package list and copyright/licence texts travel alongside it in
29
+ `src/server/traffic-radar-codec-LICENSES.md`; the source and reproducible build
30
+ are `traffic-radar-codec-source.mjs` and `scripts/build-radar-codec.mjs`.
31
+ `traffic-radar-otlp.json` is generated from OpenTelemetry's Apache-2.0 licensed
32
+ protobuf definitions, version v1.9.0:
33
+ https://github.com/open-telemetry/opentelemetry-proto/tree/v1.9.0.
34
+ The Apache-2.0 licence reproduced below also covers these definitions.
26
35
 
27
36
  `vite` used to be named in that first bullet and is not any more, because it is
28
37
  the exception the bullet cannot hold: it is a build tool that also **writes code
@@ -0,0 +1 @@
1
+ import{j as e,r,l as Q,m as _,n as Y,v as z,o as V,q as X,t as Z,w as ee,s as se,u as te,d as ne,x as ae,y as ie,h as re}from"./index-DGMP4S3t.js";function oe({snap:s}){return e.jsxs("section",{className:"bw-feed",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Live activity",e.jsxs("span",{className:"bw-feed-count",children:[s.log.length," ",s.log.length===1?"event":"events"]})]}),e.jsx("p",{className:"bw-since bw-feed-note",children:"Pages you opened yourself — counted to run the rule, never written down. Addresses appear under Findings, and only for pages a program opened."}),e.jsx("div",{className:"bw-log","aria-live":"polite","aria-relevant":"additions",children:s.log.length===0?e.jsx("p",{className:"bw-note",children:"The deck writes a line here each time it looks at a profile."}):s.log.map(a=>e.jsxs("div",{className:`bw-log-line ${a.level}${a.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(a.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),a.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:a.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:a.parts.profile,children:a.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[a.parts.value,a.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",a.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:a.text})]},`${a.atMs}-${a.level}-${a.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function le(s){const a=h=>new Date(h).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?a(s.startMs):`${a(s.startMs)} → ${a(s.endMs)}`}function ce(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const a=Math.round((s.endMs-s.startMs)/6e4);return a<1?"":`${a} min`}function de({snap:s,dismiss:a,refreshRef:h}){const[c,d]=r.useState(null),u=r.useRef(new Map),t=r.useRef(null);r.useEffect(()=>{var i,w;const o=t.current;!o||s.episodes.some(b=>E(b)===o.id)||(t.current=null,Q(((i=document.activeElement)==null?void 0:i.tagName)??null)&&((w=(o.next?u.current.get(o.next):void 0)??h.current)==null||w.focus()))},[s]);const n=o=>{t.current={id:E(o),next:_(s.episodes.map(E),E(o))},a(o)},l=r.useMemo(()=>{const o=[];for(const i of s.episodes){const w=ce(i.startMs),b=o[o.length-1];b&&b.label===w?b.episodes.push(i):o.push({label:w,episodes:[i]})}return o},[s]);return e.jsxs("section",{className:"bw-findings",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Findings",s.episodes.length>0&&e.jsxs("span",{className:"bw-feed-count",children:[s.episodes.length," ",s.episodes.length===1?"episode":"episodes"]})]}),e.jsx("div",{className:"bw-eps",children:l.length===0?e.jsxs("div",{className:"bw-empty",children:[e.jsx("p",{className:"bw-empty-head",children:"Nothing found yet"}),e.jsxs("p",{className:"bw-empty-note",children:["An episode lands here when a program opens pages in a browser nobody has touched for"," ",s.settings.quietMinutes," ",s.settings.quietMinutes===1?"minute":"minutes",". On most machines that is rare, so an empty list is the ordinary result rather than a sign something is wrong."]}),!s.settings.enabled&&e.jsx("p",{className:"bw-empty-note",children:"Watching is off, so this shows only what this deck has seen since it started. Anything an earlier run recorded comes back when you switch it on."})]}):l.map(o=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:o.label}),o.episodes.map(i=>{const w=E(i),b=c===w;return e.jsxs("div",{className:`bw-ep${b?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:p=>{p?u.current.set(w,p):u.current.delete(w)},onClick:()=>d(b?null:w),"aria-expanded":b,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:b?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:i.host}),e.jsxs("span",{className:"bw-ep-meta",children:[le(i),U(i)&&e.jsxs(e.Fragment,{children:[" · ",U(i)]}),i.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[i.count," ",i.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>n(i),"aria-label":`Dismiss ${i.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),b&&e.jsx("ul",{className:"bw-urls",children:i.urls.map((p,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(p.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:p.url})]},`${p.url}-${p.timeMs}-${y}`))})]},w)})]},o.label))})]})}function he(s){const a=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!a)return!1;const h=parseInt(a[1],16),c=[h>>16&255,h>>8&255,h&255].map(d=>{const u=d/255;return u<=.03928?u/12.92:Math.pow((u+.055)/1.055,2.4)});return .2126*c[0]+.7152*c[1]+.0722*c[2]>.5}const G=1e4,ue=12e3;function we({browsers:s,findings:a,watching:h,palette:c}){const d=r.useRef(null),u=r.useRef(null);return u.current={browsers:s,findings:a,watching:h},r.useEffect(()=>{var b;const t=d.current;if(!t)return;const n=t.getContext("2d");if(!n)return;const l=((b=window.matchMedia)==null?void 0:b.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let o=0,i=!1;const w=()=>{var q;if(i)return;const p=u.current;if(!p)return;const{browsers:y,findings:j,watching:P}=p,S=c["--accent"]||"#7dd3fc",g=c["--line"]||"#1f2229",v=c["--text-dim"]||"#6e727c",$=he(c["--bg"]),N=$?42:26,k=$?.85:.65,A=Date.now(),R=Math.min(window.devicePixelRatio||1,2),C=t.clientWidth,W=t.clientHeight;(t.width!==C*R||t.height!==W*R)&&(t.width=C*R,t.height=W*R),n.setTransform(R,0,0,R,0,0),n.clearRect(0,0,C,W);const m=C/2,x=W/2,M=Math.min(C,W)/2-6;n.strokeStyle=g,n.lineWidth=1,n.globalAlpha=$?1:.9;for(const f of[.4,.7,1])n.beginPath(),n.arc(m,x,M*f,0,Math.PI*2),n.stroke();n.beginPath(),n.moveTo(m-M,x),n.lineTo(m+M,x),n.moveTo(m,x-M),n.lineTo(m,x+M),n.stroke(),n.globalAlpha=1;const I=(P&&!l?A%G/G:0)*Math.PI*2-Math.PI/2;if(P){const f=(q=n.createConicGradient)==null?void 0:q.call(n,I-.85,m,x);f&&(f.addColorStop(0,"transparent"),f.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${S} ${N}%, transparent)`),f.addColorStop(1,"transparent"),n.fillStyle=f,n.beginPath(),n.moveTo(m,x),n.arc(m,x,M,I-.85,I),n.closePath(),n.fill()),n.strokeStyle=S,n.globalAlpha=k,n.beginPath(),n.moveTo(m,x),n.lineTo(m+Math.cos(I)*M,x+Math.sin(I)*M),n.stroke(),n.globalAlpha=1}y.forEach((f,T)=>{const B=T/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=f.lastReadMs===null?1:Math.min(1,(A-f.lastReadMs)/12e4),O=M*(.42+D*.5),F=m+Math.cos(B)*O,L=x+Math.sin(B)*O,J=Math.abs((I-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=P?Math.max(0,1-J/.55):0;n.beginPath(),n.arc(F,L,3+K*1.6,0,Math.PI*2),n.fillStyle=f.running?S:v,n.globalAlpha=f.running?.55+K*.45:.35,n.fill(),n.globalAlpha=1});for(const f of j){const T=(A-f.atMs)/ue;if(T<0||T>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===f.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=m+Math.cos(D)*M*.6,F=x+Math.sin(D)*M*.6;n.beginPath(),n.arc(O,F,4+T*22,0,Math.PI*2),n.strokeStyle=S,n.globalAlpha=(1-T)*.8,n.lineWidth=1.5,n.stroke(),n.globalAlpha=1}o=requestAnimationFrame(w)};return w(),()=>{i=!0,cancelAnimationFrame(o)}},[c]),e.jsx("canvas",{ref:d,className:"bw-radar",role:"img","aria-label":h?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function me({snap:s,watching:a,palette:h}){const[c,d]=r.useState(!1),[u,t]=r.useState(()=>Date.now());r.useEffect(()=>{const l=setInterval(()=>t(Date.now()),1e3);return()=>clearInterval(l)},[]);const n=Y(s.coverage.lastHumanMs,s.coverage.quietMs,u);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Activity overview",e.jsx("button",{className:"bw-help",onClick:()=>d(l=>!l),"aria-expanded":c,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(we,{browsers:a.map(l=>{var o;return{key:l.key,name:l.name,running:l.running,lastReadMs:((o=s.profiles.find(i=>i.browser===l.key))==null?void 0:o.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(l=>({browser:l.browser??null,atMs:l.endMs})),watching:s.settings.enabled,palette:h}),e.jsx("ul",{className:"bw-counts",children:z(a,s.profiles).map(l=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:l.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:l.name})]},l.key))})]}),e.jsx("p",{className:"bw-since",children:"Pages a program opened, since this deck started"}),s.coverage.checkedMs>0&&e.jsxs("p",{className:"bw-since",children:["Last checked ",V(s.coverage.checkedMs,u),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),n!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",X(n)]}),c&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function be({watching:s,rest:a}){const[h,c]=r.useState(!1),[d,u]=r.useState(!1);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Watched profiles",e.jsx("button",{className:"bw-help",onClick:()=>c(t=>!t),"aria-expanded":h,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),h&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Running"}),e.jsx("dd",{children:"the browser has a process on this machine right now"}),e.jsx("dt",{children:"Connected"}),e.jsx("dd",{children:"an open connection to an address Anthropic's relay uses — which it shares with claude.ai, so an open tab looks the same as an agent channel"})]}),e.jsx("ul",{className:"bw-profiles",children:s.map(t=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${t.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:t.name}),e.jsx("span",{className:"bw-prof-state",children:t.running?"running":"idle"}),t.running&&t.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},t.key))}),a.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>u(t=>!t),"aria-expanded":d,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:d?"▾":"▸"})," ",a.length," not watched"]}),d&&e.jsxs("p",{className:"bw-rest",children:[a.map(t=>t.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function pe({snap:s,quiet:a,setQuiet:h,save:c,access:d,setAccess:u}){return e.jsxs("div",{className:"bw-settings",children:[e.jsxs("label",{title:"A program opening a page counts as a finding only if nobody had touched the browser for this long. Shorter catches more and reports more of your own work; longer is quieter.",children:[e.jsx("span",{children:"Nobody browsing for"}),e.jsxs("select",{value:a??s.settings.quietMinutes,onChange:t=>{h(Number(t.target.value)),c({quietMinutes:Number(t.target.value)})},children:[e.jsx("option",{value:1,children:"1 min"}),e.jsx("option",{value:5,children:"5 min"}),e.jsx("option",{value:15,children:"15 min"}),e.jsx("option",{value:30,children:"30 min"}),e.jsx("option",{value:60,children:"60 min"})]})]}),e.jsxs("label",{children:[e.jsx("span",{children:"When it finds one"}),e.jsx("select",{value:s.settings.reaction,onChange:t=>void c({reaction:t.target.value}),disabled:!s.settings.enabled,title:s.settings.enabled?"What to do besides writing it down.":"Turn watching on to arm a reaction.",children:(s.reactions??["notify"]).map(t=>e.jsx("option",{value:t,children:t==="notify"?"notify me":t==="close-tab"?"close the tab":"quit the browser"},t))})]}),e.jsxs("div",{className:"bw-settings-note",children:[e.jsxs("p",{children:["Chrome marks a navigation that came from an extension or a command rather than from a click. These are the ones that happened while nobody had touched the browser for the time above. ",e.jsx("strong",{children:"Usually that is your own agent doing what you asked."})]}),e.jsxs("button",{className:"bw-why",onClick:()=>u(t=>!t),"aria-expanded":d,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:d?"▾":"▸"})," What Browser Watch can access"]}),d&&e.jsxs("dl",{className:"bw-access",children:[e.jsx("dt",{children:"Reads"}),e.jsxs("dd",{children:["A copy of each browser's own history database — the live file is locked while the browser holds it. Only rows newer than the moment this deck started:"," ",new Date(s.coverage.startedMs).toLocaleString(),"."]}),e.jsx("dt",{children:"Keeps"}),e.jsxs("dd",{children:["Only while the switch is on, and only the episodes it flagged — never your ordinary browsing. In ",e.jsx("code",{className:"bw-path",children:s.coverage.logPath}),typeof s.coverage.logBytes=="number"?` (${Z(s.coverage.logBytes)})`:"",", with every address written in full so you can check it yourself."]}),e.jsx("dt",{children:"Sends"}),e.jsx("dd",{children:"Nothing. No part of this reads or writes over the network; the deck serves on 127.0.0.1 and this panel talks only to it."}),e.jsx("dt",{children:"Never reads"}),e.jsx("dd",{children:"Anything from before this deck started, cookies, saved passwords, page contents, or any browser profile with no history file."})]})]})]})}function H(s,a){return a?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function ge({snap:s,saving:a,save:h,why:c,setWhy:d}){return e.jsxs("footer",{className:"bw-status",children:[e.jsx("span",{className:`bw-mode-dot${s.settings.enabled?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-mode-word",children:H(s,a).word},H(s,a).kind),e.jsx("span",{className:"bw-status-text",children:s.coverage.archived===0?s.settings.enabled?"No episodes recorded yet":"No episodes on disk":`${s.coverage.archived} ${s.coverage.archived===1?"episode":"episodes"} on disk`}),e.jsx("button",{className:`glyph-btn bw-gear${c?" on":""}`,onClick:()=>d(u=>!u),"aria-expanded":c,"aria-label":"Settings",title:"Settings",children:e.jsxs("svg",{width:"13",height:"13",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.3","aria-hidden":!0,children:[e.jsx("circle",{cx:"7",cy:"7",r:"2.1"}),e.jsx("path",{d:"M7 1.2v1.6M7 11.2v1.6M1.2 7h1.6M11.2 7h1.6M2.9 2.9l1.1 1.1M10 10l1.1 1.1M11.1 2.9L10 4M4 10l-1.1 1.1"})]})}),e.jsxs("label",{className:"bw-switch",htmlFor:"bw-enabled",children:[e.jsx("span",{className:"bw-switch-label",id:"bw-enabled-label",children:"Watch browser activity"}),e.jsx("button",{id:"bw-enabled",type:"button",role:"switch","aria-checked":s.settings.enabled,"aria-labelledby":"bw-enabled-label",className:"switch",onClick:()=>void h({enabled:!s.settings.enabled}),title:s.settings.enabled?"On — every episode it finds is written down, so the list outlives the browsing history being cleared":"Off — showing only what this deck has seen since it started. Anything an earlier run archived is hidden until you switch back on, and nothing new is kept",children:e.jsx("span",{className:"switch-knob"})})]})]})}function fe({relay:s}){const[a,h]=r.useState(!1),[c,d]=r.useState(!1),u=s.killswitch.blocked?"unblock":"block",t=s.command[u],n=r.useCallback(async()=>{await ee(t.command)&&(d(!0),window.setTimeout(()=>d(!1),1600))},[t.command]),l=s.profiles.filter(i=>{var w;return(w=i.report)==null?void 0:w.present}),o=s.profiles.filter(i=>i.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(i=>e.jsx("code",{className:"bw-path",children:i},i))]}),e.jsxs("button",{className:"bw-why",onClick:()=>h(i=>!i),"aria-expanded":a,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:a?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),a&&e.jsxs("div",{className:"bw-rc-body",children:[l.length>0&&e.jsx("dl",{className:"bw-key",children:l.map(i=>e.jsxs(r.Fragment,{children:[e.jsxs("dt",{children:[i.name,i.profile==="Default"?"":` · ${i.profile}`]}),e.jsxs("dd",{children:[i.report.enabled?"enabled":"installed but switched off",i.report.allUrls?", can act on every site":", limited to the sites it was granted",i.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",i.report.sensitiveApis.join(", ")]})]})]},`${i.browser}/${i.profile}`))}),o.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[o.map(i=>i.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),l.some(i=>i.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:t.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:n,children:c?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:t.note})]})]})}function xe(s){const[a,h]=r.useState(null),[c,d]=r.useState(null),[u,t]=r.useState(null),[n,l]=r.useState(!1),o=r.useRef(!1),[i,w]=r.useState(null),[b,p]=r.useState(!1),y=r.useRef(!1),j=r.useCallback(async g=>{var v,$;if(!(g&&!se(o.current))){g&&(o.current=!0,l(!0));try{const N=await fetch(`/api/browser-watch${g?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const k=await N.json();h(k),y.current||w(((v=k==null?void 0:k.settings)==null?void 0:v.quietMinutes)??15),s((($=k==null?void 0:k.settings)==null?void 0:$.enabled)===!0),d(null)}catch(N){d(N instanceof Error?N.message:String(N))}finally{g&&(o.current=!1,l(!1))}}},[s]);r.useEffect(()=>{j(!1)},[j]),r.useEffect(()=>{const g=setInterval(()=>{j(!1)},1e4);return()=>clearInterval(g)},[j]);const P=r.useCallback(async g=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:g.host,startMs:g.startMs})})}finally{await j(!1)}},[j]),S=r.useCallback(async g=>{y.current=!0,p(!0);try{const v=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(g)});if(!v.ok)throw new Error(`the deck answered ${v.status}`);await j(!1),t(null)}catch(v){t(v instanceof Error?v.message:String(v))}finally{y.current=!1,p(!1)}},[j]);return{snap:a,error:c,writeError:u,setWriteError:t,busy:n,quiet:i,setQuiet:w,saving:b,load:j,dismiss:P,save:S}}function ye({onClose:s,onSeen:a,onWatching:h,palette:c}){const d=te(s),u=ne(s),{snap:t,error:n,writeError:l,setWriteError:o,busy:i,quiet:w,setQuiet:b,saving:p,load:y,dismiss:j,save:P}=xe(h),[S,g]=r.useState(!1),[v,$]=r.useState(!1),N=r.useRef(null),k=r.useRef(a);k.current=a,r.useEffect(()=>()=>k.current(Date.now()),[]);const A=ae(t==null?void 0:t.browsers),R=((t==null?void 0:t.browsers)??[]).filter(m=>!(m.installed&&m.profiles>0)),C=t?ie(t):null,W=A.reduce((m,x)=>m+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",...u,role:"presentation",children:e.jsxs("div",{ref:d,className:"modal bw-modal",onClick:m=>m.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"bw-title",children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"bw-title",children:"Browser watch"}),e.jsx("span",{className:"modal-tool-id",children:t?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:N,className:"glyph-btn",onClick:()=>void y(!0),...re(i),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:i?"…":"↻"}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]})]}),e.jsxs("div",{className:"modal-body bw-body",children:[!t&&!n&&e.jsxs("div",{className:"bw-loading",children:[e.jsx("span",{className:"bw-loading-bar","aria-hidden":!0}),e.jsx("p",{children:"Reading each browser's history. The first look copies the file, which takes a moment."})]}),n&&e.jsx("div",{className:"bw-state",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not read"}),e.jsx("span",{className:"bw-row-detail",children:n})]})}),t&&C&&e.jsx("p",{className:`bw-trouble ${C.kind}`,role:"status",children:C.text}),t&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(me,{snap:t,watching:A,palette:c}),e.jsx(be,{watching:A,rest:R}),t.relay&&e.jsx(fe,{relay:t.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(de,{snap:t,dismiss:j,refreshRef:N}),e.jsx(oe,{snap:t})]})]}),t&&S&&e.jsx(pe,{snap:t,quiet:w,setQuiet:b,save:P,access:v,setAccess:$}),l&&e.jsx("div",{className:"bw-state bw-write-err",role:"alert",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not save that setting"}),e.jsxs("span",{className:"bw-row-detail",children:[l," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>o(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),t&&e.jsx(ge,{snap:t,saving:p,save:P,why:S,setWhy:g})]})]})})}export{ye as default};
@@ -0,0 +1 @@
1
+ import{r as d,z as Oe,j as e,A as Ae,u as Le,d as _e,B as Ie}from"./index-DGMP4S3t.js";function ye(s){return[...new Set(s.filter(t=>t.key.endsWith("ENDPOINT")).map(t=>t.value).filter(t=>/^https?:\/\//.test(t)))]}function Ne(s){return(s==null?void 0:s.status)!=="observing"?[]:[...new Set(s.connections.filter(t=>t.active===!0).map(t=>t.destination))]}function ve(s){try{const t=new URL(s);return/^\d+\.\d+\.\d+\.\d+$/.test(t.hostname)?`${t.hostname}:${t.port||(t.protocol==="https:"?"443":"80")}`:null}catch{return null}}function Re(s){const t=s.filter(r=>r.key==="CLAUDE_CODE_ENABLE_TELEMETRY").map(r=>r.value);return t.length?new Set(t).size>1?"Conflicting settings":t[0]==="Enabled"?"Enabled in settings":t[0]==="Disabled"?"Disabled in settings":"Unknown":"Unknown"}function Me(s){const t=s.replace(/^\uFEFF/,"").trim();if(!t)return[];let r;try{JSON.parse(t),r=[t]}catch{r=s.replace(/^\uFEFF/,"").split(/\r?\n/)}if(r.length>1e4)throw new Error("This file has more than 10,000 lines. Choose a smaller export.");return r.flatMap((m,c)=>{var g;if(!m.trim())return[];try{const h=JSON.parse(m),u=h&&typeof h=="object"?h:{},f=new Set,i=[h];for(;i.length;){const v=i.pop();if(!v||typeof v!="object")continue;if(Array.isArray(v)){for(const o of v)i.push(o);continue}const x=v;for(const o of["session.id","session_id","sessionId"])typeof x[o]=="string"&&f.add(x[o]);if(["session.id","session_id"].includes(String(x.key))){const o=x.value;typeof(o==null?void 0:o.stringValue)=="string"&&f.add(o.stringValue)}for(const o of Object.values(x))i.push(o)}return[{line:c+1,raw:m,sessionIds:[...f],label:String(u.eventName??((g=u.event)==null?void 0:g.name)??u.type??u.model??"JSON record").slice(0,160),sessionId:typeof(u.session_id??u.sessionId??u["session.id"])=="string"?u.session_id??u.sessionId??u["session.id"]:null,timestamp:typeof u.timestamp=="string"?u.timestamp:null,valid:!0}]}catch{return[{line:c+1,raw:m,label:"Invalid JSON",sessionId:null,timestamp:null,sessionIds:[],valid:!1}]}})}function De(){const[s,t]=d.useState(null),[r,m]=d.useState(!1);return d.useEffect(()=>{let c=!0,g,h=null;const u=()=>document.visibilityState!=="hidden",f=()=>{clearTimeout(g),h==null||h.abort(),h=null},i=async()=>{if(!c||!u()||h)return;const x=new AbortController;h=x;try{const o=await fetch("/api/system/traffic-radar",{signal:x.signal});if(!o.ok)throw new Error("radar_unavailable");const w=await o.json();if(!w.ok)throw new Error("radar_unavailable");c&&!x.signal.aborted&&(t(w),m(!1))}catch{c&&!x.signal.aborted&&m(!0)}finally{h===x&&(h=null),c&&!x.signal.aborted&&u()&&(g=setTimeout(i,5e3))}},v=()=>{f(),document.visibilityState!=="hidden"&&i()};return i(),document.addEventListener("visibilitychange",v),()=>{c=!1,f(),document.removeEventListener("visibilitychange",v)}},[]),{snapshot:s,failed:r}}function Pe(){const[s,t]=d.useState(null),[r,m]=d.useState(!1),[c,g]=d.useState(!1),[h,u]=d.useState(""),[f,i]=d.useState(""),[v,x]=d.useState([]),[o,w]=d.useState(null),[C,_]=d.useState(null),N=d.useRef(null),I=d.useRef(null);d.useEffect(()=>{let a=!0,p,l=null;const b=()=>document.visibilityState!=="hidden",T=()=>{clearTimeout(p),l==null||l.abort(),l=null},S=async()=>{if(!a||!b()||l)return;const L=new AbortController;l=L;try{const F=await fetch("/api/system/traffic-radar/capture",{signal:L.signal});if(!F.ok)throw new Error;const P=await F.json();a&&!L.signal.aborted&&(t(P),m(!1))}catch{a&&!L.signal.aborted&&m(!0)}finally{l===L&&(l=null),a&&!L.signal.aborted&&b()&&(p=setTimeout(S,1e3))}},M=()=>{T(),document.visibilityState!=="hidden"&&S()};return S(),document.addEventListener("visibilitychange",M),()=>{var L;a=!1,T(),(L=I.current)==null||L.abort(),document.removeEventListener("visibilitychange",M)}},[]);const k=async(a,p)=>{if(!Oe(N.current))return;N.current=a,_(a),g(!0),u("");const l=new AbortController;I.current=l;const b=setTimeout(()=>l.abort(),5e3);try{const T=await fetch(`/api/system/traffic-radar/capture/${a}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({destination:p}),signal:l.signal}),S=await T.json();if(!T.ok)throw new Error(S.error||"Capture action failed. Please retry.");a==="prepare"&&(i(S.command),x(S.commands??[{destination:String(p),command:S.command}]),w(S.sessionId)),a==="stop"&&(i(""),x([]));const M=await fetch("/api/system/traffic-radar/capture",{signal:l.signal});M.ok&&(t(await M.json()),m(!1))}catch(T){u(T instanceof Error?T.message:"Capture action failed. Please retry.")}finally{clearTimeout(b),I.current=null,N.current=null,_(null),g(!1)}};return{capture:s,failed:r,busy:c,pendingAction:C,error:h,commands:(s==null?void 0:s.sessionId)===o?v:[],command:(s==null?void 0:s.sessionId)===o?f:"",action:k}}const A=s=>s&&typeof s=="object"&&!Array.isArray(s)?s:{},J=s=>Array.isArray(s)?s:[],ae=s=>new Date(s).toLocaleTimeString(),$e=s=>({unconfirmed:"Transfer observed · receipt unconfirmed",accepted:"Collector accepted",partial:"Collector reported partial success",rejected:"Collector rejected",unknown:"Response incomplete",reset:"Stream reset"})[s.outcome],be={encrypted:"Encrypted traffic observed; contents cannot be read.",joined_midstream:"Capture joined an existing connection. Waiting for a new HTTP/2 connection to decode it reliably.",capture_gap:"Incomplete packets or a gap were observed. Affected contents are not shown as decoded.",decode_failed:"Some payloads could not be decoded. They are not shown as verified events.",payload_limit:"A payload exceeded the inspection limit and was omitted.",stream_limit:"A stream exceeded the inspection limit.",connection_limit:"The connection inspection limit was reached.",unsupported_compression:"A compression format other than gzip was observed.",unsupported_link:"This network capture format is not supported.",unsupported_network:"Some packets used a network protocol this decoder does not support.",fragmented:"Fragmented IP packets cannot be decoded in this version.",unknown_export:"A request could not be identified as an OTLP export.",connection_closed:"An HTTP/2 connection closed.",idle_connection:"An idle connection was removed from the decoder."};function K(s){const t=A(s);for(const r of["stringValue","intValue","doubleValue","boolValue","bytesValue"])if(t[r]!==void 0)return t[r];return t.arrayValue?J(A(t.arrayValue).values).map(K):t.kvlistValue?Object.fromEntries(J(A(t.kvlistValue).values).map(r=>{const m=A(r);return[String(m.key),K(m.value)]})):null}function Je(s){return/prompt/i.test(s)?"Prompt-related field included in this export.":/response|completion/i.test(s)?"Response-related field included in this export.":/tool/i.test(s)?"Tool-related field included in this export.":/session/i.test(s)?"Session identifier or session metadata.":/user|email|account/i.test(s)?"User or account metadata.":/token|cost|duration|latency/i.test(s)?"Usage or timing measurement.":"Attribute included in this export."}function U({value:s}){return e.jsx("pre",{className:"tr-value",children:typeof s=="string"?s:JSON.stringify(s,null,2)})}function le({value:s}){return e.jsx("dl",{className:"tr-attributes",children:J(s).map((t,r)=>{const m=A(t);return e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("code",{children:String(m.key??"Unnamed attribute")}),e.jsx("span",{children:Je(String(m.key))})]}),e.jsx("dd",{children:e.jsx(U,{value:K(m.value)})})]},r)})})}function Ue({record:s}){const t=["gauge","sum","histogram","exponentialHistogram","summary"].find(r=>s[r]);return t?e.jsxs("div",{children:[s.description&&e.jsx("p",{className:"tr-note",children:String(s.description)}),J(A(s[t]).dataPoints).map((r,m)=>{const c=A(r);return e.jsxs("section",{children:[e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Measurement"}),e.jsx("dd",{children:t})]}),c.asInt!==void 0||c.asDouble!==void 0?e.jsxs("div",{children:[e.jsx("dt",{children:"Value"}),e.jsx("dd",{children:e.jsxs("code",{children:[String(c.asInt??c.asDouble)," ",String(s.unit??"")]})})]}):null,c.count!==void 0&&e.jsxs("div",{children:[e.jsx("dt",{children:"Count"}),e.jsx("dd",{children:e.jsx("code",{children:String(c.count)})})]}),c.sum!==void 0&&e.jsxs("div",{children:[e.jsx("dt",{children:"Sum"}),e.jsx("dd",{children:e.jsxs("code",{children:[String(c.sum)," ",String(s.unit??"")]})})]})]}),e.jsx(le,{value:c.attributes}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All measurement fields"}),e.jsx(U,{value:c})]})]},m)})]}):null}function Fe({record:s}){let t=null;try{t=`${Number(BigInt(String(s.endTimeUnixNano))-BigInt(String(s.startTimeUnixNano)))/1e6} ms`}catch{t=null}return e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Duration"}),e.jsx("dd",{children:t??"Not included"})]}),s.kind&&e.jsxs("div",{children:[e.jsx("dt",{children:"Span kind"}),e.jsx("dd",{children:String(s.kind)})]}),s.status&&e.jsxs("div",{children:[e.jsx("dt",{children:"Span status"}),e.jsx("dd",{children:e.jsx(U,{value:s.status})})]})]})}function Ve({payload:s,signal:t}){const r=t==="traces"?"Spans":t==="logs"?"Logs":"Metrics",m=t==="traces"?"spans":t==="logs"?"logRecords":"metrics";return e.jsx("div",{className:"tr-decoded",children:J(s["resource"+r]).map((c,g)=>{const h=A(c);return e.jsxs("section",{children:[e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"Resource metadata"}),e.jsx(le,{value:A(h.resource).attributes})]}),J(h["scope"+r]).map((u,f)=>{const i=A(u);return e.jsxs("div",{children:[i.scope&&e.jsxs("p",{className:"tr-note",children:["Instrumentation: ",String(A(i.scope).name??"Unnamed")," ",String(A(i.scope).version??"")]}),J(i[m]).map((v,x)=>{const o=A(v);return e.jsxs("article",{className:"tr-record",children:[e.jsx("h4",{children:String(o.eventName??o.name??`${t==="logs"?"Log record":"Record"} ${x+1}`)}),o.body&&e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"tr-note",children:"Event body"}),e.jsx(U,{value:K(o.body)})]}),t==="metrics"&&e.jsx(Ue,{record:o}),t==="traces"&&e.jsx(Fe,{record:o}),o.attributes&&e.jsx(le,{value:o.attributes}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All record fields"}),e.jsx(U,{value:o})]})]},x)})]},f)})]},g)})})}function Be({capture:s,radar:t,failed:r,busy:m,pendingAction:c=null,error:g,command:h,commands:u=[],action:f,sessionFilter:i="all",sessions:v=[]}){var ue,he,pe,fe,xe,je,ge;const x=ye(((ue=t==null?void 0:t.config)==null?void 0:ue.variables)??[]),o=[...new Set(x.map(ve).filter(n=>!!n))],w=Ne(t).filter(n=>!o.includes(n)&&ve(`http://${n}`)),[C,_]=d.useState("all"),[N,I]=d.useState(""),[k,a]=d.useState(null),[p,l]=d.useState(null),[b,T]=d.useState("all"),[S,M]=d.useState("all"),[L,F]=d.useState(null),[P,Q]=d.useState(null),[X,H]=d.useState(null),[Z,re]=d.useState("summary"),[E,ee]=d.useState(null),[oe,Se]=d.useState(0),se=d.useRef(null),ne=(s==null?void 0:s.events)??[],B=P?P.map(n=>ne.find(j=>j.id===n.id)).filter(n=>!!n):ne,W=B.filter(n=>{var j,O;return(S==="all"||n.signal===S)&&(b==="all"||b===n.destination)&&(i==="all"||(i==="unidentified"?!((j=n.sessionIds)!=null&&j.length):(O=n.sessionIds)==null?void 0:O.includes(i)))}),$=((s==null?void 0:s.observations)??[]).filter(n=>(L===null||L.includes(n.id))&&S==="all"&&(i==="all"||i==="unidentified")&&(b==="all"||b===n.destination)),Y=$.find(n=>n.id===k),y=k!==null?void 0:p===null?W[0]:W.find(n=>n.id===p),G=y==null?void 0:y.id;d.useEffect(()=>{l(null),a(null)},[i,S,b]),d.useEffect(()=>{if(ee(null),H(null),G===void 0)return;const n=new AbortController;let j=!0;const O=setTimeout(()=>n.abort(),5e3);return fetch(`/api/system/traffic-radar/capture/event?id=${G}`,{signal:n.signal}).then(async R=>{if(!R.ok)throw new Error("This message has expired. Select a newer message.");const ke=await R.json();j&&!n.signal.aborted&&ee({id:G,payload:ke.payload})}).catch(R=>{j&&ee({id:G,error:n.signal.aborted?"Loading timed out. Try again.":R instanceof Error?R.message:"Could not read this message."})}).finally(()=>clearTimeout(O)),()=>{j=!1,clearTimeout(O),n.abort()}},[G,oe]);const D=!!s&&["awaiting","receiving","capturing"].includes(s.state),q=((he=s==null?void 0:s.sources)==null?void 0:he.filter(n=>n.active).length)??0,z=((pe=s==null?void 0:s.sources)==null?void 0:pe.length)??1,we=r?"Monitor connection lost":s?{idle:"Message capture stopped",awaiting:"Terminal activation needed",receiving:"Listening · waiting for traffic",capturing:"Monitoring traffic",stopped:"Message capture stopped",expired:"Monitoring expired",interrupted:"Capture disconnected",error:"Capture could not be read"}[s.state]:"Reading monitor…",V=n=>{const j=Ae(c??(m?"capture":null),n);return{disabled:j.disabled,"aria-busy":j.busy}},Ce=n=>{var j,O,R;a(null),l(n.id),typeof window<"u"&&((j=window.matchMedia)!=null&&j.call(window,"(max-width: 640px)").matches)&&((O=se.current)==null||O.focus({preventScroll:!0}),(R=se.current)==null||R.scrollIntoView({block:"start",behavior:"auto"}))},ce=()=>{var j;H(null),Q(null),F(null),l(null),a(null);const n=C==="custom"?[N.trim()]:C==="all"?o:[C];f("prepare",n.length?n:((j=s==null?void 0:s.sources)==null?void 0:j.map(O=>O.destination))??(s!=null&&s.destination?[s.destination]:[]))},te=u.length?u:h?[{destination:(s==null?void 0:s.destination)??"Collector",command:h}]:[],de=async(n,j)=>{try{await navigator.clipboard.writeText(n),H(j)}catch{H("failed")}},me=(n=[])=>n.length?n.map(j=>{var O;return`${((O=v.find(R=>R.id===j))==null?void 0:O.label)??"Session"} · ${j.slice(0,8)}`}).join(", "):"Unidentified session",Te=((s==null?void 0:s.issues.encrypted)??0)>0,Ee=new Set([...((fe=s==null?void 0:s.sources)==null?void 0:fe.map(n=>n.destination))??[],...o]),ie=(t==null?void 0:t.status)==="observing"?t.connections.filter(n=>n.active&&Ee.has(n.destination)):[];return e.jsxs("div",{className:"tr-telemetry",children:[e.jsxs("div",{className:"tr-capture-bar",children:[e.jsxs("div",{children:[e.jsxs("h3",{role:"status",children:[e.jsx("span",{className:`tr-live-dot${D&&q?" is-live":""}`}),we]}),e.jsxs("p",{className:"tr-note",children:[D?`${q} of ${z} destination${z===1?"":"s"} listening. `:"See messages sent to your configured collectors. ","Claude settings stay unchanged."]})]}),e.jsxs("div",{className:"tr-capture-actions",children:[D?e.jsx("button",{className:"btn",...V("stop"),onClick:()=>void f("stop"),children:"Stop monitoring"}):e.jsx("button",{className:"btn tr-start",...V("prepare"),disabled:V("prepare").disabled||!s||r||(t==null?void 0:t.status)==="unsupported"||C==="all"&&!o.length||C==="custom"&&!N.trim(),onClick:ce,children:c==="prepare"?"Starting…":"Start monitoring"}),e.jsx("button",{className:"btn",...V("clear"),disabled:V("clear").disabled||!(s!=null&&s.events.length)&&!((xe=s==null?void 0:s.observations)!=null&&xe.length),onClick:()=>{l(null),a(null),Q(null),F(null),f("clear")},children:"Clear messages"})]})]}),!D&&e.jsxs("div",{className:"tr-destination",children:[e.jsxs("label",{children:["Destination",e.jsxs("select",{className:"tr-select","aria-label":"Capture destination",value:C,onChange:n=>_(n.target.value),children:[e.jsxs("option",{value:"all",children:["All configured IPv4 destinations (",o.length,")"]}),o.map(n=>e.jsx("option",{value:n,children:n},n)),w.length>0&&e.jsx("optgroup",{label:"Observed Claude connections · telemetry unverified",children:w.map(n=>e.jsxs("option",{value:n,children:[n," · observed"]},n))}),e.jsx("option",{value:"custom",children:"Another IPv4 address…"})]})]}),C==="custom"&&e.jsx("input",{className:"tr-input","aria-label":"Collector IPv4 and port",value:N,onChange:n=>I(n.target.value),placeholder:"192.168.1.10:4317"}),!o.length&&C==="all"&&e.jsx("span",{className:"tr-note",children:w.length?"Choose an observed connection or enter your collector address.":"Enter your collector address to monitor traffic. Missing settings do not mean telemetry is off."})]}),(t==null?void 0:t.status)==="unsupported"&&e.jsx("p",{className:"tr-capture-error",role:"status",children:"Live capture is available on macOS. You can still inspect a local file."}),g&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:g}),D&&!te.length&&(s==null?void 0:s.state)==="awaiting"&&e.jsxs("div",{className:"tr-activation",children:[e.jsx("p",{children:"Activation is pending. Generate the Terminal instructions again to continue."}),e.jsx("button",{className:"btn",...V("prepare"),onClick:ce,children:"Show activation instructions"}),e.jsx("p",{className:"tr-note",children:"The previous token will be invalidated."})]}),te.length>0&&D&&e.jsxs("details",{className:"tr-activation",open:!q,children:[e.jsxs("summary",{children:["Activate in Terminal",z>1?` · ${q}/${z} listening`:""]}),e.jsx("p",{children:"Run each command in a separate Terminal tab. Only tcpdump asks for administrator permission."}),te.map(n=>e.jsxs("div",{className:"tr-command-entry",children:[e.jsxs("div",{children:[e.jsx("code",{children:n.destination}),e.jsx("button",{className:"btn",onClick:()=>void de(n.command,n.destination),children:X===n.destination?"Copied":"Copy command"})]}),e.jsx("pre",{className:"tr-command",children:n.command})]},n.destination)),e.jsx("p",{className:"tr-note",children:"Press Ctrl+C in each Terminal tab to stop tcpdump. Monitoring expires after 10 minutes. Commands contain a temporary local token; do not share them."})]}),X==="failed"&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:"Clipboard unavailable. Select the text and copy it manually."}),r&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:"Displayed messages are from the last successful read. Reconnecting automatically."}),e.jsxs("div",{className:"tr-workspace",children:[e.jsxs("section",{className:"tr-feed","aria-label":"Captured telemetry messages",children:[e.jsxs("div",{className:"tr-feed-head",children:[e.jsxs("h3",{children:["Messages ",e.jsx("span",{className:"tr-count",children:W.length+$.length})]}),e.jsx("button",{className:"btn",disabled:!B.length&&!$.length,onClick:()=>{Q(P?null:[...B]),F(P?null:((s==null?void 0:s.observations)??[]).map(n=>n.id))},children:P?"Resume list":"Pause list"})]}),e.jsxs("div",{className:"tr-filter",children:[e.jsxs("label",{children:["Type",e.jsxs("select",{className:"tr-select","aria-label":"Message type",value:S,onChange:n=>{l(null),M(n.target.value)},children:[e.jsx("option",{value:"all",children:"All types"}),e.jsx("option",{value:"logs",children:"Events"}),e.jsx("option",{value:"metrics",children:"Metrics"}),e.jsx("option",{value:"traces",children:"Traces"})]})]}),e.jsxs("label",{children:["To",e.jsxs("select",{className:"tr-select","aria-label":"Message destination",value:b,onChange:n=>{l(null),T(n.target.value)},children:[e.jsx("option",{value:"all",children:"All destinations"}),[...new Set([...o,...((je=s==null?void 0:s.sources)==null?void 0:je.map(n=>n.destination))??[],...ne.map(n=>n.destination)])].map(n=>e.jsx("option",{value:n,children:n},n))]})]})]}),P&&e.jsx("p",{className:"tr-note",children:"List paused. Monitoring continues."}),Te&&e.jsxs("div",{className:"tr-observation",role:"status",children:[e.jsx("strong",{children:"Encrypted traffic observed"}),e.jsx("p",{className:"tr-note",children:"The connection is visible; its contents cannot be read. Inspect the collector for its JSON."})]}),((s==null?void 0:s.issues.joined_midstream)??0)>0&&e.jsx("p",{className:"tr-warning",children:"Waiting for a new HTTP/2 connection to decode messages. Existing traffic is not reconstructable."}),W.length?e.jsx("ul",{className:"tr-connections",children:W.map(n=>e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":(y==null?void 0:y.id)===n.id,onClick:()=>Ce(n),children:[e.jsxs("span",{className:"tr-event-top",children:[e.jsx("span",{className:"tr-signal",children:n.signal==="logs"?"Event":n.signal==="metrics"?"Metric":"Trace"}),e.jsx("time",{dateTime:new Date(n.at).toISOString(),children:ae(n.at)})]}),e.jsx("strong",{className:"tr-event-name",children:n.name}),e.jsx("span",{children:me(n.sessionIds)}),e.jsx("code",{children:n.destination}),e.jsxs("span",{className:"tr-event-bottom",children:[e.jsx("span",{className:`tr-receipt ${n.outcome==="accepted"?"tr-tone-ok":n.outcome==="rejected"||n.outcome==="partial"?"tr-tone-enabled":""}`,children:n.outcome==="accepted"?"Accepted":n.outcome==="unconfirmed"?"Receipt unconfirmed":n.outcome==="partial"?"Partially accepted":n.outcome==="rejected"?"Rejected":"Receipt unknown"}),e.jsxs("span",{children:[n.count," ",n.count===1?"record":"records"," · ",n.bytes.toLocaleString()," B"]})]})]})},n.id))}):!$.length&&e.jsxs("div",{className:"tr-empty-capture",children:[e.jsx("h4",{children:B.length||$.length?"No matching messages":D?"Waiting for messages":"Message capture is off"}),e.jsx("p",{className:"tr-note",children:B.length?"Try another session or message type.":D?"Messages appear here once capture is activated and new decodable traffic arrives.":"No messages have been captured by Radar. Claude may still be sending telemetry. Choose a destination, start monitoring, then run the Terminal command to inspect new traffic."})]}),$.length>0&&e.jsx("ul",{className:"tr-connections tr-unreadable",children:$.map(n=>e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":k===n.id,onClick:()=>{l(null),a(n.id)},children:[e.jsxs("span",{className:"tr-event-top",children:[e.jsx("span",{children:"Connection observation"}),e.jsx("time",{children:ae(n.at)})]}),e.jsx("strong",{children:n.reason==="encrypted"?"Encrypted traffic":"Contents unavailable"}),e.jsx("code",{children:n.destination}),e.jsx("span",{children:"Unidentified session · no decoded JSON"})]})},n.id))}),ie.length>0&&e.jsxs("details",{className:"tr-details",children:[e.jsxs("summary",{children:["Connections to these collectors (",ie.length,")"]}),e.jsx("ul",{className:"tr-live-connections",children:ie.map(n=>e.jsxs("li",{children:[e.jsx("code",{children:n.destination}),e.jsxs("span",{children:["Claude · PID ",n.pid," · ",n.workspace??"Workspace unknown"]})]},`${n.pid}:${n.destination}`))}),e.jsx("p",{className:"tr-note",children:"A connection alone does not prove a telemetry message was sent. These connections are not filtered by session."})]})]}),e.jsx("section",{className:"tr-inspector","aria-label":"Message contents",ref:se,tabIndex:-1,children:Y?e.jsxs("div",{className:"tr-inspector-empty",children:[e.jsx("h3",{children:Y.reason==="encrypted"?"Encrypted contents":"Contents unavailable"}),e.jsx("code",{children:Y.destination}),e.jsx("p",{className:"tr-note",children:be[Y.reason]??"This connection could not be decoded."}),e.jsx("p",{className:"tr-note",children:"There is no decoded JSON or confirmed session ID for this observation."})]}):y?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"tr-inspector-head",children:[e.jsx("h3",{children:y.name}),e.jsx("p",{className:"tr-note",children:me(y.sessionIds)})]}),e.jsxs("div",{className:"tr-detail-tabs",role:"group","aria-label":"Message format",children:[e.jsx("button",{className:"btn","aria-pressed":Z==="summary",onClick:()=>re("summary"),children:"Overview"}),e.jsx("button",{className:"btn","aria-pressed":Z==="json",onClick:()=>re("json"),children:"JSON"}),e.jsx("button",{className:"btn tr-copy-json",disabled:!(E!=null&&E.payload)||E.id!==y.id,onClick:()=>void de(JSON.stringify(E==null?void 0:E.payload,null,2),"json"),children:X==="json"?"Copied":"Copy JSON"})]}),(E==null?void 0:E.id)===y.id?E.payload?Z==="json"?e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"tr-note",children:"Decoded OTLP fields. Unknown protobuf fields and transport headers are excluded."}),e.jsx(U,{value:E.payload})]}):e.jsxs(e.Fragment,{children:[e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Destination"}),e.jsx("dd",{children:e.jsx("code",{children:y.destination})})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Time"}),e.jsx("dd",{children:ae(y.at)})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Receipt"}),e.jsx("dd",{children:$e(y)})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Size"}),e.jsxs("dd",{children:[y.bytes.toLocaleString()," bytes · ",y.count," ",y.count===1?"record":"records"]})]}),((ge=y.response)==null?void 0:ge.message)&&e.jsxs("div",{children:[e.jsx("dt",{children:"Response"}),e.jsx("dd",{children:e.jsx(U,{value:y.response.message})})]})]}),e.jsx(Ve,{payload:E.payload,signal:y.signal})]}):e.jsxs("div",{role:"alert",children:[e.jsx("p",{children:E.error}),e.jsx("button",{className:"btn",onClick:()=>Se(oe+1),children:"Retry"})]}):e.jsx("p",{className:"tr-note",role:"status",children:"Loading message contents…"})]}):e.jsxs("div",{className:"tr-inspector-empty",children:[e.jsx("svg",{width:"40",height:"40",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.25","aria-hidden":"true",children:e.jsx("path",{d:"M8 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h3M16 4h3a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3M10 8l-3 4 3 4M14 8l3 4-3 4"})}),e.jsx("h3",{children:p!==null?"Message no longer available":"See what was sent"}),e.jsx("p",{className:"tr-note",children:p!==null?"The selected message is no longer retained or does not match your filter. Select another message.":"Select a message to explore its contents or inspect the full JSON."})]})})]}),e.jsxs("footer",{className:"tr-monitor-footer",children:[e.jsxs("span",{children:[r?"Connection interrupted":D?"Local capture":"Not monitoring",s!=null&&s.bytes?` · ${s.bytes.toLocaleString()} capture bytes`:""]}),e.jsxs("details",{children:[e.jsx("summary",{children:"Monitoring limits"}),e.jsxs("div",{children:[e.jsx("p",{children:"Plaintext IPv4 OTLP/gRPC only. HTTPS contents cannot be decoded. Traffic to the chosen addresses can come from any process, not only Claude."}),e.jsx("p",{children:"Messages stay in local memory: up to 100 exports for 5 minutes. No capture file is written. Closing this modal does not stop capture; use Stop monitoring and Ctrl+C in Terminal."}),Object.entries((s==null?void 0:s.issues)??{}).map(([n,j])=>e.jsxs("p",{children:[be[n]??"An inspection limitation was observed."," (",j,")"]},n))]})]})]})]})}function We({sessionFilter:s="all",onSessions:t}){const[r,m]=d.useState([]),[c,g]=d.useState(""),[h,u]=d.useState(""),[f,i]=d.useState(!1),[v,x]=d.useState(null),[o,w]=d.useState(""),[C,_]=d.useState(!1),N=d.useRef(0),I=async l=>{if(!l)return;const b=++N.current;u(""),i(!0),_(!1);try{if(l.size>10*1024*1024)throw new Error("Choose a file smaller than 10 MB.");const T=await l.text(),S=Me(T);if(b!==N.current)return;t==null||t([...new Set(S.flatMap(M=>M.sessionIds))]),m(S),g(l.name),x(null),w(""),S.length||u("This file is empty. Choose a JSON or JSONL telemetry file.")}catch(T){b===N.current&&u(T instanceof Error?T.message:"Could not read this file.")}finally{b===N.current&&i(!1)}},k=r.filter(l=>(s==="all"||(s==="unidentified"?!l.sessionIds.length:l.sessionIds.includes(s)))&&(!o||l.raw.toLowerCase().includes(o.toLowerCase()))),a=k.slice(0,200),p=v===null?a[0]:k.find(l=>l.line===v);return e.jsxs("div",{className:"tr-file",children:[e.jsxs("div",{className:"tr-file-toolbar",children:[e.jsxs("div",{children:[e.jsx("h3",{children:"Inspect a telemetry file"}),e.jsx("p",{className:"tr-note",children:"Open a local JSON or JSONL file. It stays in your browser and is not uploaded."})]}),e.jsxs("label",{className:"btn tr-file-picker",children:[f?"Reading…":c?"Choose another file":"Open file",e.jsx("input",{type:"file",accept:".json,.jsonl,.ndjson,application/json","aria-label":"Open telemetry file",onChange:l=>{var b;I((b=l.target.files)==null?void 0:b[0]),l.target.value=""}})]})]}),h&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:h}),e.jsxs("div",{className:"tr-workspace",children:[e.jsxs("section",{className:"tr-feed","aria-label":"File records",children:[e.jsxs("h3",{children:[c||"File records"," ",e.jsx("span",{className:"tr-count",children:k.length})]}),r.length>0&&e.jsx("input",{type:"search",className:"tr-input tr-search","aria-label":"Search file contents",placeholder:"Search contents…",value:o,onChange:l=>{w(l.target.value),x(null)}}),a.length?e.jsx("ul",{className:"tr-connections",children:a.map(l=>e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":(p==null?void 0:p.line)===l.line,onClick:()=>{x(l.line),_(!1)},children:[e.jsxs("span",{className:"tr-event-top",children:[e.jsxs("span",{children:["Line ",l.line]}),e.jsx("span",{className:l.valid?"":"tr-tone-enabled",children:l.valid?"JSON":"Invalid JSON"})]}),e.jsx("strong",{children:l.label}),l.timestamp&&e.jsx("span",{children:l.timestamp}),e.jsx("span",{children:l.sessionIds.join(", ")||"Unidentified session"})]})},l.line))}):e.jsxs("div",{className:"tr-empty-capture",children:[e.jsx("h4",{children:r.length?"No matching records":"Your saved records, in one place"}),e.jsx("p",{className:"tr-note",children:r.length?"Try another session or search.":"Choose a file to browse its rows and inspect the original JSON. A saved file is separate from live network capture."})]}),k.length>a.length&&e.jsx("p",{className:"tr-note",children:"Showing the first 200 matching rows. Search to narrow the list."})]}),e.jsx("section",{className:"tr-inspector","aria-label":"File record contents",children:p?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"tr-feed-head",children:[e.jsxs("h3",{children:["Line ",p.line]}),e.jsx("button",{className:"btn",onClick:()=>{navigator.clipboard.writeText(p.raw).then(()=>_(!0),()=>u("Clipboard unavailable. Select the JSON and copy it manually."))},children:C?"Copied":"Copy JSON"})]}),e.jsx("p",{className:"tr-note",children:"Original file contents. This does not prove the record was sent or received."}),e.jsx("pre",{className:"tr-value",children:p.raw})]}):e.jsxs("div",{className:"tr-inspector-empty",children:[e.jsx("h3",{children:"Inspect a record"}),e.jsx("p",{className:"tr-note",children:"Select a row to read its original contents."})]})})]})]})}function qe({onClose:s,sessions:t=[]}){const r=Le(s),m=_e(s),{snapshot:c,failed:g}=De(),h=Pe();return e.jsx("div",{className:"modal-backdrop",...m,role:"presentation",children:e.jsxs("div",{className:"modal tr-modal",ref:r,role:"dialog","aria-modal":"true","aria-labelledby":"tr-title",onClick:u=>u.stopPropagation(),children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"tr-title",children:"Telemetry Radar"}),e.jsx("span",{className:"tr-beta",title:"Beta feature — still being tested and improved.",children:"Beta"})]}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]}),e.jsx(Ge,{snapshot:c,failed:g,captureState:h,sessions:t})]})})}function Ge({snapshot:s,failed:t=!1,captureState:r,sessions:m=[]}){var I,k;const[c,g]=d.useState("monitor"),[h,u]=d.useState([]),[f,i]=d.useState("all"),v=["monitor","configuration","file"],x=a=>{var b;const p=Ie(a,v.indexOf(c),v.length);if(p.kind!=="select")return;a.preventDefault();const l=v[p.index];g(l),(b=document.getElementById(`tr-tab-${l}`))==null||b.focus()},o=((I=s==null?void 0:s.config)==null?void 0:I.variables)??[],w=ye(o),C=Re(o),_=t?[]:Ne(s),N=new Map(m.map(a=>[a.id,a.label]));for(const a of((k=r==null?void 0:r.capture)==null?void 0:k.events)??[])for(const p of a.sessionIds??[])N.has(p)||N.set(p,p.slice(0,8));for(const a of h)N.has(a)||N.set(a,a.slice(0,8));return e.jsxs("div",{className:"tr-body",children:[e.jsxs("div",{className:"tr-summary",children:[e.jsxs("div",{className:"tr-summary-state",children:[e.jsx("span",{className:`tr-state ${t||!s?"tr-tone-unknown":C==="Enabled in settings"?"tr-tone-enabled":"tr-tone-unknown"}`,children:t?"Configuration unavailable":s?C==="Unknown"?"Configuration unknown":C:"Reading settings…"}),e.jsx("span",{className:"tr-note",children:w.length?`${w.length} configured destination${w.length===1?"":"s"}`:"No destination found in the settings files read. Telemetry may still be running."})]}),w.length>0&&e.jsx("div",{className:"tr-endpoints",children:w.map(a=>e.jsx("code",{children:a},a))}),!w.length&&_.length>0&&e.jsxs("div",{className:"tr-observed-summary",children:[e.jsx("span",{className:"tr-state",children:"Claude connections observed"}),e.jsx("div",{className:"tr-endpoints",children:_.map(a=>e.jsx("code",{children:a},a))}),e.jsx("p",{className:"tr-note",children:"These are active connections, not confirmed telemetry messages. Choose an observed address below to inspect its traffic."})]})]}),e.jsxs("div",{className:"tr-navigation",children:[e.jsx("div",{className:"tr-tabs",role:"tablist","aria-label":"Radar views",onKeyDown:x,children:[["monitor","Monitor"],["configuration","Configuration"],["file","File"]].map(([a,p])=>e.jsx("button",{id:`tr-tab-${a}`,className:"tr-tab",role:"tab","aria-selected":c===a,"aria-controls":`tr-panel-${a}`,tabIndex:c===a?0:-1,onClick:()=>g(a),children:p},a))}),e.jsxs("label",{className:"tr-session-filter",children:["Session",e.jsxs("select",{className:"tr-select",value:f,onChange:a=>i(a.target.value),"aria-label":"Session",children:[e.jsx("option",{value:"all",children:"All sessions"}),[...N].map(([a,p])=>e.jsxs("option",{value:a,children:[p," · ",a.slice(0,8)]},a)),e.jsx("option",{value:"unidentified",children:"Unidentified session"})]})]})]}),e.jsx("div",{className:"tr-page",id:"tr-panel-monitor",role:"tabpanel","aria-labelledby":"tr-tab-monitor",hidden:c!=="monitor",children:r?e.jsx(Be,{...r,radar:t&&s?{...s,status:"unavailable"}:s,sessionFilter:f,sessions:m}):e.jsx("p",{className:"tr-empty",children:"Reading monitor status…"})}),e.jsx("div",{className:"tr-page",id:"tr-panel-configuration",role:"tabpanel","aria-labelledby":"tr-tab-configuration",hidden:c!=="configuration",children:s?e.jsx(He,{snapshot:s,stale:t,sessionLabel:f==="all"?void 0:N.get(f)??f}):e.jsx("p",{className:"tr-empty",children:t?"Could not read settings. Sampling will retry.":"Reading local settings…"})}),e.jsx("div",{className:"tr-page",id:"tr-panel-file",role:"tabpanel","aria-labelledby":"tr-tab-file",hidden:c!=="file",children:e.jsx(We,{onSessions:u,sessionFilter:f})})]})}function He({snapshot:s,sessionLabel:t,stale:r=!1}){var u,f;const m=((u=s.config)==null?void 0:u.variables)??[],c=m.filter(i=>i.key.endsWith("ENDPOINT")),g=m.filter(i=>i.key.startsWith("OTEL_LOG_")||i.key.includes("ENABLE")||i.key.includes("TELEMETRY_BETA")),h={CLAUDE_CODE_ENABLE_TELEMETRY:"Telemetry",OTEL_LOG_USER_PROMPTS:"Your prompts",OTEL_LOG_ASSISTANT_RESPONSES:"Claude responses",OTEL_LOG_TOOL_DETAILS:"Tool details",OTEL_LOG_TOOL_CONTENT:"Tool contents",OTEL_LOG_RAW_API_BODIES:"Full API bodies",OTEL_LOG_MANAGED_SETTINGS:"Managed settings",CLAUDE_CODE_ENHANCED_TELEMETRY_BETA:"Enhanced telemetry",ENABLE_ENHANCED_TELEMETRY_BETA:"Enhanced telemetry"};return e.jsxs("section",{className:"tr-config","aria-label":"Telemetry configuration",children:[e.jsx("h3",{children:"What Claude is configured to send"}),r&&e.jsx("p",{className:"tr-warning",role:"status",children:"Last successfully read settings. Current configuration could not be verified."}),e.jsx("p",{className:"tr-note",children:"Read from local Claude settings. The effective environment of a running session is not verified."}),t&&e.jsxs("p",{className:"tr-note",children:["Selected session: ",t,". These shared settings are evidence; this session’s runtime values are unverified."]}),e.jsx("h4",{children:"Destinations"}),c.length?e.jsx("ul",{className:"tr-destinations",children:c.map(i=>e.jsxs("li",{children:[e.jsx("code",{children:i.value}),e.jsxs("span",{children:[i.source," · ",i.key]})]},`${i.source}:${i.key}`))}):e.jsx("p",{className:"tr-note",children:"No destination found in the sources read. This does not mean telemetry is off."}),!c.length&&s.connections.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("h4",{children:"Observed Claude connections"}),e.jsx("ul",{className:"tr-live-connections",children:s.connections.map(i=>e.jsxs("li",{children:[e.jsx("code",{children:i.destination}),e.jsxs("span",{children:[r||i.active===null?"Visibility unavailable":i.active?"Active connection":"Previously observed"," · PID ",i.pid," · ",i.workspace??"Workspace unknown"]})]},`${i.pid}:${i.destination}`))}),e.jsx("p",{className:"tr-note",children:"A running process can keep previously loaded settings after a settings file disappears. Connections alone do not identify telemetry or prove a message was sent."})]}),e.jsx("h4",{children:"Telemetry and content"}),g.length?e.jsx("dl",{className:"tr-variables",children:g.map(i=>e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("strong",{children:h[i.key]??i.key}),e.jsx("code",{children:i.key}),e.jsx("span",{children:i.source})]}),e.jsxs("dd",{children:[e.jsx("span",{className:`tr-state ${i.value.startsWith("Enabled")?"tr-tone-enabled":"tr-tone-unknown"}`,children:i.value}),i.rawValue!==void 0&&e.jsx("code",{className:"tr-raw-flag",children:i.rawValue})]})]},`${i.source}:${i.key}`))}):e.jsx("p",{className:"tr-note",children:"No telemetry flags found in the settings read."}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All variables and sources"}),e.jsx("dl",{className:"tr-variables",children:m.map(i=>e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("code",{children:i.key}),e.jsx("span",{children:i.source})]}),e.jsx("dd",{children:i.value})]},`${i.source}:${i.key}`))}),e.jsx("ul",{className:"tr-sources",children:(f=s.config)==null?void 0:f.sources.map(i=>e.jsxs("li",{children:[e.jsx("span",{children:i.name}),e.jsx("span",{children:i.status==="read"?"Read":i.status==="absent"?"Not found":"Unavailable"})]},i.name))}),e.jsx("p",{className:"tr-note",children:"Endpoint credentials and headers are hidden. Settings can differ from an already running process."})]})]})}export{He as Configuration,Ge as TrafficRadarView,qe as default};
@@ -1 +1 @@
1
- import{a as O,f as p,r as i,s as Z,p as _,j as a,b as g,c as I,u as ee,d as se,e as ae,g as te,h as E,i as ne,k as oe}from"./index-BKLXyjIh.js";function le(s){return!s.answer||s.answer.range!==s.want?{phase:"busy",stale:!1}:s.answer.ok?{phase:s.answer.days>0?"chart":"empty",stale:s.loading}:{phase:"error",stale:s.loading}}function q(s){const e=new Map;for(const n of s)for(const o of n.agents??[]){if(!o||typeof o.agent!="string"||o.agent==="")continue;const r=e.get(o.agent)??{id:o.agent,cost:0,tokens:0};r.cost+=Number.isFinite(o.totalCost)?o.totalCost:0,r.tokens+=Number.isFinite(o.totalTokens)?o.totalTokens:0,e.set(o.agent,r)}return[...e.values()].sort((n,o)=>o.cost-n.cost||n.id.localeCompare(o.id))}function re(s){const e=q([{agents:s}]);return e.length<2?null:e.map(n=>`${O(n.id)} ${p(n.cost)}`).join(" · ")}function F(s){switch(s.toLowerCase()){case"claude":return"var(--model-opus)";case"codex":return"var(--model-codex)";case"gemini":return"var(--model-gemini)";case"copilot":return"var(--model-haiku)";default:return"var(--model-other)"}}function ce(s,e){if(!(e>0)||!(s>0))return"0%";const n=s/e*100;return n<.1?"<0.1%":`${n.toFixed(1)}%`}const u=s=>typeof s=="number"&&Number.isFinite(s)?s:0,$=s=>typeof s=="string"?s:"",U=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function ie(s){const e=s??{};return{modelName:$(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ue(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:$(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:$(e.period),totalCost:u(e.totalCost),totalTokens:u(e.totalTokens),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens),modelsUsed:U(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(ie):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:U(e.metadata.agents)}}:{}}}function de(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ue):void 0}}function C(s){const e=s.toLowerCase();return e.includes("opus")?"var(--model-opus)":e.includes("sonnet")?"var(--model-sonnet)":e.includes("haiku")?"var(--model-haiku)":e.includes("gpt-5")||e.includes("gpt5")?"var(--model-gpt5)":e.includes("gpt")?"var(--model-gpt)":e.includes("gemini")?"var(--model-gemini)":e.includes("codex")?"var(--model-codex)":e.includes("fable")||e.includes("mythos")?"var(--model-fable)":"var(--model-other)"}function he(s){let e=0,n=0,o=0,r=0;const m=new Map;for(const l of s){e+=l.totalCost,n+=l.totalTokens,o+=l.inputTokens+l.outputTokens,r+=l.cacheReadTokens;for(const d of l.modelBreakdowns)m.set(d.modelName,(m.get(d.modelName)??0)+d.cost)}return{totalCost:e,totalTok:n,inOut:o,cacheRead:r,modelCosts:m}}function me(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function G(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function v(s,e){return e>0?s/e*100:0}function pe(s){var n,o;return re(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function ge(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function fe(s){const[e,n]=i.useState(null),[o,r]=i.useState(!1),m=i.useState(ge)[0],l=i.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const j=s;r(!0);const h=`/api/ccusage?since=${_(j)}${f?"&refresh=1":""}`;fetch(h).then(c=>c.json()).then(c=>{x()&&n({range:j,resp:de(c)})}).catch(()=>{x()&&n({range:j,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return i.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function xe({selectedDay:s}){const e=pe(s);return a.jsxs("div",{className:"uh-detail",children:[a.jsxs("div",{className:"uh-detail-head",children:[a.jsx("span",{className:"uh-detail-date",children:s.period}),a.jsx("span",{className:"uh-detail-cost",children:p(s.totalCost)}),e&&a.jsx("span",{className:"uh-detail-agents",title:e,children:e})]}),a.jsxs("div",{className:"uh-detail-mini",children:[a.jsx(b,{label:"input",val:g(s.inputTokens)}),a.jsx(b,{label:"output",val:g(s.outputTokens)}),a.jsx(b,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(b,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:G(s.modelBreakdowns).map(n=>{const o=v(n.cost,s.totalCost);return a.jsxs("div",{className:"uh-model-row",title:n.modelName,children:[a.jsxs("span",{className:"uh-model-name",children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:I(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:C(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function b({label:s,val:e}){return a.jsxs("div",{className:"uh-ministat",children:[a.jsx("span",{className:"uh-ministat-val",children:e}),a.jsx("span",{className:"uh-ministat-label",children:s})]})}const je=[7,14,30,90];function Ne({onClose:s,providers:e}){var P;const[n,o]=i.useState(30),[r,m]=i.useState(null),{landed:l,loading:d,reload:f}=fe(n),x=i.useRef(null),j=ee(s,{focusRef:x}),S=se(s),h=le({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((P=l.resp.days)==null?void 0:P.length)??0}}),c=h.phase==="chart"?l.resp.days??[]:[],M=i.useRef(null),H=ae(h.phase!=="error",M),V=i.useMemo(()=>c.reduce((t,k)=>Math.max(t,k.totalCost),0),[c]),A=i.useRef(null);i.useLayoutEffect(()=>{const t=A.current;t&&(t.scrollLeft=t.scrollWidth)},[c]);const{totalCost:T,totalTok:W,inOut:z,cacheRead:D,modelCosts:L}=i.useMemo(()=>he(c),[c]),J=i.useMemo(()=>me(L),[L]),N=i.useMemo(()=>q(c),[c]),K=N.length>1,B=r?c.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",...S,role:"presentation",children:a.jsxs("div",{ref:j,className:"modal uh-modal",onClick:t=>t.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":"Usage history",children:[a.jsxs("header",{className:"uh-head",children:[a.jsxs("div",{className:"uh-titlewrap",children:[a.jsx("div",{className:"uh-title",children:"Usage history"}),a.jsx("div",{className:"uh-sub",children:h.stale?"running ccusage…":te(e,N.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:je.map(t=>a.jsxs("button",{type:"button","aria-pressed":n===t,className:"uh-range-btn",onClick:()=>{o(t),m(null)},children:[t,"d"]},t))}),a.jsx("button",{ref:M,className:"glyph-btn uh-reload",onClick:f,...E(d),title:"Re-run ccusage","aria-label":"Reload",children:d?"…":"↻"}),a.jsx("button",{ref:x,className:"glyph-btn uh-close",onClick:s,"aria-label":"Close",children:"×"})]}),h.phase==="busy"?a.jsxs("div",{className:"uh-status uh-status-wait","aria-busy":"true",children:[a.jsx("span",{className:"uh-wait-word",children:l?"running ccusage…":"running ccusage… (first run downloads the package)"}),a.jsx("span",{className:"uh-wait-axis","aria-hidden":!0})]}):h.phase==="error"?a.jsxs("div",{className:"uh-status uh-err",title:oe(l.resp)||void 0,children:[ne(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{H(),f()},...E(d),children:d?"trying…":"Try again"})})]}):h.phase==="empty"?a.jsx("div",{className:`uh-status${h.stale?" uh-stale":""}`,"aria-busy":h.stale||void 0,children:"no usage in this range"}):a.jsxs("div",{className:h.stale?"uh-stale":void 0,"aria-busy":h.stale||void 0,children:[a.jsxs("div",{className:"uh-totals",children:[a.jsx(y,{label:"total cost",val:p(T),accent:!0}),a.jsx(y,{label:"tokens",val:g(W)}),a.jsx(y,{label:"input+output",val:g(z)}),a.jsx(y,{label:"cache reads",val:g(D)})]}),K&&a.jsxs("div",{className:"uh-agents",role:"group","aria-label":"Cost by CLI",children:[a.jsx("div",{className:"uh-agent-bar","aria-hidden":"true",children:N.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${v(t.cost,T)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:N.map(t=>a.jsxs("span",{className:"uh-agent-key",title:`${t.id} · ${g(t.tokens)} tokens`,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:F(t.id)}}),O(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:ce(t.cost,T)})]},t.id))})]}),a.jsx("div",{ref:A,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:c.map((t,k)=>{const Q=v(t.totalCost,V),w=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${w?" sel":""}`,onClick:()=>m(w?null:t.period),"aria-pressed":w,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/c.length}%`,"--uh-i":k,"--uh-step":`${Math.min(10,200/Math.max(c.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:G(t.modelBreakdowns).map(R=>{const Y=v(R.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:C(R.modelName)}},R.modelName)})}),a.jsx("span",{className:"uh-bar-label",children:t.period.slice(5)})]},t.period)})}),a.jsx("div",{className:"uh-legend",children:J.map(([t,k])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(t)}}),I(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(k)})]},t))}),B&&a.jsx(xe,{selectedDay:B})]})]})})}function y({label:s,val:e,accent:n}){return a.jsxs("div",{className:"uh-stat",children:[a.jsx("span",{className:`uh-stat-val${n?" accent":""}`,children:e}),a.jsx("span",{className:"uh-stat-label",children:s})]})}export{Ne as default};
1
+ import{a as O,f as p,r as i,s as Z,p as _,j as a,b as g,c as I,u as ee,d as se,e as ae,g as te,h as E,i as ne,k as oe}from"./index-DGMP4S3t.js";function le(s){return!s.answer||s.answer.range!==s.want?{phase:"busy",stale:!1}:s.answer.ok?{phase:s.answer.days>0?"chart":"empty",stale:s.loading}:{phase:"error",stale:s.loading}}function q(s){const e=new Map;for(const n of s)for(const o of n.agents??[]){if(!o||typeof o.agent!="string"||o.agent==="")continue;const r=e.get(o.agent)??{id:o.agent,cost:0,tokens:0};r.cost+=Number.isFinite(o.totalCost)?o.totalCost:0,r.tokens+=Number.isFinite(o.totalTokens)?o.totalTokens:0,e.set(o.agent,r)}return[...e.values()].sort((n,o)=>o.cost-n.cost||n.id.localeCompare(o.id))}function re(s){const e=q([{agents:s}]);return e.length<2?null:e.map(n=>`${O(n.id)} ${p(n.cost)}`).join(" · ")}function F(s){switch(s.toLowerCase()){case"claude":return"var(--model-opus)";case"codex":return"var(--model-codex)";case"gemini":return"var(--model-gemini)";case"copilot":return"var(--model-haiku)";default:return"var(--model-other)"}}function ce(s,e){if(!(e>0)||!(s>0))return"0%";const n=s/e*100;return n<.1?"<0.1%":`${n.toFixed(1)}%`}const u=s=>typeof s=="number"&&Number.isFinite(s)?s:0,$=s=>typeof s=="string"?s:"",U=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function ie(s){const e=s??{};return{modelName:$(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ue(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:$(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:$(e.period),totalCost:u(e.totalCost),totalTokens:u(e.totalTokens),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens),modelsUsed:U(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(ie):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:U(e.metadata.agents)}}:{}}}function de(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ue):void 0}}function C(s){const e=s.toLowerCase();return e.includes("opus")?"var(--model-opus)":e.includes("sonnet")?"var(--model-sonnet)":e.includes("haiku")?"var(--model-haiku)":e.includes("gpt-5")||e.includes("gpt5")?"var(--model-gpt5)":e.includes("gpt")?"var(--model-gpt)":e.includes("gemini")?"var(--model-gemini)":e.includes("codex")?"var(--model-codex)":e.includes("fable")||e.includes("mythos")?"var(--model-fable)":"var(--model-other)"}function he(s){let e=0,n=0,o=0,r=0;const m=new Map;for(const l of s){e+=l.totalCost,n+=l.totalTokens,o+=l.inputTokens+l.outputTokens,r+=l.cacheReadTokens;for(const d of l.modelBreakdowns)m.set(d.modelName,(m.get(d.modelName)??0)+d.cost)}return{totalCost:e,totalTok:n,inOut:o,cacheRead:r,modelCosts:m}}function me(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function G(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function v(s,e){return e>0?s/e*100:0}function pe(s){var n,o;return re(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function ge(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function fe(s){const[e,n]=i.useState(null),[o,r]=i.useState(!1),m=i.useState(ge)[0],l=i.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const j=s;r(!0);const h=`/api/ccusage?since=${_(j)}${f?"&refresh=1":""}`;fetch(h).then(c=>c.json()).then(c=>{x()&&n({range:j,resp:de(c)})}).catch(()=>{x()&&n({range:j,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return i.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function xe({selectedDay:s}){const e=pe(s);return a.jsxs("div",{className:"uh-detail",children:[a.jsxs("div",{className:"uh-detail-head",children:[a.jsx("span",{className:"uh-detail-date",children:s.period}),a.jsx("span",{className:"uh-detail-cost",children:p(s.totalCost)}),e&&a.jsx("span",{className:"uh-detail-agents",title:e,children:e})]}),a.jsxs("div",{className:"uh-detail-mini",children:[a.jsx(b,{label:"input",val:g(s.inputTokens)}),a.jsx(b,{label:"output",val:g(s.outputTokens)}),a.jsx(b,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(b,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:G(s.modelBreakdowns).map(n=>{const o=v(n.cost,s.totalCost);return a.jsxs("div",{className:"uh-model-row",title:n.modelName,children:[a.jsxs("span",{className:"uh-model-name",children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:I(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:C(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function b({label:s,val:e}){return a.jsxs("div",{className:"uh-ministat",children:[a.jsx("span",{className:"uh-ministat-val",children:e}),a.jsx("span",{className:"uh-ministat-label",children:s})]})}const je=[7,14,30,90];function Ne({onClose:s,providers:e}){var P;const[n,o]=i.useState(30),[r,m]=i.useState(null),{landed:l,loading:d,reload:f}=fe(n),x=i.useRef(null),j=ee(s,{focusRef:x}),S=se(s),h=le({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((P=l.resp.days)==null?void 0:P.length)??0}}),c=h.phase==="chart"?l.resp.days??[]:[],M=i.useRef(null),H=ae(h.phase!=="error",M),V=i.useMemo(()=>c.reduce((t,k)=>Math.max(t,k.totalCost),0),[c]),A=i.useRef(null);i.useLayoutEffect(()=>{const t=A.current;t&&(t.scrollLeft=t.scrollWidth)},[c]);const{totalCost:T,totalTok:W,inOut:z,cacheRead:D,modelCosts:L}=i.useMemo(()=>he(c),[c]),J=i.useMemo(()=>me(L),[L]),N=i.useMemo(()=>q(c),[c]),K=N.length>1,B=r?c.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",...S,role:"presentation",children:a.jsxs("div",{ref:j,className:"modal uh-modal",onClick:t=>t.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":"Usage history",children:[a.jsxs("header",{className:"uh-head",children:[a.jsxs("div",{className:"uh-titlewrap",children:[a.jsx("div",{className:"uh-title",children:"Usage history"}),a.jsx("div",{className:"uh-sub",children:h.stale?"running ccusage…":te(e,N.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:je.map(t=>a.jsxs("button",{type:"button","aria-pressed":n===t,className:"uh-range-btn",onClick:()=>{o(t),m(null)},children:[t,"d"]},t))}),a.jsx("button",{ref:M,className:"glyph-btn uh-reload",onClick:f,...E(d),title:"Re-run ccusage","aria-label":"Reload",children:d?"…":"↻"}),a.jsx("button",{ref:x,className:"glyph-btn uh-close",onClick:s,"aria-label":"Close",children:"×"})]}),h.phase==="busy"?a.jsxs("div",{className:"uh-status uh-status-wait","aria-busy":"true",children:[a.jsx("span",{className:"uh-wait-word",children:l?"running ccusage…":"running ccusage… (first run downloads the package)"}),a.jsx("span",{className:"uh-wait-axis","aria-hidden":!0})]}):h.phase==="error"?a.jsxs("div",{className:"uh-status uh-err",title:oe(l.resp)||void 0,children:[ne(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{H(),f()},...E(d),children:d?"trying…":"Try again"})})]}):h.phase==="empty"?a.jsx("div",{className:`uh-status${h.stale?" uh-stale":""}`,"aria-busy":h.stale||void 0,children:"no usage in this range"}):a.jsxs("div",{className:h.stale?"uh-stale":void 0,"aria-busy":h.stale||void 0,children:[a.jsxs("div",{className:"uh-totals",children:[a.jsx(y,{label:"total cost",val:p(T),accent:!0}),a.jsx(y,{label:"tokens",val:g(W)}),a.jsx(y,{label:"input+output",val:g(z)}),a.jsx(y,{label:"cache reads",val:g(D)})]}),K&&a.jsxs("div",{className:"uh-agents",role:"group","aria-label":"Cost by CLI",children:[a.jsx("div",{className:"uh-agent-bar","aria-hidden":"true",children:N.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${v(t.cost,T)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:N.map(t=>a.jsxs("span",{className:"uh-agent-key",title:`${t.id} · ${g(t.tokens)} tokens`,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:F(t.id)}}),O(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:ce(t.cost,T)})]},t.id))})]}),a.jsx("div",{ref:A,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:c.map((t,k)=>{const Q=v(t.totalCost,V),w=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${w?" sel":""}`,onClick:()=>m(w?null:t.period),"aria-pressed":w,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/c.length}%`,"--uh-i":k,"--uh-step":`${Math.min(10,200/Math.max(c.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:G(t.modelBreakdowns).map(R=>{const Y=v(R.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:C(R.modelName)}},R.modelName)})}),a.jsx("span",{className:"uh-bar-label",children:t.period.slice(5)})]},t.period)})}),a.jsx("div",{className:"uh-legend",children:J.map(([t,k])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(t)}}),I(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(k)})]},t))}),B&&a.jsx(xe,{selectedDay:B})]})]})})}function y({label:s,val:e,accent:n}){return a.jsxs("div",{className:"uh-stat",children:[a.jsx("span",{className:`uh-stat-val${n?" accent":""}`,children:e}),a.jsx("span",{className:"uh-stat-label",children:s})]})}export{Ne as default};