ccdeck 3.33.1 → 3.34.0

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
@@ -22,7 +22,7 @@ Or the desktop app, with the waiting count in your menu bar: **[macOS](https://g
22
22
 
23
23
  *A generated session, drawn by the deck itself — see `assets/canvas-demo.mjs`. Click through for full size.*
24
24
 
25
- who is blocked on you · tool calls · one canvas · cost · quota · local · no telemetry
25
+ who is blocked on you · tool calls · one canvas · cost · quota · local · sessions never leave
26
26
 
27
27
  [ccdeck.dev](https://ccdeck.dev) · [What you get](#what-you-get) · [Quick start](#quick-start) · [How it works](#how-it-works) · [What it touches](#what-it-touches) · [Accounts](#accounts) · [Local network](#local-network) · [Options](#options) · [FAQ](#questions-people-ask)
28
28
 
@@ -108,7 +108,7 @@ log in. **`ccdeck --stop` is the off switch**; `Ctrl+C` only cancels a start tha
108
108
  is still printing. `ccdeck --status` says what is running, and `--foreground`
109
109
  holds the terminal the way every version before 3.20 did.
110
110
 
111
- No config file. No account. No telemetry — nothing about your sessions is reported anywhere.
111
+ No config file. No account. Nothing about your sessions is reported anywhere — the deck does send anonymous usage reports (its version, your system, the errors it hits), on by default, and one switch under Appearance turns them off.
112
112
 
113
113
  **The deck cannot steer your agent.** The hook it installs is a one-way forwarder: it POSTs the event, exits `0`, and writes nothing to stdout. Those are the two channels Claude Code's hook protocol gives a hook for allowing, denying, deferring or rewriting the tool call it was told about, and this one uses neither — it has no way to answer at all. `src/web/__tests__/hook-read-only.test.ts` pins both halves, over the source and by running the real script.
114
114
 
@@ -191,7 +191,7 @@ Quota is the one thing that is not just reading. It needs a live token, so when
191
191
 
192
192
  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.
193
193
 
194
- 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. 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). `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.
194
+ 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. 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). Unless you switch it off under **Appearance → Help improve ccdeck**, the deck also sends anonymous reports to `api.ccdeck.dev`: an install event, an update event, one "active" a day, and the errors it runs into with folders, addresses and keys scrubbed out, all under a random id that is tied to nothing on your machine and never your sessions, prompts or files; switching it off deletes what was sent, and `AGENTS_DECK_NO_REPORTS=1` keeps it off from the first start. **Send feedback…** under Appearance sends what you write 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. `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.
195
195
 
196
196
  ## Browser Watch
197
197
 
@@ -378,13 +378,14 @@ Environment:
378
378
  |---|---|
379
379
  | `AGENT_DAG_PORT` | Default port, same as `-p` |
380
380
  | `CODEX_HOME` | Override `~/.codex` |
381
- | `AGENTS_DECK_NO_INSTALL=1` | Never install or update claude-swap / ccusage, never ask npm about releases, and never read the status pages |
381
+ | `AGENTS_DECK_NO_INSTALL=1` | Never install or update claude-swap / ccusage, never ask npm about releases, never read the status pages, and never send reports or feedback |
382
382
  | `AGENTS_DECK_NO_DOWNLOAD=1` | Never download the `uv` binary, but keep the managed installs |
383
383
  | `AGENTS_DECK_NO_UPDATE_CHECK=1` | Don't ask npm about releases, but keep everything else |
384
384
  | `AGENTS_DECK_NO_STATUS=1` | Don't read Anthropic's and OpenAI's status pages, so no incident chip appears |
385
385
  | `AGENTS_DECK_NO_FRESHEN=1` | Never nudge claude-swap to collect usage early |
386
386
  | `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 |
387
387
  | `AGENTS_DECK_NO_LAN=1` | Keep **Local network** off, whatever its switch in the panel says |
388
+ | `AGENTS_DECK_NO_REPORTS=1` | Never send anonymous reports, whatever the switch in **Appearance** says |
388
389
  | `AGENTS_DECK_CSWAP` | Full path to `cswap`, when it lives somewhere unusual |
389
390
  | `AGENTS_DECK_CLAUDE` | Full path to the `claude` CLI |
390
391
  | `AGENTS_DECK_CCUSAGE` | Full path to your own `ccusage`, used ahead of everything else |
@@ -504,8 +505,9 @@ queue needs to know what is happening now. Codex does report it — its
504
505
  that is open work, not a wall.
505
506
 
506
507
  **Does anything leave my machine?**
507
- Your sessions, never. The deck binds `127.0.0.1` and has no telemetry. The only
508
- outbound requests are a ~20-byte version check to the npm registry at most once
508
+ Your sessions, never. The deck binds `127.0.0.1`. It does send anonymous usage
509
+ reports — version, system, errors — unless you switch them off under Appearance.
510
+ Beyond those, the outbound requests are a ~20-byte version check to the npm registry at most once
509
511
  an hour, and whatever the usage panels ask Anthropic and OpenAI for about your
510
512
  own quota. [What it touches](#what-it-touches) lists all of it.
511
513
 
@@ -1 +1 @@
1
- import{j as e,r as c,k as _,l as V,m as Y,n as z,s as X,u as Z,g as ee}from"./index-D7ddxIpC.js";function se({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(t=>e.jsxs("div",{className:`bw-log-line ${t.level}${t.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(t.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),t.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:t.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:t.parts.profile,children:t.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[t.parts.value,t.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",t.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:t.text})]},`${t.atMs}-${t.level}-${t.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function te(s){const t=l=>new Date(l).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?t(s.startMs):`${t(s.startMs)} → ${t(s.endMs)}`}function ne(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const t=Math.round((s.endMs-s.startMs)/6e4);return t<1?"":`${t} min`}function ae({snap:s,dismiss:t,refreshRef:l}){const[r,h]=c.useState(null),o=c.useRef(new Map),a=c.useRef(null);c.useEffect(()=>{var i,w;const d=a.current;!d||s.episodes.some(m=>E(m)===d.id)||(a.current=null,_(((i=document.activeElement)==null?void 0:i.tagName)??null)&&((w=(d.next?o.current.get(d.next):void 0)??l.current)==null||w.focus()))},[s]);const n=d=>{a.current={id:E(d),next:V(s.episodes.map(E),E(d))},t(d)},u=c.useMemo(()=>{const d=[];for(const i of s.episodes){const w=ne(i.startMs),m=d[d.length-1];m&&m.label===w?m.episodes.push(i):d.push({label:w,episodes:[i]})}return d},[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:u.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."})]}):u.map(d=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:d.label}),d.episodes.map(i=>{const w=E(i),m=r===w;return e.jsxs("div",{className:`bw-ep${m?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:b=>{b?o.current.set(w,b):o.current.delete(w)},onClick:()=>h(m?null:w),"aria-expanded":m,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:m?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:i.host}),e.jsxs("span",{className:"bw-ep-meta",children:[te(i),U(i)&&e.jsxs(e.Fragment,{children:[" · ",U(i)]}),i.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[i.count," ",i.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>n(i),"aria-label":`Dismiss ${i.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),m&&e.jsx("ul",{className:"bw-urls",children:i.urls.map((b,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(b.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:b.url})]},`${b.url}-${b.timeMs}-${y}`))})]},w)})]},d.label))})]})}function re(s){const t=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!t)return!1;const l=parseInt(t[1],16),r=[l>>16&255,l>>8&255,l&255].map(h=>{const o=h/255;return o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4)});return .2126*r[0]+.7152*r[1]+.0722*r[2]>.5}const G=1e4,ie=12e3;function oe({browsers:s,findings:t,watching:l,palette:r}){const h=c.useRef(null),o=c.useRef(null);return o.current={browsers:s,findings:t,watching:l},c.useEffect(()=>{var m;const a=h.current;if(!a)return;const n=a.getContext("2d");if(!n)return;const u=((m=window.matchMedia)==null?void 0:m.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let d=0,i=!1;const w=()=>{var q;if(i)return;const b=o.current;if(!b)return;const{browsers:y,findings:p,watching:P}=b,R=r["--accent"]||"#7dd3fc",f=r["--line"]||"#1f2229",j=r["--text-dim"]||"#6e727c",C=re(r["--bg"]),N=C?42:26,v=C?.85:.65,I=Date.now(),$=Math.min(window.devicePixelRatio||1,2),W=a.clientWidth,k=a.clientHeight;(a.width!==W*$||a.height!==k*$)&&(a.width=W*$,a.height=k*$),n.setTransform($,0,0,$,0,0),n.clearRect(0,0,W,k);const x=W/2,M=k/2,S=Math.min(W,k)/2-6;n.strokeStyle=f,n.lineWidth=1,n.globalAlpha=C?1:.9;for(const g of[.4,.7,1])n.beginPath(),n.arc(x,M,S*g,0,Math.PI*2),n.stroke();n.beginPath(),n.moveTo(x-S,M),n.lineTo(x+S,M),n.moveTo(x,M-S),n.lineTo(x,M+S),n.stroke(),n.globalAlpha=1;const A=(P&&!u?I%G/G:0)*Math.PI*2-Math.PI/2;if(P){const g=(q=n.createConicGradient)==null?void 0:q.call(n,A-.85,x,M);g&&(g.addColorStop(0,"transparent"),g.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${R} ${N}%, transparent)`),g.addColorStop(1,"transparent"),n.fillStyle=g,n.beginPath(),n.moveTo(x,M),n.arc(x,M,S,A-.85,A),n.closePath(),n.fill()),n.strokeStyle=R,n.globalAlpha=v,n.beginPath(),n.moveTo(x,M),n.lineTo(x+Math.cos(A)*S,M+Math.sin(A)*S),n.stroke(),n.globalAlpha=1}y.forEach((g,T)=>{const B=T/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=g.lastReadMs===null?1:Math.min(1,(I-g.lastReadMs)/12e4),O=S*(.42+D*.5),F=x+Math.cos(B)*O,L=M+Math.sin(B)*O,Q=Math.abs((A-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=P?Math.max(0,1-Q/.55):0;n.beginPath(),n.arc(F,L,3+K*1.6,0,Math.PI*2),n.fillStyle=g.running?R:j,n.globalAlpha=g.running?.55+K*.45:.35,n.fill(),n.globalAlpha=1});for(const g of p){const T=(I-g.atMs)/ie;if(T<0||T>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===g.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=x+Math.cos(D)*S*.6,F=M+Math.sin(D)*S*.6;n.beginPath(),n.arc(O,F,4+T*22,0,Math.PI*2),n.strokeStyle=R,n.globalAlpha=(1-T)*.8,n.lineWidth=1.5,n.stroke(),n.globalAlpha=1}d=requestAnimationFrame(w)};return w(),()=>{i=!0,cancelAnimationFrame(d)}},[r]),e.jsx("canvas",{ref:h,className:"bw-radar",role:"img","aria-label":l?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function le(s,t,l){if(s===null)return null;const r=s+t-l;return r>0?r:null}function ce(s){const t=Math.ceil(s/1e3),l=Math.floor(t/60),r=t%60;return l>0?`${l}m ${r}s`:`${r}s`}function J(s){return(s??[]).filter(t=>t.installed&&t.profiles>0)}function de(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function he(s,t){return s.map(l=>({key:l.key,name:l.name,running:l.running,visits:t.filter(r=>r.browser===l.key).reduce((r,h)=>r+h.programVisits,0)}))}function ue(s,t){if(s===null)return null;const l=Math.max(0,Math.round((t-s)/1e3));if(l<5)return"just now";if(l<60)return`${l} sec ago`;const r=Math.round(l/60);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`}function we(s){const t=s.profiles.filter(r=>r.degraded);if(t.length>0){const r=t[0],h=t.length-1;return{kind:"unreadable",text:`${r.name}/${r.profile} could not be read — ${r.reason??"no reason given"}`+(h>0?`, and ${h} more`:"")+". The watch continues on the profiles it can read."}}const l=J(s.browsers);return l.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."}:l.some(r=>r.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 me({snap:s,watching:t,palette:l}){const[r,h]=c.useState(!1),[o,a]=c.useState(()=>Date.now());c.useEffect(()=>{const u=setInterval(()=>a(Date.now()),1e3);return()=>clearInterval(u)},[]);const n=le(s.coverage.lastHumanMs,s.coverage.quietMs,o);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:()=>h(u=>!u),"aria-expanded":r,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(oe,{browsers:t.map(u=>{var d;return{key:u.key,name:u.name,running:u.running,lastReadMs:((d=s.profiles.find(i=>i.browser===u.key))==null?void 0:d.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(u=>({browser:u.browser??null,atMs:u.endMs})),watching:s.settings.enabled,palette:l}),e.jsx("ul",{className:"bw-counts",children:he(t,s.profiles).map(u=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:u.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:u.name})]},u.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 ",ue(s.coverage.checkedMs,o),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),n!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",ce(n)]}),r&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function be({watching:s,rest:t}){const[l,r]=c.useState(!1),[h,o]=c.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:()=>r(a=>!a),"aria-expanded":l,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),l&&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(a=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${a.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:a.name}),e.jsx("span",{className:"bw-prof-state",children:a.running?"running":"idle"}),a.running&&a.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},a.key))}),t.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," ",t.length," not watched"]}),h&&e.jsxs("p",{className:"bw-rest",children:[t.map(a=>a.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function fe({snap:s,quiet:t,setQuiet:l,save:r,access:h,setAccess:o}){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:t??s.settings.quietMinutes,onChange:a=>{l(Number(a.target.value)),r({quietMinutes:Number(a.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:a=>void r({reaction:a.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(a=>e.jsx("option",{value:a,children:a==="notify"?"notify me":a==="close-tab"?"close the tab":"quit the browser"},a))})]}),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:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," What Browser Watch can access"]}),h&&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"?` (${de(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,t){return t?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function ge({snap:s,saving:t,save:l,why:r,setWhy:h}){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,t).word},H(s,t).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${r?" on":""}`,onClick:()=>h(o=>!o),"aria-expanded":r,"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 l({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 pe({relay:s}){const[t,l]=c.useState(!1),[r,h]=c.useState(!1),o=s.killswitch.blocked?"unblock":"block",a=s.command[o],n=c.useCallback(async()=>{await z(a.command)&&(h(!0),window.setTimeout(()=>h(!1),1600))},[a.command]),u=s.profiles.filter(i=>{var w;return(w=i.report)==null?void 0:w.present}),d=s.profiles.filter(i=>i.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(i=>e.jsx("code",{className:"bw-path",children:i},i))]}),e.jsxs("button",{className:"bw-why",onClick:()=>l(i=>!i),"aria-expanded":t,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:t?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),t&&e.jsxs("div",{className:"bw-rc-body",children:[u.length>0&&e.jsx("dl",{className:"bw-key",children:u.map(i=>e.jsxs(c.Fragment,{children:[e.jsxs("dt",{children:[i.name,i.profile==="Default"?"":` · ${i.profile}`]}),e.jsxs("dd",{children:[i.report.enabled?"enabled":"installed but switched off",i.report.allUrls?", can act on every site":", limited to the sites it was granted",i.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",i.report.sensitiveApis.join(", ")]})]})]},`${i.browser}/${i.profile}`))}),d.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[d.map(i=>i.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),u.some(i=>i.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:a.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:n,children:r?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:a.note})]})]})}function xe(s){const[t,l]=c.useState(null),[r,h]=c.useState(null),[o,a]=c.useState(null),[n,u]=c.useState(!1),d=c.useRef(!1),[i,w]=c.useState(null),[m,b]=c.useState(!1),y=c.useRef(!1),p=c.useCallback(async f=>{var j,C;if(!(f&&!X(d.current))){f&&(d.current=!0,u(!0));try{const N=await fetch(`/api/browser-watch${f?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const v=await N.json();l(v),y.current||w(((j=v==null?void 0:v.settings)==null?void 0:j.quietMinutes)??15),s(((C=v==null?void 0:v.settings)==null?void 0:C.enabled)===!0),h(null)}catch(N){h(N instanceof Error?N.message:String(N))}finally{f&&(d.current=!1,u(!1))}}},[s]);c.useEffect(()=>{p(!1)},[p]),c.useEffect(()=>{const f=setInterval(()=>{p(!1)},1e4);return()=>clearInterval(f)},[p]);const P=c.useCallback(async f=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:f.host,startMs:f.startMs})})}finally{await p(!1)}},[p]),R=c.useCallback(async f=>{y.current=!0,b(!0);try{const j=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)});if(!j.ok)throw new Error(`the deck answered ${j.status}`);await p(!1),a(null)}catch(j){a(j instanceof Error?j.message:String(j))}finally{y.current=!1,b(!1)}},[p]);return{snap:t,error:r,writeError:o,setWriteError:a,busy:n,quiet:i,setQuiet:w,saving:m,load:p,dismiss:P,save:R}}function ye({onClose:s,onSeen:t,onWatching:l,palette:r}){const h=Z(s),{snap:o,error:a,writeError:n,setWriteError:u,busy:d,quiet:i,setQuiet:w,saving:m,load:b,dismiss:y,save:p}=xe(l),[P,R]=c.useState(!1),[f,j]=c.useState(!1),C=c.useRef(null),N=c.useRef(t);N.current=t,c.useEffect(()=>()=>N.current(Date.now()),[]);const v=J(o==null?void 0:o.browsers),I=((o==null?void 0:o.browsers)??[]).filter(k=>!(k.installed&&k.profiles>0)),$=o?we(o):null,W=v.reduce((k,x)=>k+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:e.jsxs("div",{ref:h,className:"modal bw-modal",onClick:k=>k.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:o?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:C,className:"glyph-btn",onClick:()=>void b(!0),...ee(d),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:d?"…":"↻"}),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:[!o&&!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})]})}),o&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),o&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(me,{snap:o,watching:v,palette:r}),e.jsx(be,{watching:v,rest:I}),o.relay&&e.jsx(pe,{relay:o.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(ae,{snap:o,dismiss:y,refreshRef:C}),e.jsx(se,{snap:o})]})]}),o&&P&&e.jsx(fe,{snap:o,quiet:i,setQuiet:w,save:p,access:f,setAccess:j}),n&&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:[n," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>u(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),o&&e.jsx(ge,{snap:o,saving:m,save:p,why:P,setWhy:R})]})]})})}export{ye as default};
1
+ import{j as e,r as c,k as _,l as V,m as Y,n as z,s as X,u as Z,g as ee}from"./index-CgHJ_aHk.js";function se({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(t=>e.jsxs("div",{className:`bw-log-line ${t.level}${t.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(t.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),t.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:t.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:t.parts.profile,children:t.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[t.parts.value,t.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",t.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:t.text})]},`${t.atMs}-${t.level}-${t.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function te(s){const t=l=>new Date(l).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?t(s.startMs):`${t(s.startMs)} → ${t(s.endMs)}`}function ne(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const t=Math.round((s.endMs-s.startMs)/6e4);return t<1?"":`${t} min`}function ae({snap:s,dismiss:t,refreshRef:l}){const[r,h]=c.useState(null),o=c.useRef(new Map),a=c.useRef(null);c.useEffect(()=>{var i,w;const d=a.current;!d||s.episodes.some(m=>E(m)===d.id)||(a.current=null,_(((i=document.activeElement)==null?void 0:i.tagName)??null)&&((w=(d.next?o.current.get(d.next):void 0)??l.current)==null||w.focus()))},[s]);const n=d=>{a.current={id:E(d),next:V(s.episodes.map(E),E(d))},t(d)},u=c.useMemo(()=>{const d=[];for(const i of s.episodes){const w=ne(i.startMs),m=d[d.length-1];m&&m.label===w?m.episodes.push(i):d.push({label:w,episodes:[i]})}return d},[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:u.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."})]}):u.map(d=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:d.label}),d.episodes.map(i=>{const w=E(i),m=r===w;return e.jsxs("div",{className:`bw-ep${m?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:b=>{b?o.current.set(w,b):o.current.delete(w)},onClick:()=>h(m?null:w),"aria-expanded":m,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:m?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:i.host}),e.jsxs("span",{className:"bw-ep-meta",children:[te(i),U(i)&&e.jsxs(e.Fragment,{children:[" · ",U(i)]}),i.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[i.count," ",i.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>n(i),"aria-label":`Dismiss ${i.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),m&&e.jsx("ul",{className:"bw-urls",children:i.urls.map((b,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(b.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:b.url})]},`${b.url}-${b.timeMs}-${y}`))})]},w)})]},d.label))})]})}function re(s){const t=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!t)return!1;const l=parseInt(t[1],16),r=[l>>16&255,l>>8&255,l&255].map(h=>{const o=h/255;return o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4)});return .2126*r[0]+.7152*r[1]+.0722*r[2]>.5}const G=1e4,ie=12e3;function oe({browsers:s,findings:t,watching:l,palette:r}){const h=c.useRef(null),o=c.useRef(null);return o.current={browsers:s,findings:t,watching:l},c.useEffect(()=>{var m;const a=h.current;if(!a)return;const n=a.getContext("2d");if(!n)return;const u=((m=window.matchMedia)==null?void 0:m.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let d=0,i=!1;const w=()=>{var q;if(i)return;const b=o.current;if(!b)return;const{browsers:y,findings:p,watching:P}=b,R=r["--accent"]||"#7dd3fc",f=r["--line"]||"#1f2229",j=r["--text-dim"]||"#6e727c",C=re(r["--bg"]),N=C?42:26,v=C?.85:.65,I=Date.now(),$=Math.min(window.devicePixelRatio||1,2),W=a.clientWidth,k=a.clientHeight;(a.width!==W*$||a.height!==k*$)&&(a.width=W*$,a.height=k*$),n.setTransform($,0,0,$,0,0),n.clearRect(0,0,W,k);const x=W/2,M=k/2,S=Math.min(W,k)/2-6;n.strokeStyle=f,n.lineWidth=1,n.globalAlpha=C?1:.9;for(const g of[.4,.7,1])n.beginPath(),n.arc(x,M,S*g,0,Math.PI*2),n.stroke();n.beginPath(),n.moveTo(x-S,M),n.lineTo(x+S,M),n.moveTo(x,M-S),n.lineTo(x,M+S),n.stroke(),n.globalAlpha=1;const A=(P&&!u?I%G/G:0)*Math.PI*2-Math.PI/2;if(P){const g=(q=n.createConicGradient)==null?void 0:q.call(n,A-.85,x,M);g&&(g.addColorStop(0,"transparent"),g.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${R} ${N}%, transparent)`),g.addColorStop(1,"transparent"),n.fillStyle=g,n.beginPath(),n.moveTo(x,M),n.arc(x,M,S,A-.85,A),n.closePath(),n.fill()),n.strokeStyle=R,n.globalAlpha=v,n.beginPath(),n.moveTo(x,M),n.lineTo(x+Math.cos(A)*S,M+Math.sin(A)*S),n.stroke(),n.globalAlpha=1}y.forEach((g,T)=>{const B=T/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=g.lastReadMs===null?1:Math.min(1,(I-g.lastReadMs)/12e4),O=S*(.42+D*.5),F=x+Math.cos(B)*O,L=M+Math.sin(B)*O,Q=Math.abs((A-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=P?Math.max(0,1-Q/.55):0;n.beginPath(),n.arc(F,L,3+K*1.6,0,Math.PI*2),n.fillStyle=g.running?R:j,n.globalAlpha=g.running?.55+K*.45:.35,n.fill(),n.globalAlpha=1});for(const g of p){const T=(I-g.atMs)/ie;if(T<0||T>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===g.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=x+Math.cos(D)*S*.6,F=M+Math.sin(D)*S*.6;n.beginPath(),n.arc(O,F,4+T*22,0,Math.PI*2),n.strokeStyle=R,n.globalAlpha=(1-T)*.8,n.lineWidth=1.5,n.stroke(),n.globalAlpha=1}d=requestAnimationFrame(w)};return w(),()=>{i=!0,cancelAnimationFrame(d)}},[r]),e.jsx("canvas",{ref:h,className:"bw-radar",role:"img","aria-label":l?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function le(s,t,l){if(s===null)return null;const r=s+t-l;return r>0?r:null}function ce(s){const t=Math.ceil(s/1e3),l=Math.floor(t/60),r=t%60;return l>0?`${l}m ${r}s`:`${r}s`}function J(s){return(s??[]).filter(t=>t.installed&&t.profiles>0)}function de(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function he(s,t){return s.map(l=>({key:l.key,name:l.name,running:l.running,visits:t.filter(r=>r.browser===l.key).reduce((r,h)=>r+h.programVisits,0)}))}function ue(s,t){if(s===null)return null;const l=Math.max(0,Math.round((t-s)/1e3));if(l<5)return"just now";if(l<60)return`${l} sec ago`;const r=Math.round(l/60);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`}function we(s){const t=s.profiles.filter(r=>r.degraded);if(t.length>0){const r=t[0],h=t.length-1;return{kind:"unreadable",text:`${r.name}/${r.profile} could not be read — ${r.reason??"no reason given"}`+(h>0?`, and ${h} more`:"")+". The watch continues on the profiles it can read."}}const l=J(s.browsers);return l.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."}:l.some(r=>r.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 me({snap:s,watching:t,palette:l}){const[r,h]=c.useState(!1),[o,a]=c.useState(()=>Date.now());c.useEffect(()=>{const u=setInterval(()=>a(Date.now()),1e3);return()=>clearInterval(u)},[]);const n=le(s.coverage.lastHumanMs,s.coverage.quietMs,o);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:()=>h(u=>!u),"aria-expanded":r,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(oe,{browsers:t.map(u=>{var d;return{key:u.key,name:u.name,running:u.running,lastReadMs:((d=s.profiles.find(i=>i.browser===u.key))==null?void 0:d.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(u=>({browser:u.browser??null,atMs:u.endMs})),watching:s.settings.enabled,palette:l}),e.jsx("ul",{className:"bw-counts",children:he(t,s.profiles).map(u=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:u.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:u.name})]},u.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 ",ue(s.coverage.checkedMs,o),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),n!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",ce(n)]}),r&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function be({watching:s,rest:t}){const[l,r]=c.useState(!1),[h,o]=c.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:()=>r(a=>!a),"aria-expanded":l,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),l&&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(a=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${a.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:a.name}),e.jsx("span",{className:"bw-prof-state",children:a.running?"running":"idle"}),a.running&&a.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},a.key))}),t.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," ",t.length," not watched"]}),h&&e.jsxs("p",{className:"bw-rest",children:[t.map(a=>a.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function fe({snap:s,quiet:t,setQuiet:l,save:r,access:h,setAccess:o}){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:t??s.settings.quietMinutes,onChange:a=>{l(Number(a.target.value)),r({quietMinutes:Number(a.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:a=>void r({reaction:a.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(a=>e.jsx("option",{value:a,children:a==="notify"?"notify me":a==="close-tab"?"close the tab":"quit the browser"},a))})]}),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:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," What Browser Watch can access"]}),h&&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"?` (${de(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,t){return t?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function ge({snap:s,saving:t,save:l,why:r,setWhy:h}){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,t).word},H(s,t).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${r?" on":""}`,onClick:()=>h(o=>!o),"aria-expanded":r,"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 l({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 pe({relay:s}){const[t,l]=c.useState(!1),[r,h]=c.useState(!1),o=s.killswitch.blocked?"unblock":"block",a=s.command[o],n=c.useCallback(async()=>{await z(a.command)&&(h(!0),window.setTimeout(()=>h(!1),1600))},[a.command]),u=s.profiles.filter(i=>{var w;return(w=i.report)==null?void 0:w.present}),d=s.profiles.filter(i=>i.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(i=>e.jsx("code",{className:"bw-path",children:i},i))]}),e.jsxs("button",{className:"bw-why",onClick:()=>l(i=>!i),"aria-expanded":t,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:t?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),t&&e.jsxs("div",{className:"bw-rc-body",children:[u.length>0&&e.jsx("dl",{className:"bw-key",children:u.map(i=>e.jsxs(c.Fragment,{children:[e.jsxs("dt",{children:[i.name,i.profile==="Default"?"":` · ${i.profile}`]}),e.jsxs("dd",{children:[i.report.enabled?"enabled":"installed but switched off",i.report.allUrls?", can act on every site":", limited to the sites it was granted",i.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",i.report.sensitiveApis.join(", ")]})]})]},`${i.browser}/${i.profile}`))}),d.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[d.map(i=>i.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),u.some(i=>i.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:a.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:n,children:r?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:a.note})]})]})}function xe(s){const[t,l]=c.useState(null),[r,h]=c.useState(null),[o,a]=c.useState(null),[n,u]=c.useState(!1),d=c.useRef(!1),[i,w]=c.useState(null),[m,b]=c.useState(!1),y=c.useRef(!1),p=c.useCallback(async f=>{var j,C;if(!(f&&!X(d.current))){f&&(d.current=!0,u(!0));try{const N=await fetch(`/api/browser-watch${f?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const v=await N.json();l(v),y.current||w(((j=v==null?void 0:v.settings)==null?void 0:j.quietMinutes)??15),s(((C=v==null?void 0:v.settings)==null?void 0:C.enabled)===!0),h(null)}catch(N){h(N instanceof Error?N.message:String(N))}finally{f&&(d.current=!1,u(!1))}}},[s]);c.useEffect(()=>{p(!1)},[p]),c.useEffect(()=>{const f=setInterval(()=>{p(!1)},1e4);return()=>clearInterval(f)},[p]);const P=c.useCallback(async f=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:f.host,startMs:f.startMs})})}finally{await p(!1)}},[p]),R=c.useCallback(async f=>{y.current=!0,b(!0);try{const j=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)});if(!j.ok)throw new Error(`the deck answered ${j.status}`);await p(!1),a(null)}catch(j){a(j instanceof Error?j.message:String(j))}finally{y.current=!1,b(!1)}},[p]);return{snap:t,error:r,writeError:o,setWriteError:a,busy:n,quiet:i,setQuiet:w,saving:m,load:p,dismiss:P,save:R}}function ye({onClose:s,onSeen:t,onWatching:l,palette:r}){const h=Z(s),{snap:o,error:a,writeError:n,setWriteError:u,busy:d,quiet:i,setQuiet:w,saving:m,load:b,dismiss:y,save:p}=xe(l),[P,R]=c.useState(!1),[f,j]=c.useState(!1),C=c.useRef(null),N=c.useRef(t);N.current=t,c.useEffect(()=>()=>N.current(Date.now()),[]);const v=J(o==null?void 0:o.browsers),I=((o==null?void 0:o.browsers)??[]).filter(k=>!(k.installed&&k.profiles>0)),$=o?we(o):null,W=v.reduce((k,x)=>k+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:e.jsxs("div",{ref:h,className:"modal bw-modal",onClick:k=>k.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:o?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:C,className:"glyph-btn",onClick:()=>void b(!0),...ee(d),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:d?"…":"↻"}),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:[!o&&!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})]})}),o&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),o&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(me,{snap:o,watching:v,palette:r}),e.jsx(be,{watching:v,rest:I}),o.relay&&e.jsx(pe,{relay:o.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(ae,{snap:o,dismiss:y,refreshRef:C}),e.jsx(se,{snap:o})]})]}),o&&P&&e.jsx(fe,{snap:o,quiet:i,setQuiet:w,save:p,access:f,setAccess:j}),n&&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:[n," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>u(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),o&&e.jsx(ge,{snap:o,saving:m,save:p,why:P,setWhy:R})]})]})})}export{ye as default};
@@ -1 +1 @@
1
- import{a as U,f as p,r as c,s as Z,p as _,j as a,b as g,c as O,u as D,d as ee,e as se,g as E,h as ae,i as te}from"./index-D7ddxIpC.js";function ne(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 I(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 oe(s){const e=I([{agents:s}]);return e.length<2?null:e.map(n=>`${U(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 le(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,M=s=>typeof s=="string"?s:"",P=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function re(s){const e=s??{};return{modelName:M(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ce(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:M(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:M(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:P(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(re):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:P(e.metadata.agents)}}:{}}}function ie(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ce):void 0}}function T(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 ue(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 de(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function q(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function C(s,e){return e>0?s/e*100:0}function he(s){var n,o;return oe(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function me(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function pe(s){const[e,n]=c.useState(null),[o,r]=c.useState(!1),m=c.useState(me)[0],l=c.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const k=s;r(!0);const i=`/api/ccusage?since=${_(k)}${f?"&refresh=1":""}`;fetch(i).then(j=>j.json()).then(j=>{x()&&n({range:k,resp:ie(j)})}).catch(()=>{x()&&n({range:k,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return c.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function ge({selectedDay:s}){const e=he(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(y,{label:"input",val:g(s.inputTokens)}),a.jsx(y,{label:"output",val:g(s.outputTokens)}),a.jsx(y,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(y,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:q(s.modelBreakdowns).map(n=>{const o=C(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:T(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:O(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:T(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function y({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 fe=[7,14,30,90];function je({onClose:s,providers:e}){var B;const[n,o]=c.useState(30),[r,m]=c.useState(null),{landed:l,loading:d,reload:f}=pe(n),x=c.useRef(null),k=D(s,{focusRef:x}),h=ne({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((B=l.resp.days)==null?void 0:B.length)??0}}),i=h.phase==="chart"?l.resp.days??[]:[],j=c.useRef(null),G=ee(h.phase!=="error",j),H=c.useMemo(()=>i.reduce((t,N)=>Math.max(t,N.totalCost),0),[i]),S=c.useRef(null);c.useLayoutEffect(()=>{const t=S.current;t&&(t.scrollLeft=t.scrollWidth)},[i]);const{totalCost:w,totalTok:V,inOut:W,cacheRead:z,modelCosts:A}=c.useMemo(()=>ue(i),[i]),J=c.useMemo(()=>de(A),[A]),b=c.useMemo(()=>I(i),[i]),K=b.length>1,L=r?i.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:a.jsxs("div",{ref:k,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…":se(e,b.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:fe.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:j,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:te(l.resp)||void 0,children:[ae(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{G(),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(v,{label:"total cost",val:p(w),accent:!0}),a.jsx(v,{label:"tokens",val:g(V)}),a.jsx(v,{label:"input+output",val:g(W)}),a.jsx(v,{label:"cache reads",val:g(z)})]}),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:b.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${C(t.cost,w)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:b.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)}}),U(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:le(t.cost,w)})]},t.id))})]}),a.jsx("div",{ref:S,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:i.map((t,N)=>{const Q=C(t.totalCost,H),R=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${R?" sel":""}`,onClick:()=>m(R?null:t.period),"aria-pressed":R,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/i.length}%`,"--uh-i":N,"--uh-step":`${Math.min(10,200/Math.max(i.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:q(t.modelBreakdowns).map($=>{const Y=C($.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:T($.modelName)}},$.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,N])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:T(t)}}),O(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(N)})]},t))}),L&&a.jsx(ge,{selectedDay:L})]})]})})}function v({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{je as default};
1
+ import{a as U,f as p,r as c,s as Z,p as _,j as a,b as g,c as O,u as D,d as ee,e as se,g as E,h as ae,i as te}from"./index-CgHJ_aHk.js";function ne(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 I(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 oe(s){const e=I([{agents:s}]);return e.length<2?null:e.map(n=>`${U(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 le(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,M=s=>typeof s=="string"?s:"",P=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function re(s){const e=s??{};return{modelName:M(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ce(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:M(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:M(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:P(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(re):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:P(e.metadata.agents)}}:{}}}function ie(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ce):void 0}}function T(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 ue(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 de(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function q(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function C(s,e){return e>0?s/e*100:0}function he(s){var n,o;return oe(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function me(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function pe(s){const[e,n]=c.useState(null),[o,r]=c.useState(!1),m=c.useState(me)[0],l=c.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const k=s;r(!0);const i=`/api/ccusage?since=${_(k)}${f?"&refresh=1":""}`;fetch(i).then(j=>j.json()).then(j=>{x()&&n({range:k,resp:ie(j)})}).catch(()=>{x()&&n({range:k,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return c.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function ge({selectedDay:s}){const e=he(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(y,{label:"input",val:g(s.inputTokens)}),a.jsx(y,{label:"output",val:g(s.outputTokens)}),a.jsx(y,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(y,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:q(s.modelBreakdowns).map(n=>{const o=C(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:T(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:O(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:T(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function y({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 fe=[7,14,30,90];function je({onClose:s,providers:e}){var B;const[n,o]=c.useState(30),[r,m]=c.useState(null),{landed:l,loading:d,reload:f}=pe(n),x=c.useRef(null),k=D(s,{focusRef:x}),h=ne({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((B=l.resp.days)==null?void 0:B.length)??0}}),i=h.phase==="chart"?l.resp.days??[]:[],j=c.useRef(null),G=ee(h.phase!=="error",j),H=c.useMemo(()=>i.reduce((t,N)=>Math.max(t,N.totalCost),0),[i]),S=c.useRef(null);c.useLayoutEffect(()=>{const t=S.current;t&&(t.scrollLeft=t.scrollWidth)},[i]);const{totalCost:w,totalTok:V,inOut:W,cacheRead:z,modelCosts:A}=c.useMemo(()=>ue(i),[i]),J=c.useMemo(()=>de(A),[A]),b=c.useMemo(()=>I(i),[i]),K=b.length>1,L=r?i.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:a.jsxs("div",{ref:k,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…":se(e,b.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:fe.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:j,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:te(l.resp)||void 0,children:[ae(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{G(),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(v,{label:"total cost",val:p(w),accent:!0}),a.jsx(v,{label:"tokens",val:g(V)}),a.jsx(v,{label:"input+output",val:g(W)}),a.jsx(v,{label:"cache reads",val:g(z)})]}),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:b.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${C(t.cost,w)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:b.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)}}),U(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:le(t.cost,w)})]},t.id))})]}),a.jsx("div",{ref:S,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:i.map((t,N)=>{const Q=C(t.totalCost,H),R=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${R?" sel":""}`,onClick:()=>m(R?null:t.period),"aria-pressed":R,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/i.length}%`,"--uh-i":N,"--uh-step":`${Math.min(10,200/Math.max(i.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:q(t.modelBreakdowns).map($=>{const Y=C($.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:T($.modelName)}},$.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,N])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:T(t)}}),O(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(N)})]},t))}),L&&a.jsx(ge,{selectedDay:L})]})]})})}function v({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{je as default};