ccdeck 3.37.1 → 3.38.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
@@ -94,6 +94,7 @@ The deck opens on these eight pictures the first time it runs — they are the w
94
94
  | | |
95
95
  |---|---|
96
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** | Each sidebar row says it in a line: what a running session's newest reply says it is doing — the sentence it wrote, or the description on the command it ran. 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. |
97
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. |
98
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. |
99
100
  | **Cost and quota, live** | Spend per model and per session, plus Claude and Codex quota windows as they refill. |
@@ -159,7 +160,7 @@ The machine panel shows a **Thermal** section only where the machine actually an
159
160
 
160
161
  | | reads | needs |
161
162
  | --- | --- | --- |
162
- | Linux | `/sys/class/hwmon`, then `/sys/class/thermal/thermal_zone*` | nothing |
163
+ | Linux | `/sys/class/hwmon`, then `/sys/class/thermal/thermal_zone*`; on Intel, `thermal_throttle/package_throttle_total_time_ms` for throttling — the share of time the clock was held down | nothing |
163
164
  | Windows | the `Thermal Zone Information` performance counter, then `MSAcpi_ThermalZoneTemperature`, then LibreHardwareMonitor's web server if it happens to be running | nothing — where the machine has an ACPI thermal zone. Many do not; see below |
164
165
  | macOS, Intel | `ioreg` for the GPU, `pmset -g therm` for throttling | nothing |
165
166
  | macOS, Apple Silicon | `macmon`, which the deck fetches for you | nothing |
@@ -210,6 +211,8 @@ It never steers an agent or edits your code, but it is not read-only either —
210
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
213
 
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
+
213
216
  ## Browser Watch
214
217
 
215
218
  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.
@@ -272,11 +275,11 @@ Four pictures, which are also the guide the section opens from `See how it works
272
275
  </tr>
273
276
  </table>
274
277
 
275
- **On each machine, in order:** open the Claude accounts panel (`A`), open **Local network**'s settings (the sliders button), and tick the logins this machine may hand out. The section is on unless somebody switched it off. The two decks find each other over a UDP beacon on port 45317 and ask each other to pair; press `accept` where it is asked, and from then on every paired deck is asked once a minute for anything this deck's expired logins need.
278
+ **On each machine, in order:** open the Claude accounts panel (`A`), open **Local network**'s settings (the sliders button), and tick the logins this machine may hand out. The section is on unless somebody switched it off. The two decks find each other over a UDP beacon on port 45317 and ask each other to pair; press `accept` where it is asked, and from then on every paired deck is asked once a minute for the ticked logins this deck is missing or has expired.
276
279
 
277
280
  **When they do not find each other** — a VPN, a guest network, two subnets — `+` at the top of the section reaches a deck by address, or with an invite the other deck minted. An invite is the route when the machine that cannot be seen is this one.
278
281
 
279
- **What is shared, and with whom.** Nothing until you tick a login, and then only that login, and only with paired decks — the deck asks the machines it finds, and a machine that is asked waits for somebody there to press `accept` unless its `Say yes to every deck that asks` switch, in the same dialog, is on. That switch starts on, so your own machines pair without a press; what still takes a person is the tick, because a paired deck is handed nothing until somebody ticks a login here. On a network you do not own, turn the switch off. A login that arrives from a deck somebody here accepted or paired by invite is ticked here too, so this machine can repair the next one; a deck the switch paired is handed only the logins a person ticked, so untick and tick that one again to hand it to those decks as well. A paired deck can fill an *expired* slot of this deck's and nothing else: it cannot overwrite a login that still works here. What crosses the wire is the same live credential a share carries, sealed to the deck it is addressed to, so treat the pairing decision as the moment that matters. Unpairing stops future rounds; a login already copied stays where it went.
282
+ **What is shared, and with whom.** Nothing until you tick a login, and then only that login, and only with paired decks. A login ticked on one deck arrives on every deck paired with it: added where it is missing, repaired where it has expired, and never in place of one that still works. The deck asks the machines it finds, and a machine that is asked waits for somebody there to press `accept` unless its `Say yes to every deck that asks` switch, in the same dialog, is on. That switch starts on, so your own machines pair without a press. It also means a deck the switch pairs can add the logins ticked on it to this machine, and Auto-switch, if it is on, may later move Claude Code to one of them — so on a network you do not own, turn the switch off. What still takes a person is the tick: a paired deck is handed nothing until somebody ticks a login here. A login that arrives from a deck somebody here accepted or paired by invite is ticked here too, so this machine can repair the next one; a deck the switch paired is handed only the logins a person ticked on this deck, never the ones that were ticked by arriving, so untick and tick such a login again to hand it to those decks as well — and what a deck the switch paired brings here is not ticked here at all. What crosses the wire is the same live credential a share carries, sealed to the deck it is addressed to, so treat the pairing decision as the moment that matters. Unpairing stops future rounds; a login already copied stays where it went.
280
283
 
281
284
  On a Mac, **cannot share here** is different from **expired here**. It means this ccdeck process cannot read the Keychain copy, so that machine is not used as a credential source and another deck does not keep trying to “repair” the same healthy slot every minute. A LAN import is checked after it lands too; if the receiving Mac still cannot read the Keychain, the round reports that problem instead of claiming the login was repaired.
282
285
 
@@ -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-DDxnWaTa.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-DnnkOgia.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-DDxnWaTa.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-DnnkOgia.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};