ccdeck 3.38.3 → 3.38.4

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
@@ -130,7 +130,7 @@ Step by step: [Install ccdeck with npx and see your first session](https://ccdec
130
130
 
131
131
  ### Desktop app
132
132
 
133
- The same deck as an app: it starts the deck itself and puts an icon in the menu bar (the tray on Windows and Linux) that counts the Claude Code sessions stopped on a permission prompt or a question — the number sits beside the icon on macOS, and in the icon's tooltip and menu on Windows and Linux. With **Notifications while closed** ticked in that menu, which starts off, it also sends a notification while the window is closed wherever an open page would have played a sound, with the deck's own tone on macOS. It needs no Node.js. The deck it starts is given whichever of the variables in the environment table further down your login shell sets — `AGENTS_DECK_NO_REPORTS` and `CLAUDE_CONFIG_DIR` among them — however the app was opened, as a deck started from a terminal would be. If a deck from `npx ccdeck` is already running, the app uses that one rather than starting a second, and replaces it only when it is older than the one the app carries.
133
+ The same deck as an app: it starts the deck itself and puts an icon in the menu bar (the tray on Windows and Linux) that counts the Claude Code sessions stopped on a permission prompt or a question — the number sits beside the icon on macOS, and in the icon's tooltip and menu on Windows and Linux. With **Notifications while closed** ticked in that menu, which starts off, it also sends a notification while the window is closed wherever an open page would have played a sound, with the deck's own tone on macOS. The app also shows the [account notifications](#accounts), silently and whether its window is open or closed. It needs no Node.js. The deck it starts is given whichever of the variables in the environment table further down your login shell sets — `AGENTS_DECK_NO_REPORTS` and `CLAUDE_CONFIG_DIR` among them — however the app was opened, as a deck started from a terminal would be. If a deck from `npx ccdeck` is already running, the app uses that one rather than starting a second, and replaces it only when it is older than the one the app carries.
134
134
 
135
135
  | System | Download |
136
136
  | --- | --- |
@@ -203,13 +203,13 @@ Each one fires the bundled `hook.js`, which POSTs the event JSON to the running
203
203
 
204
204
  **OpenAI Codex** — Codex CLI hooks do not fire reliably on Windows, so nothing is installed at all. The server tails Codex's own rollout files at `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` and reconstructs the equivalent stream — session start, prompts, tool calls, token usage, model. No hook install, no trust prompt. Set `CODEX_HOME` to override the path.
205
205
 
206
- Quota is the one thing that is not just reading. It needs a live token, so when the one in `~/.codex/auth.json` is within 90 seconds of expiring the deck refreshes it exactly as the CLI does and writes the rotated credential back — one refresh at a time, re-reading the file inside the lock, and atomically, because OpenAI's refresh tokens are single-use and a rotation that never reaches disk costs you a `codex login`. It happens only while the page is open, and nothing else in `auth.json` is touched.
206
+ Quota is the one thing that is not just reading. It needs a live token, so when the one in `~/.codex/auth.json` is within 90 seconds of expiring the deck refreshes it exactly as the CLI does and writes the rotated credential back — one refresh at a time, re-reading the file inside the lock, and atomically, because OpenAI's refresh tokens are single-use and a rotation that never reaches disk costs you a `codex login`. It happens only while the page is open or a quota notification is on (see [Accounts](#accounts)), and nothing else in `auth.json` is touched.
207
207
 
208
208
  ## What it touches
209
209
 
210
210
  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.
211
211
 
212
- 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, 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.
212
+ 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.
213
213
 
214
214
  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.
215
215
 
@@ -233,6 +233,14 @@ The deck installs that package itself, so it installs it **bounded**: `claude-sw
233
233
 
234
234
  **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.
235
235
 
236
+ **Account notifications** are three switches in the sound settings (the speaker in the topbar), 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.
237
+
238
+ - **Account auto-switched**, on unless you turn it off. When Auto-switch moves Claude Code to another account, one notification says where to and, when the engine's own reading shows it, why: `Switched to work · personal@example.com reached 90%`, with an account's alias when it has one. A switch you make yourself is not announced.
239
+ - **Quota at 90% and 100%**, off until you turn it on. When a window of the Claude or Codex account you are on reaches 90%, and once more at 100%: Claude's 5-hour, 7-day and per-model windows, and every Codex window the Usage panel shows, the extra limits included — `5-hour usage reached 90% · resets in 2h 14m`. Only the account in use is watched, not every account you have stored. When reaching it is what makes Auto-switch move the account, the switch notification says it and this one is not sent, unless the switch notification is off.
240
+ - **Quota reset**, off until you turn it on. When one of those windows that had reached 90% starts over: `5-hour window available again`. A window that never got that high resets without a word.
241
+
242
+ Each is said once per window. The deck writes what it has said to `account-notify.json`, beside its settings, so a restart does not say it again, and nothing said about one account counts for another. With either quota switch on, the deck reads the quota of the accounts in use about every two minutes even while no page is open — the same reads, through the same caches and limits, as the Usage panel's. `AGENTS_DECK_NO_NOTIFY=1` keeps all three off.
243
+
236
244
  **`+` → Sign in** runs `claude auth login`, shows you the link, and hands the result to `cswap add`. The sign-in usually completes by itself once you approve it in the browser; only when the page shows a code — as it does on a deck opened from another machine — is there one to paste back into the dialog. The account you were using **stays active** — signing in replaces the live credentials, so the previous one is switched back the moment the new one is recorded. A pasted code goes straight into the CLI's stdin on this machine; it is never stored, logged, or sent anywhere else.
237
245
 
238
246
  **`share`** on an account produces a `ccdeck2:…` blob to paste into another deck's **`+` → Paste a share**.
@@ -418,7 +426,7 @@ Environment:
418
426
  | `AGENTS_DECK_NO_UPDATE_CHECK=1` | Don't ask npm about releases, but keep everything else |
419
427
  | `AGENTS_DECK_NO_STATUS=1` | Don't read Anthropic's and OpenAI's status pages, so no incident chip appears |
420
428
  | `AGENTS_DECK_NO_FRESHEN=1` | Never nudge claude-swap to collect usage early |
421
- | `AGENTS_DECK_NO_NOTIFY=1` | Never raise a desktop notification: not when a session blocks and no page is open, and not when Browser Watch finds something |
429
+ | `AGENTS_DECK_NO_NOTIFY=1` | Never raise a desktop notification: not when a session blocks and no page is open, not when Browser Watch finds something, and not about your accounts |
422
430
  | `AGENTS_DECK_NO_LAN=1` | Keep **Local network** off, whatever its switch in the panel says |
423
431
  | `AGENTS_DECK_NO_REPORTS=1` | Never send usage reports |
424
432
  | `AGENTS_DECK_CSWAP` | Full path to `cswap`, when it lives somewhere unusual |
@@ -1 +1 @@
1
- import{j as e,r as l,l as _,m as V,n as Y,o as z,s as X,u as Z,d as ee,h as se}from"./index-ClJbck-M.js";function te({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(n=>e.jsxs("div",{className:`bw-log-line ${n.level}${n.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(n.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),n.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:n.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:n.parts.profile,children:n.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[n.parts.value,n.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",n.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:n.text})]},`${n.atMs}-${n.level}-${n.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function ne(s){const n=o=>new Date(o).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?n(s.startMs):`${n(s.startMs)} → ${n(s.endMs)}`}function ae(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const n=Math.round((s.endMs-s.startMs)/6e4);return n<1?"":`${n} min`}function re({snap:s,dismiss:n,refreshRef:o}){const[i,d]=l.useState(null),u=l.useRef(new Map),t=l.useRef(null);l.useEffect(()=>{var r,w;const c=t.current;!c||s.episodes.some(b=>E(b)===c.id)||(t.current=null,_(((r=document.activeElement)==null?void 0:r.tagName)??null)&&((w=(c.next?u.current.get(c.next):void 0)??o.current)==null||w.focus()))},[s]);const a=c=>{t.current={id:E(c),next:V(s.episodes.map(E),E(c))},n(c)},h=l.useMemo(()=>{const c=[];for(const r of s.episodes){const w=ae(r.startMs),b=c[c.length-1];b&&b.label===w?b.episodes.push(r):c.push({label:w,episodes:[r]})}return c},[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:h.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."})]}):h.map(c=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:c.label}),c.episodes.map(r=>{const w=E(r),b=i===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:f=>{f?u.current.set(w,f):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:r.host}),e.jsxs("span",{className:"bw-ep-meta",children:[ne(r),U(r)&&e.jsxs(e.Fragment,{children:[" · ",U(r)]}),r.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:[r.count," ",r.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>a(r),"aria-label":`Dismiss ${r.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),b&&e.jsx("ul",{className:"bw-urls",children:r.urls.map((f,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(f.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:f.url})]},`${f.url}-${f.timeMs}-${y}`))})]},w)})]},c.label))})]})}function ie(s){const n=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!n)return!1;const o=parseInt(n[1],16),i=[o>>16&255,o>>8&255,o&255].map(d=>{const u=d/255;return u<=.03928?u/12.92:Math.pow((u+.055)/1.055,2.4)});return .2126*i[0]+.7152*i[1]+.0722*i[2]>.5}const G=1e4,oe=12e3;function le({browsers:s,findings:n,watching:o,palette:i}){const d=l.useRef(null),u=l.useRef(null);return u.current={browsers:s,findings:n,watching:o},l.useEffect(()=>{var b;const t=d.current;if(!t)return;const a=t.getContext("2d");if(!a)return;const h=((b=window.matchMedia)==null?void 0:b.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let c=0,r=!1;const w=()=>{var q;if(r)return;const f=u.current;if(!f)return;const{browsers:y,findings:j,watching:C}=f,S=i["--accent"]||"#7dd3fc",g=i["--line"]||"#1f2229",v=i["--text-dim"]||"#6e727c",P=ie(i["--bg"]),N=P?42:26,k=P?.85:.65,A=Date.now(),R=Math.min(window.devicePixelRatio||1,2),$=t.clientWidth,W=t.clientHeight;(t.width!==$*R||t.height!==W*R)&&(t.width=$*R,t.height=W*R),a.setTransform(R,0,0,R,0,0),a.clearRect(0,0,$,W);const m=$/2,x=W/2,M=Math.min($,W)/2-6;a.strokeStyle=g,a.lineWidth=1,a.globalAlpha=P?1:.9;for(const p of[.4,.7,1])a.beginPath(),a.arc(m,x,M*p,0,Math.PI*2),a.stroke();a.beginPath(),a.moveTo(m-M,x),a.lineTo(m+M,x),a.moveTo(m,x-M),a.lineTo(m,x+M),a.stroke(),a.globalAlpha=1;const T=(C&&!h?A%G/G:0)*Math.PI*2-Math.PI/2;if(C){const p=(q=a.createConicGradient)==null?void 0:q.call(a,T-.85,m,x);p&&(p.addColorStop(0,"transparent"),p.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${S} ${N}%, transparent)`),p.addColorStop(1,"transparent"),a.fillStyle=p,a.beginPath(),a.moveTo(m,x),a.arc(m,x,M,T-.85,T),a.closePath(),a.fill()),a.strokeStyle=S,a.globalAlpha=k,a.beginPath(),a.moveTo(m,x),a.lineTo(m+Math.cos(T)*M,x+Math.sin(T)*M),a.stroke(),a.globalAlpha=1}y.forEach((p,I)=>{const B=I/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=p.lastReadMs===null?1:Math.min(1,(A-p.lastReadMs)/12e4),O=M*(.42+D*.5),F=m+Math.cos(B)*O,L=x+Math.sin(B)*O,Q=Math.abs((T-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=C?Math.max(0,1-Q/.55):0;a.beginPath(),a.arc(F,L,3+K*1.6,0,Math.PI*2),a.fillStyle=p.running?S:v,a.globalAlpha=p.running?.55+K*.45:.35,a.fill(),a.globalAlpha=1});for(const p of j){const I=(A-p.atMs)/oe;if(I<0||I>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===p.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;a.beginPath(),a.arc(O,F,4+I*22,0,Math.PI*2),a.strokeStyle=S,a.globalAlpha=(1-I)*.8,a.lineWidth=1.5,a.stroke(),a.globalAlpha=1}c=requestAnimationFrame(w)};return w(),()=>{r=!0,cancelAnimationFrame(c)}},[i]),e.jsx("canvas",{ref:d,className:"bw-radar",role:"img","aria-label":o?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function ce(s,n,o){if(s===null)return null;const i=s+n-o;return i>0?i:null}function de(s){const n=Math.ceil(s/1e3),o=Math.floor(n/60),i=n%60;return o>0?`${o}m ${i}s`:`${i}s`}function J(s){return(s??[]).filter(n=>n.installed&&n.profiles>0)}function he(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function ue(s,n){return s.map(o=>({key:o.key,name:o.name,running:o.running,visits:n.filter(i=>i.browser===o.key).reduce((i,d)=>i+d.programVisits,0)}))}function we(s,n){if(s===null)return null;const o=Math.max(0,Math.round((n-s)/1e3));if(o<5)return"just now";if(o<60)return`${o} sec ago`;const i=Math.round(o/60);return i<60?`${i} min ago`:`${Math.round(i/60)} hr ago`}function me(s){const n=s.profiles.filter(i=>i.degraded);if(n.length>0){const i=n[0],d=n.length-1;return{kind:"unreadable",text:`${i.name}/${i.profile} could not be read — ${i.reason??"no reason given"}`+(d>0?`, and ${d} more`:"")+". The watch continues on the profiles it can read."}}const o=J(s.browsers);return o.length===0?{kind:"no-profiles",text:"No browser on this machine has a profile to read. Open one of the browsers below once and it will be watched from then on."}:o.some(i=>i.running)?null:{kind:"none-running",text:"No watched browser is running, so nothing can be navigated right now. Their history is still read, and anything found while they were open is still here."}}function be({snap:s,watching:n,palette:o}){const[i,d]=l.useState(!1),[u,t]=l.useState(()=>Date.now());l.useEffect(()=>{const h=setInterval(()=>t(Date.now()),1e3);return()=>clearInterval(h)},[]);const a=ce(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(h=>!h),"aria-expanded":i,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(le,{browsers:n.map(h=>{var c;return{key:h.key,name:h.name,running:h.running,lastReadMs:((c=s.profiles.find(r=>r.browser===h.key))==null?void 0:c.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(h=>({browser:h.browser??null,atMs:h.endMs})),watching:s.settings.enabled,palette:o}),e.jsx("ul",{className:"bw-counts",children:ue(n,s.profiles).map(h=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:h.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:h.name})]},h.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 ",we(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"]})]}),a!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",de(a)]}),i&&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 fe({watching:s,rest:n}){const[o,i]=l.useState(!1),[d,u]=l.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:()=>i(t=>!t),"aria-expanded":o,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),o&&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))}),n.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?"▾":"▸"})," ",n.length," not watched"]}),d&&e.jsxs("p",{className:"bw-rest",children:[n.map(t=>t.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function ge({snap:s,quiet:n,setQuiet:o,save:i,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:n??s.settings.quietMinutes,onChange:t=>{o(Number(t.target.value)),i({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 i({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"?` (${he(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,n){return n?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function pe({snap:s,saving:n,save:o,why:i,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,n).word},H(s,n).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${i?" on":""}`,onClick:()=>d(u=>!u),"aria-expanded":i,"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 o({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 xe({relay:s}){const[n,o]=l.useState(!1),[i,d]=l.useState(!1),u=s.killswitch.blocked?"unblock":"block",t=s.command[u],a=l.useCallback(async()=>{await z(t.command)&&(d(!0),window.setTimeout(()=>d(!1),1600))},[t.command]),h=s.profiles.filter(r=>{var w;return(w=r.report)==null?void 0:w.present}),c=s.profiles.filter(r=>r.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(r=>e.jsx("code",{className:"bw-path",children:r},r))]}),e.jsxs("button",{className:"bw-why",onClick:()=>o(r=>!r),"aria-expanded":n,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:n?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),n&&e.jsxs("div",{className:"bw-rc-body",children:[h.length>0&&e.jsx("dl",{className:"bw-key",children:h.map(r=>e.jsxs(l.Fragment,{children:[e.jsxs("dt",{children:[r.name,r.profile==="Default"?"":` · ${r.profile}`]}),e.jsxs("dd",{children:[r.report.enabled?"enabled":"installed but switched off",r.report.allUrls?", can act on every site":", limited to the sites it was granted",r.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",r.report.sensitiveApis.join(", ")]})]})]},`${r.browser}/${r.profile}`))}),c.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[c.map(r=>r.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."]}),h.some(r=>r.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:a,children:i?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:t.note})]})]})}function je(s){const[n,o]=l.useState(null),[i,d]=l.useState(null),[u,t]=l.useState(null),[a,h]=l.useState(!1),c=l.useRef(!1),[r,w]=l.useState(null),[b,f]=l.useState(!1),y=l.useRef(!1),j=l.useCallback(async g=>{var v,P;if(!(g&&!X(c.current))){g&&(c.current=!0,h(!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();o(k),y.current||w(((v=k==null?void 0:k.settings)==null?void 0:v.quietMinutes)??15),s(((P=k==null?void 0:k.settings)==null?void 0:P.enabled)===!0),d(null)}catch(N){d(N instanceof Error?N.message:String(N))}finally{g&&(c.current=!1,h(!1))}}},[s]);l.useEffect(()=>{j(!1)},[j]),l.useEffect(()=>{const g=setInterval(()=>{j(!1)},1e4);return()=>clearInterval(g)},[j]);const C=l.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=l.useCallback(async g=>{y.current=!0,f(!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,f(!1)}},[j]);return{snap:n,error:i,writeError:u,setWriteError:t,busy:a,quiet:r,setQuiet:w,saving:b,load:j,dismiss:C,save:S}}function Ne({onClose:s,onSeen:n,onWatching:o,palette:i}){const d=Z(s),u=ee(s),{snap:t,error:a,writeError:h,setWriteError:c,busy:r,quiet:w,setQuiet:b,saving:f,load:y,dismiss:j,save:C}=je(o),[S,g]=l.useState(!1),[v,P]=l.useState(!1),N=l.useRef(null),k=l.useRef(n);k.current=n,l.useEffect(()=>()=>k.current(Date.now()),[]);const A=J(t==null?void 0:t.browsers),R=((t==null?void 0:t.browsers)??[]).filter(m=>!(m.installed&&m.profiles>0)),$=t?me(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),...se(r),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:r?"…":"↻"}),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&&!a&&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."})]}),a&&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:a})]})}),t&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),t&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(be,{snap:t,watching:A,palette:i}),e.jsx(fe,{watching:A,rest:R}),t.relay&&e.jsx(xe,{relay:t.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(re,{snap:t,dismiss:j,refreshRef:N}),e.jsx(te,{snap:t})]})]}),t&&S&&e.jsx(ge,{snap:t,quiet:w,setQuiet:b,save:C,access:v,setAccess:P}),h&&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:[h," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>c(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),t&&e.jsx(pe,{snap:t,saving:f,save:C,why:S,setWhy:g})]})]})})}export{Ne as default};
1
+ import{j as e,r as l,l as _,m as V,n as Y,o as z,s as X,u as Z,d as ee,h as se}from"./index-FnWpX0X5.js";function te({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(n=>e.jsxs("div",{className:`bw-log-line ${n.level}${n.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(n.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),n.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:n.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:n.parts.profile,children:n.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[n.parts.value,n.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",n.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:n.text})]},`${n.atMs}-${n.level}-${n.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function ne(s){const n=o=>new Date(o).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?n(s.startMs):`${n(s.startMs)} → ${n(s.endMs)}`}function ae(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const n=Math.round((s.endMs-s.startMs)/6e4);return n<1?"":`${n} min`}function re({snap:s,dismiss:n,refreshRef:o}){const[i,d]=l.useState(null),u=l.useRef(new Map),t=l.useRef(null);l.useEffect(()=>{var r,w;const c=t.current;!c||s.episodes.some(b=>E(b)===c.id)||(t.current=null,_(((r=document.activeElement)==null?void 0:r.tagName)??null)&&((w=(c.next?u.current.get(c.next):void 0)??o.current)==null||w.focus()))},[s]);const a=c=>{t.current={id:E(c),next:V(s.episodes.map(E),E(c))},n(c)},h=l.useMemo(()=>{const c=[];for(const r of s.episodes){const w=ae(r.startMs),b=c[c.length-1];b&&b.label===w?b.episodes.push(r):c.push({label:w,episodes:[r]})}return c},[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:h.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."})]}):h.map(c=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:c.label}),c.episodes.map(r=>{const w=E(r),b=i===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:f=>{f?u.current.set(w,f):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:r.host}),e.jsxs("span",{className:"bw-ep-meta",children:[ne(r),U(r)&&e.jsxs(e.Fragment,{children:[" · ",U(r)]}),r.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:[r.count," ",r.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>a(r),"aria-label":`Dismiss ${r.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),b&&e.jsx("ul",{className:"bw-urls",children:r.urls.map((f,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(f.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:f.url})]},`${f.url}-${f.timeMs}-${y}`))})]},w)})]},c.label))})]})}function ie(s){const n=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!n)return!1;const o=parseInt(n[1],16),i=[o>>16&255,o>>8&255,o&255].map(d=>{const u=d/255;return u<=.03928?u/12.92:Math.pow((u+.055)/1.055,2.4)});return .2126*i[0]+.7152*i[1]+.0722*i[2]>.5}const G=1e4,oe=12e3;function le({browsers:s,findings:n,watching:o,palette:i}){const d=l.useRef(null),u=l.useRef(null);return u.current={browsers:s,findings:n,watching:o},l.useEffect(()=>{var b;const t=d.current;if(!t)return;const a=t.getContext("2d");if(!a)return;const h=((b=window.matchMedia)==null?void 0:b.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let c=0,r=!1;const w=()=>{var q;if(r)return;const f=u.current;if(!f)return;const{browsers:y,findings:j,watching:C}=f,S=i["--accent"]||"#7dd3fc",g=i["--line"]||"#1f2229",v=i["--text-dim"]||"#6e727c",P=ie(i["--bg"]),N=P?42:26,k=P?.85:.65,A=Date.now(),R=Math.min(window.devicePixelRatio||1,2),$=t.clientWidth,W=t.clientHeight;(t.width!==$*R||t.height!==W*R)&&(t.width=$*R,t.height=W*R),a.setTransform(R,0,0,R,0,0),a.clearRect(0,0,$,W);const m=$/2,x=W/2,M=Math.min($,W)/2-6;a.strokeStyle=g,a.lineWidth=1,a.globalAlpha=P?1:.9;for(const p of[.4,.7,1])a.beginPath(),a.arc(m,x,M*p,0,Math.PI*2),a.stroke();a.beginPath(),a.moveTo(m-M,x),a.lineTo(m+M,x),a.moveTo(m,x-M),a.lineTo(m,x+M),a.stroke(),a.globalAlpha=1;const T=(C&&!h?A%G/G:0)*Math.PI*2-Math.PI/2;if(C){const p=(q=a.createConicGradient)==null?void 0:q.call(a,T-.85,m,x);p&&(p.addColorStop(0,"transparent"),p.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${S} ${N}%, transparent)`),p.addColorStop(1,"transparent"),a.fillStyle=p,a.beginPath(),a.moveTo(m,x),a.arc(m,x,M,T-.85,T),a.closePath(),a.fill()),a.strokeStyle=S,a.globalAlpha=k,a.beginPath(),a.moveTo(m,x),a.lineTo(m+Math.cos(T)*M,x+Math.sin(T)*M),a.stroke(),a.globalAlpha=1}y.forEach((p,I)=>{const B=I/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=p.lastReadMs===null?1:Math.min(1,(A-p.lastReadMs)/12e4),O=M*(.42+D*.5),F=m+Math.cos(B)*O,L=x+Math.sin(B)*O,Q=Math.abs((T-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=C?Math.max(0,1-Q/.55):0;a.beginPath(),a.arc(F,L,3+K*1.6,0,Math.PI*2),a.fillStyle=p.running?S:v,a.globalAlpha=p.running?.55+K*.45:.35,a.fill(),a.globalAlpha=1});for(const p of j){const I=(A-p.atMs)/oe;if(I<0||I>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===p.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;a.beginPath(),a.arc(O,F,4+I*22,0,Math.PI*2),a.strokeStyle=S,a.globalAlpha=(1-I)*.8,a.lineWidth=1.5,a.stroke(),a.globalAlpha=1}c=requestAnimationFrame(w)};return w(),()=>{r=!0,cancelAnimationFrame(c)}},[i]),e.jsx("canvas",{ref:d,className:"bw-radar",role:"img","aria-label":o?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function ce(s,n,o){if(s===null)return null;const i=s+n-o;return i>0?i:null}function de(s){const n=Math.ceil(s/1e3),o=Math.floor(n/60),i=n%60;return o>0?`${o}m ${i}s`:`${i}s`}function J(s){return(s??[]).filter(n=>n.installed&&n.profiles>0)}function he(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function ue(s,n){return s.map(o=>({key:o.key,name:o.name,running:o.running,visits:n.filter(i=>i.browser===o.key).reduce((i,d)=>i+d.programVisits,0)}))}function we(s,n){if(s===null)return null;const o=Math.max(0,Math.round((n-s)/1e3));if(o<5)return"just now";if(o<60)return`${o} sec ago`;const i=Math.round(o/60);return i<60?`${i} min ago`:`${Math.round(i/60)} hr ago`}function me(s){const n=s.profiles.filter(i=>i.degraded);if(n.length>0){const i=n[0],d=n.length-1;return{kind:"unreadable",text:`${i.name}/${i.profile} could not be read — ${i.reason??"no reason given"}`+(d>0?`, and ${d} more`:"")+". The watch continues on the profiles it can read."}}const o=J(s.browsers);return o.length===0?{kind:"no-profiles",text:"No browser on this machine has a profile to read. Open one of the browsers below once and it will be watched from then on."}:o.some(i=>i.running)?null:{kind:"none-running",text:"No watched browser is running, so nothing can be navigated right now. Their history is still read, and anything found while they were open is still here."}}function be({snap:s,watching:n,palette:o}){const[i,d]=l.useState(!1),[u,t]=l.useState(()=>Date.now());l.useEffect(()=>{const h=setInterval(()=>t(Date.now()),1e3);return()=>clearInterval(h)},[]);const a=ce(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(h=>!h),"aria-expanded":i,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(le,{browsers:n.map(h=>{var c;return{key:h.key,name:h.name,running:h.running,lastReadMs:((c=s.profiles.find(r=>r.browser===h.key))==null?void 0:c.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(h=>({browser:h.browser??null,atMs:h.endMs})),watching:s.settings.enabled,palette:o}),e.jsx("ul",{className:"bw-counts",children:ue(n,s.profiles).map(h=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:h.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:h.name})]},h.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 ",we(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"]})]}),a!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",de(a)]}),i&&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 fe({watching:s,rest:n}){const[o,i]=l.useState(!1),[d,u]=l.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:()=>i(t=>!t),"aria-expanded":o,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),o&&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))}),n.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?"▾":"▸"})," ",n.length," not watched"]}),d&&e.jsxs("p",{className:"bw-rest",children:[n.map(t=>t.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function ge({snap:s,quiet:n,setQuiet:o,save:i,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:n??s.settings.quietMinutes,onChange:t=>{o(Number(t.target.value)),i({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 i({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"?` (${he(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,n){return n?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function pe({snap:s,saving:n,save:o,why:i,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,n).word},H(s,n).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${i?" on":""}`,onClick:()=>d(u=>!u),"aria-expanded":i,"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 o({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 xe({relay:s}){const[n,o]=l.useState(!1),[i,d]=l.useState(!1),u=s.killswitch.blocked?"unblock":"block",t=s.command[u],a=l.useCallback(async()=>{await z(t.command)&&(d(!0),window.setTimeout(()=>d(!1),1600))},[t.command]),h=s.profiles.filter(r=>{var w;return(w=r.report)==null?void 0:w.present}),c=s.profiles.filter(r=>r.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(r=>e.jsx("code",{className:"bw-path",children:r},r))]}),e.jsxs("button",{className:"bw-why",onClick:()=>o(r=>!r),"aria-expanded":n,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:n?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),n&&e.jsxs("div",{className:"bw-rc-body",children:[h.length>0&&e.jsx("dl",{className:"bw-key",children:h.map(r=>e.jsxs(l.Fragment,{children:[e.jsxs("dt",{children:[r.name,r.profile==="Default"?"":` · ${r.profile}`]}),e.jsxs("dd",{children:[r.report.enabled?"enabled":"installed but switched off",r.report.allUrls?", can act on every site":", limited to the sites it was granted",r.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",r.report.sensitiveApis.join(", ")]})]})]},`${r.browser}/${r.profile}`))}),c.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[c.map(r=>r.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."]}),h.some(r=>r.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:a,children:i?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:t.note})]})]})}function je(s){const[n,o]=l.useState(null),[i,d]=l.useState(null),[u,t]=l.useState(null),[a,h]=l.useState(!1),c=l.useRef(!1),[r,w]=l.useState(null),[b,f]=l.useState(!1),y=l.useRef(!1),j=l.useCallback(async g=>{var v,P;if(!(g&&!X(c.current))){g&&(c.current=!0,h(!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();o(k),y.current||w(((v=k==null?void 0:k.settings)==null?void 0:v.quietMinutes)??15),s(((P=k==null?void 0:k.settings)==null?void 0:P.enabled)===!0),d(null)}catch(N){d(N instanceof Error?N.message:String(N))}finally{g&&(c.current=!1,h(!1))}}},[s]);l.useEffect(()=>{j(!1)},[j]),l.useEffect(()=>{const g=setInterval(()=>{j(!1)},1e4);return()=>clearInterval(g)},[j]);const C=l.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=l.useCallback(async g=>{y.current=!0,f(!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,f(!1)}},[j]);return{snap:n,error:i,writeError:u,setWriteError:t,busy:a,quiet:r,setQuiet:w,saving:b,load:j,dismiss:C,save:S}}function Ne({onClose:s,onSeen:n,onWatching:o,palette:i}){const d=Z(s),u=ee(s),{snap:t,error:a,writeError:h,setWriteError:c,busy:r,quiet:w,setQuiet:b,saving:f,load:y,dismiss:j,save:C}=je(o),[S,g]=l.useState(!1),[v,P]=l.useState(!1),N=l.useRef(null),k=l.useRef(n);k.current=n,l.useEffect(()=>()=>k.current(Date.now()),[]);const A=J(t==null?void 0:t.browsers),R=((t==null?void 0:t.browsers)??[]).filter(m=>!(m.installed&&m.profiles>0)),$=t?me(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),...se(r),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:r?"…":"↻"}),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&&!a&&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."})]}),a&&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:a})]})}),t&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),t&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(be,{snap:t,watching:A,palette:i}),e.jsx(fe,{watching:A,rest:R}),t.relay&&e.jsx(xe,{relay:t.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(re,{snap:t,dismiss:j,refreshRef:N}),e.jsx(te,{snap:t})]})]}),t&&S&&e.jsx(ge,{snap:t,quiet:w,setQuiet:b,save:C,access:v,setAccess:P}),h&&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:[h," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>c(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),t&&e.jsx(pe,{snap:t,saving:f,save:C,why:S,setWhy:g})]})]})})}export{Ne 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-ClJbck-M.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-FnWpX0X5.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};