ccdeck 3.38.4 → 3.38.6
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 +8 -4
- package/dist/web/assets/{BrowserWatchModal-Pdx1qMyq.js → BrowserWatchModal-FsBjYpPs.js} +1 -1
- package/dist/web/assets/{UsageHistoryModal-DdeJQWA7.js → UsageHistoryModal-C775SS_q.js} +1 -1
- package/dist/web/assets/index-BKLXyjIh.js +303 -0
- package/dist/web/assets/index-DnJPDhs9.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -1
- package/src/server/thermal-metrics.mjs +100 -29
- package/src/server/thermal-sampler.mjs +8 -4
- package/dist/web/assets/index-6_mJZ_aP.css +0 -1
- package/dist/web/assets/index-FnWpX0X5.js +0 -309
package/README.md
CHANGED
|
@@ -120,6 +120,8 @@ log in. **`ccdeck --stop` is the off switch**; `Ctrl+C` only cancels a start tha
|
|
|
120
120
|
is still printing. `ccdeck --status` says what is running, and `--foreground`
|
|
121
121
|
holds the terminal the way every version before 3.20 did.
|
|
122
122
|
|
|
123
|
+
Everything the page lets you set — the theme, the sounds, notifications, Claude FM and the minimap character — is in **Settings**, behind the gear at the right of the topbar, or `Cmd+,` (`Ctrl+,` on Windows and Linux). A change applies the moment you make it. The sounds and how loud each tone is are in **Settings › Sounds**, which `V` opens straight away; `M` turns sounds off or on from anywhere. `?` lists every key.
|
|
124
|
+
|
|
123
125
|
No config file. No account. What your sessions contain — your prompts, the replies, the files they touch — is never reported anywhere. The deck does send usage reports, on by default: its version, your system, your IP address and a device fingerprint, rough counts such as how many sessions ran in a day, and the errors it hits. It has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start.
|
|
124
126
|
|
|
125
127
|
**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.
|
|
@@ -161,7 +163,7 @@ The machine panel shows a **Thermal** section only where the machine actually an
|
|
|
161
163
|
| | reads | needs |
|
|
162
164
|
| --- | --- | --- |
|
|
163
165
|
| 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 |
|
|
164
|
-
| Windows | the `Thermal Zone Information` performance counter, then `MSAcpi_ThermalZoneTemperature
|
|
166
|
+
| Windows | an ACPI thermal zone, through the `Thermal Zone Information` performance counter, then `MSAcpi_ThermalZoneTemperature` — drawn only once its value has changed; then LibreHardwareMonitor's web server if it happens to be running | nothing — where the machine has an ACPI thermal zone that measures something. Many do not; see below |
|
|
165
167
|
| macOS, Intel | `ioreg` for the GPU, `pmset -g therm` for throttling | nothing |
|
|
166
168
|
| macOS, Apple Silicon | `macmon`, which the deck fetches for you | nothing |
|
|
167
169
|
|
|
@@ -184,9 +186,11 @@ Windows is the platform where this most often shows nothing, and that is not a d
|
|
|
184
186
|
|
|
185
187
|
That is a class of machine, not a fault: modern Intel laptops moved thermal management into Intel DTT and stopped declaring the ACPI zones that Windows exposes to ordinary programs. There is no standard user-mode Windows API for CPU temperature — which is why HWiNFO, Core Temp and LibreHardwareMonitor all install a kernel driver, and why this deck does not.
|
|
186
188
|
|
|
187
|
-
Where the counter does have instances — many desktop boards, servers, and older laptops — it is read without any privileges at all.
|
|
189
|
+
Where the counter does have instances — many desktop boards, servers, and older laptops — it is read without any privileges at all. A Windows in another language names the counter in that language, and the deck looks the local name up when the English one answers nothing; that lookup has not yet been run on a non-English Windows.
|
|
190
|
+
|
|
191
|
+
**A thermal zone is not the CPU, and not always a sensor.** A zone measures whatever the machine's firmware wired it to — the case surface, the board, or the embedded controller's hottest reading — and on many laptops it is wired to nothing. Intel's reference firmware, which most laptop BIOSes start from, answers a fixed 27.85 °C when the zone has no source, so the counter reads 28 °C on every sample however hot the CPU is. The deck therefore draws a zone only once its value has changed — Microsoft's own test of a working zone is that its temperature changes under a varying workload — and names the row *Thermal zone*, or `TZ00`, `TZ01` when there are two, never *CPU*. The cost is a short wait: a real zone that holds one value from the moment the deck starts appears when it first changes, and stays from then on.
|
|
188
192
|
|
|
189
|
-
One thing does work on the machines above, and it costs you nothing to have: if **LibreHardwareMonitor** happens to be running with its web server on, the deck reads its numbers over plain HTTP on localhost, which needs no privileges. That is a read, not a request — the deck does not install it, will not ask you to, and shows no section if it is not there. It is mentioned only so nobody is surprised to see degrees appear on a machine that had none.
|
|
193
|
+
One thing does work on the machines above, and on the ones whose zone never moves, and it costs you nothing to have: if **LibreHardwareMonitor** happens to be running with its web server on, the deck reads its numbers over plain HTTP on localhost, which needs no privileges. That is a read, not a request — the deck does not install it, will not ask you to, and shows no section if it is not there. It is mentioned only so nobody is surprised to see degrees appear on a machine that had none.
|
|
190
194
|
|
|
191
195
|
## How it works
|
|
192
196
|
|
|
@@ -233,7 +237,7 @@ The deck installs that package itself, so it installs it **bounded**: `claude-sw
|
|
|
233
237
|
|
|
234
238
|
**Switch** on an account's row makes it the active one. New sessions start on it; sessions already running are not restarted, and pick it up on their next message — up to about 30 seconds later on macOS. **Auto-switch**, off until you turn it on, does the same without a press: about once a minute the deck runs claude-swap's own engine, which, once the active account passes your threshold (90% unless you choose another) of its 5-hour or 7-day window, moves Claude Code to the account with the most room left. Every account keeps its own limits; when no other account is under the threshold, nothing switches.
|
|
235
239
|
|
|
236
|
-
**Account notifications** are three switches in
|
|
240
|
+
**Account notifications** are three switches in **Settings › Notifications** (the gear in the topbar, or `Cmd+,`; `Ctrl+,` on Windows and Linux), each on its own and none of them tied to **Notifications while closed**: they are sent whether the deck is open or not, because nothing on the page says them. The desktop app shows them as ccdeck, without a sound.
|
|
237
241
|
|
|
238
242
|
- **Account auto-switched**, on unless you turn it off. When Auto-switch moves Claude Code to another account, one notification says where to and, when the engine's own reading shows it, why: `Switched to work · personal@example.com reached 90%`, with an account's alias when it has one. A switch you make yourself is not announced.
|
|
239
243
|
- **Quota at 90% and 100%**, off until you turn it on. When a window of the Claude or Codex account you are on reaches 90%, and once more at 100%: Claude's 5-hour, 7-day and per-model windows, and every Codex window the Usage panel shows, the extra limits included — `5-hour usage reached 90% · resets in 2h 14m`. Only the account in use is watched, not every account you have stored. When reaching it is what makes Auto-switch move the account, the switch notification says it and this one is not sent, unless the switch notification is off.
|
|
@@ -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-FnWpX0X5.js";function te({snap:s}){return e.jsxs("section",{className:"bw-feed",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Live activity",e.jsxs("span",{className:"bw-feed-count",children:[s.log.length," ",s.log.length===1?"event":"events"]})]}),e.jsx("p",{className:"bw-since bw-feed-note",children:"Pages you opened yourself — counted to run the rule, never written down. Addresses appear under Findings, and only for pages a program opened."}),e.jsx("div",{className:"bw-log","aria-live":"polite","aria-relevant":"additions",children:s.log.length===0?e.jsx("p",{className:"bw-note",children:"The deck writes a line here each time it looks at a profile."}):s.log.map(n=>e.jsxs("div",{className:`bw-log-line ${n.level}${n.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(n.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),n.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:n.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:n.parts.profile,children:n.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[n.parts.value,n.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",n.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:n.text})]},`${n.atMs}-${n.level}-${n.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function ne(s){const n=o=>new Date(o).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?n(s.startMs):`${n(s.startMs)} → ${n(s.endMs)}`}function ae(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const n=Math.round((s.endMs-s.startMs)/6e4);return n<1?"":`${n} min`}function re({snap:s,dismiss:n,refreshRef:o}){const[i,d]=l.useState(null),u=l.useRef(new Map),t=l.useRef(null);l.useEffect(()=>{var r,w;const c=t.current;!c||s.episodes.some(b=>E(b)===c.id)||(t.current=null,_(((r=document.activeElement)==null?void 0:r.tagName)??null)&&((w=(c.next?u.current.get(c.next):void 0)??o.current)==null||w.focus()))},[s]);const a=c=>{t.current={id:E(c),next:V(s.episodes.map(E),E(c))},n(c)},h=l.useMemo(()=>{const c=[];for(const r of s.episodes){const w=ae(r.startMs),b=c[c.length-1];b&&b.label===w?b.episodes.push(r):c.push({label:w,episodes:[r]})}return c},[s]);return e.jsxs("section",{className:"bw-findings",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Findings",s.episodes.length>0&&e.jsxs("span",{className:"bw-feed-count",children:[s.episodes.length," ",s.episodes.length===1?"episode":"episodes"]})]}),e.jsx("div",{className:"bw-eps",children:h.length===0?e.jsxs("div",{className:"bw-empty",children:[e.jsx("p",{className:"bw-empty-head",children:"Nothing found yet"}),e.jsxs("p",{className:"bw-empty-note",children:["An episode lands here when a program opens pages in a browser nobody has touched for"," ",s.settings.quietMinutes," ",s.settings.quietMinutes===1?"minute":"minutes",". On most machines that is rare, so an empty list is the ordinary result rather than a sign something is wrong."]}),!s.settings.enabled&&e.jsx("p",{className:"bw-empty-note",children:"Watching is off, so this shows only what this deck has seen since it started. Anything an earlier run recorded comes back when you switch it on."})]}):h.map(c=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:c.label}),c.episodes.map(r=>{const w=E(r),b=i===w;return e.jsxs("div",{className:`bw-ep${b?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:f=>{f?u.current.set(w,f):u.current.delete(w)},onClick:()=>d(b?null:w),"aria-expanded":b,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:b?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:r.host}),e.jsxs("span",{className:"bw-ep-meta",children:[ne(r),U(r)&&e.jsxs(e.Fragment,{children:[" · ",U(r)]}),r.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[r.count," ",r.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>a(r),"aria-label":`Dismiss ${r.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),b&&e.jsx("ul",{className:"bw-urls",children:r.urls.map((f,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(f.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:f.url})]},`${f.url}-${f.timeMs}-${y}`))})]},w)})]},c.label))})]})}function ie(s){const n=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!n)return!1;const o=parseInt(n[1],16),i=[o>>16&255,o>>8&255,o&255].map(d=>{const u=d/255;return u<=.03928?u/12.92:Math.pow((u+.055)/1.055,2.4)});return .2126*i[0]+.7152*i[1]+.0722*i[2]>.5}const G=1e4,oe=12e3;function le({browsers:s,findings:n,watching:o,palette:i}){const d=l.useRef(null),u=l.useRef(null);return u.current={browsers:s,findings:n,watching:o},l.useEffect(()=>{var b;const t=d.current;if(!t)return;const a=t.getContext("2d");if(!a)return;const h=((b=window.matchMedia)==null?void 0:b.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let c=0,r=!1;const w=()=>{var q;if(r)return;const f=u.current;if(!f)return;const{browsers:y,findings:j,watching:C}=f,S=i["--accent"]||"#7dd3fc",g=i["--line"]||"#1f2229",v=i["--text-dim"]||"#6e727c",P=ie(i["--bg"]),N=P?42:26,k=P?.85:.65,A=Date.now(),R=Math.min(window.devicePixelRatio||1,2),$=t.clientWidth,W=t.clientHeight;(t.width!==$*R||t.height!==W*R)&&(t.width=$*R,t.height=W*R),a.setTransform(R,0,0,R,0,0),a.clearRect(0,0,$,W);const m=$/2,x=W/2,M=Math.min($,W)/2-6;a.strokeStyle=g,a.lineWidth=1,a.globalAlpha=P?1:.9;for(const p of[.4,.7,1])a.beginPath(),a.arc(m,x,M*p,0,Math.PI*2),a.stroke();a.beginPath(),a.moveTo(m-M,x),a.lineTo(m+M,x),a.moveTo(m,x-M),a.lineTo(m,x+M),a.stroke(),a.globalAlpha=1;const T=(C&&!h?A%G/G:0)*Math.PI*2-Math.PI/2;if(C){const p=(q=a.createConicGradient)==null?void 0:q.call(a,T-.85,m,x);p&&(p.addColorStop(0,"transparent"),p.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${S} ${N}%, transparent)`),p.addColorStop(1,"transparent"),a.fillStyle=p,a.beginPath(),a.moveTo(m,x),a.arc(m,x,M,T-.85,T),a.closePath(),a.fill()),a.strokeStyle=S,a.globalAlpha=k,a.beginPath(),a.moveTo(m,x),a.lineTo(m+Math.cos(T)*M,x+Math.sin(T)*M),a.stroke(),a.globalAlpha=1}y.forEach((p,I)=>{const B=I/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=p.lastReadMs===null?1:Math.min(1,(A-p.lastReadMs)/12e4),O=M*(.42+D*.5),F=m+Math.cos(B)*O,L=x+Math.sin(B)*O,Q=Math.abs((T-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=C?Math.max(0,1-Q/.55):0;a.beginPath(),a.arc(F,L,3+K*1.6,0,Math.PI*2),a.fillStyle=p.running?S:v,a.globalAlpha=p.running?.55+K*.45:.35,a.fill(),a.globalAlpha=1});for(const p of j){const I=(A-p.atMs)/oe;if(I<0||I>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===p.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=m+Math.cos(D)*M*.6,F=x+Math.sin(D)*M*.6;a.beginPath(),a.arc(O,F,4+I*22,0,Math.PI*2),a.strokeStyle=S,a.globalAlpha=(1-I)*.8,a.lineWidth=1.5,a.stroke(),a.globalAlpha=1}c=requestAnimationFrame(w)};return w(),()=>{r=!0,cancelAnimationFrame(c)}},[i]),e.jsx("canvas",{ref:d,className:"bw-radar",role:"img","aria-label":o?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function ce(s,n,o){if(s===null)return null;const i=s+n-o;return i>0?i:null}function de(s){const n=Math.ceil(s/1e3),o=Math.floor(n/60),i=n%60;return o>0?`${o}m ${i}s`:`${i}s`}function J(s){return(s??[]).filter(n=>n.installed&&n.profiles>0)}function he(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function ue(s,n){return s.map(o=>({key:o.key,name:o.name,running:o.running,visits:n.filter(i=>i.browser===o.key).reduce((i,d)=>i+d.programVisits,0)}))}function we(s,n){if(s===null)return null;const o=Math.max(0,Math.round((n-s)/1e3));if(o<5)return"just now";if(o<60)return`${o} sec ago`;const i=Math.round(o/60);return i<60?`${i} min ago`:`${Math.round(i/60)} hr ago`}function me(s){const n=s.profiles.filter(i=>i.degraded);if(n.length>0){const i=n[0],d=n.length-1;return{kind:"unreadable",text:`${i.name}/${i.profile} could not be read — ${i.reason??"no reason given"}`+(d>0?`, and ${d} more`:"")+". The watch continues on the profiles it can read."}}const o=J(s.browsers);return o.length===0?{kind:"no-profiles",text:"No browser on this machine has a profile to read. Open one of the browsers below once and it will be watched from then on."}:o.some(i=>i.running)?null:{kind:"none-running",text:"No watched browser is running, so nothing can be navigated right now. Their history is still read, and anything found while they were open is still here."}}function be({snap:s,watching:n,palette:o}){const[i,d]=l.useState(!1),[u,t]=l.useState(()=>Date.now());l.useEffect(()=>{const h=setInterval(()=>t(Date.now()),1e3);return()=>clearInterval(h)},[]);const a=ce(s.coverage.lastHumanMs,s.coverage.quietMs,u);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Activity overview",e.jsx("button",{className:"bw-help",onClick:()=>d(h=>!h),"aria-expanded":i,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(le,{browsers:n.map(h=>{var c;return{key:h.key,name:h.name,running:h.running,lastReadMs:((c=s.profiles.find(r=>r.browser===h.key))==null?void 0:c.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(h=>({browser:h.browser??null,atMs:h.endMs})),watching:s.settings.enabled,palette:o}),e.jsx("ul",{className:"bw-counts",children:ue(n,s.profiles).map(h=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:h.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:h.name})]},h.key))})]}),e.jsx("p",{className:"bw-since",children:"Pages a program opened, since this deck started"}),s.coverage.checkedMs>0&&e.jsxs("p",{className:"bw-since",children:["Last checked ",we(s.coverage.checkedMs,u),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),a!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",de(a)]}),i&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function fe({watching:s,rest:n}){const[o,i]=l.useState(!1),[d,u]=l.useState(!1);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Watched profiles",e.jsx("button",{className:"bw-help",onClick:()=>i(t=>!t),"aria-expanded":o,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),o&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Running"}),e.jsx("dd",{children:"the browser has a process on this machine right now"}),e.jsx("dt",{children:"Connected"}),e.jsx("dd",{children:"an open connection to an address Anthropic's relay uses — which it shares with claude.ai, so an open tab looks the same as an agent channel"})]}),e.jsx("ul",{className:"bw-profiles",children:s.map(t=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${t.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:t.name}),e.jsx("span",{className:"bw-prof-state",children:t.running?"running":"idle"}),t.running&&t.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},t.key))}),n.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>u(t=>!t),"aria-expanded":d,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:d?"▾":"▸"})," ",n.length," not watched"]}),d&&e.jsxs("p",{className:"bw-rest",children:[n.map(t=>t.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function ge({snap:s,quiet:n,setQuiet:o,save:i,access:d,setAccess:u}){return e.jsxs("div",{className:"bw-settings",children:[e.jsxs("label",{title:"A program opening a page counts as a finding only if nobody had touched the browser for this long. Shorter catches more and reports more of your own work; longer is quieter.",children:[e.jsx("span",{children:"Nobody browsing for"}),e.jsxs("select",{value:n??s.settings.quietMinutes,onChange:t=>{o(Number(t.target.value)),i({quietMinutes:Number(t.target.value)})},children:[e.jsx("option",{value:1,children:"1 min"}),e.jsx("option",{value:5,children:"5 min"}),e.jsx("option",{value:15,children:"15 min"}),e.jsx("option",{value:30,children:"30 min"}),e.jsx("option",{value:60,children:"60 min"})]})]}),e.jsxs("label",{children:[e.jsx("span",{children:"When it finds one"}),e.jsx("select",{value:s.settings.reaction,onChange:t=>void i({reaction:t.target.value}),disabled:!s.settings.enabled,title:s.settings.enabled?"What to do besides writing it down.":"Turn watching on to arm a reaction.",children:(s.reactions??["notify"]).map(t=>e.jsx("option",{value:t,children:t==="notify"?"notify me":t==="close-tab"?"close the tab":"quit the browser"},t))})]}),e.jsxs("div",{className:"bw-settings-note",children:[e.jsxs("p",{children:["Chrome marks a navigation that came from an extension or a command rather than from a click. These are the ones that happened while nobody had touched the browser for the time above. ",e.jsx("strong",{children:"Usually that is your own agent doing what you asked."})]}),e.jsxs("button",{className:"bw-why",onClick:()=>u(t=>!t),"aria-expanded":d,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:d?"▾":"▸"})," What Browser Watch can access"]}),d&&e.jsxs("dl",{className:"bw-access",children:[e.jsx("dt",{children:"Reads"}),e.jsxs("dd",{children:["A copy of each browser's own history database — the live file is locked while the browser holds it. Only rows newer than the moment this deck started:"," ",new Date(s.coverage.startedMs).toLocaleString(),"."]}),e.jsx("dt",{children:"Keeps"}),e.jsxs("dd",{children:["Only while the switch is on, and only the episodes it flagged — never your ordinary browsing. In ",e.jsx("code",{className:"bw-path",children:s.coverage.logPath}),typeof s.coverage.logBytes=="number"?` (${he(s.coverage.logBytes)})`:"",", with every address written in full so you can check it yourself."]}),e.jsx("dt",{children:"Sends"}),e.jsx("dd",{children:"Nothing. No part of this reads or writes over the network; the deck serves on 127.0.0.1 and this panel talks only to it."}),e.jsx("dt",{children:"Never reads"}),e.jsx("dd",{children:"Anything from before this deck started, cookies, saved passwords, page contents, or any browser profile with no history file."})]})]})]})}function H(s,n){return n?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function pe({snap:s,saving:n,save:o,why:i,setWhy:d}){return e.jsxs("footer",{className:"bw-status",children:[e.jsx("span",{className:`bw-mode-dot${s.settings.enabled?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-mode-word",children:H(s,n).word},H(s,n).kind),e.jsx("span",{className:"bw-status-text",children:s.coverage.archived===0?s.settings.enabled?"No episodes recorded yet":"No episodes on disk":`${s.coverage.archived} ${s.coverage.archived===1?"episode":"episodes"} on disk`}),e.jsx("button",{className:`glyph-btn bw-gear${i?" on":""}`,onClick:()=>d(u=>!u),"aria-expanded":i,"aria-label":"Settings",title:"Settings",children:e.jsxs("svg",{width:"13",height:"13",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.3","aria-hidden":!0,children:[e.jsx("circle",{cx:"7",cy:"7",r:"2.1"}),e.jsx("path",{d:"M7 1.2v1.6M7 11.2v1.6M1.2 7h1.6M11.2 7h1.6M2.9 2.9l1.1 1.1M10 10l1.1 1.1M11.1 2.9L10 4M4 10l-1.1 1.1"})]})}),e.jsxs("label",{className:"bw-switch",htmlFor:"bw-enabled",children:[e.jsx("span",{className:"bw-switch-label",id:"bw-enabled-label",children:"Watch browser activity"}),e.jsx("button",{id:"bw-enabled",type:"button",role:"switch","aria-checked":s.settings.enabled,"aria-labelledby":"bw-enabled-label",className:"switch",onClick:()=>void o({enabled:!s.settings.enabled}),title:s.settings.enabled?"On — every episode it finds is written down, so the list outlives the browsing history being cleared":"Off — showing only what this deck has seen since it started. Anything an earlier run archived is hidden until you switch back on, and nothing new is kept",children:e.jsx("span",{className:"switch-knob"})})]})]})}function xe({relay:s}){const[n,o]=l.useState(!1),[i,d]=l.useState(!1),u=s.killswitch.blocked?"unblock":"block",t=s.command[u],a=l.useCallback(async()=>{await z(t.command)&&(d(!0),window.setTimeout(()=>d(!1),1600))},[t.command]),h=s.profiles.filter(r=>{var w;return(w=r.report)==null?void 0:w.present}),c=s.profiles.filter(r=>r.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(r=>e.jsx("code",{className:"bw-path",children:r},r))]}),e.jsxs("button",{className:"bw-why",onClick:()=>o(r=>!r),"aria-expanded":n,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:n?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),n&&e.jsxs("div",{className:"bw-rc-body",children:[h.length>0&&e.jsx("dl",{className:"bw-key",children:h.map(r=>e.jsxs(l.Fragment,{children:[e.jsxs("dt",{children:[r.name,r.profile==="Default"?"":` · ${r.profile}`]}),e.jsxs("dd",{children:[r.report.enabled?"enabled":"installed but switched off",r.report.allUrls?", can act on every site":", limited to the sites it was granted",r.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",r.report.sensitiveApis.join(", ")]})]})]},`${r.browser}/${r.profile}`))}),c.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[c.map(r=>r.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),h.some(r=>r.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:t.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:a,children:i?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:t.note})]})]})}function je(s){const[n,o]=l.useState(null),[i,d]=l.useState(null),[u,t]=l.useState(null),[a,h]=l.useState(!1),c=l.useRef(!1),[r,w]=l.useState(null),[b,f]=l.useState(!1),y=l.useRef(!1),j=l.useCallback(async g=>{var v,P;if(!(g&&!X(c.current))){g&&(c.current=!0,h(!0));try{const N=await fetch(`/api/browser-watch${g?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const k=await N.json();o(k),y.current||w(((v=k==null?void 0:k.settings)==null?void 0:v.quietMinutes)??15),s(((P=k==null?void 0:k.settings)==null?void 0:P.enabled)===!0),d(null)}catch(N){d(N instanceof Error?N.message:String(N))}finally{g&&(c.current=!1,h(!1))}}},[s]);l.useEffect(()=>{j(!1)},[j]),l.useEffect(()=>{const g=setInterval(()=>{j(!1)},1e4);return()=>clearInterval(g)},[j]);const C=l.useCallback(async g=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:g.host,startMs:g.startMs})})}finally{await j(!1)}},[j]),S=l.useCallback(async g=>{y.current=!0,f(!0);try{const v=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(g)});if(!v.ok)throw new Error(`the deck answered ${v.status}`);await j(!1),t(null)}catch(v){t(v instanceof Error?v.message:String(v))}finally{y.current=!1,f(!1)}},[j]);return{snap:n,error:i,writeError:u,setWriteError:t,busy:a,quiet:r,setQuiet:w,saving:b,load:j,dismiss:C,save:S}}function Ne({onClose:s,onSeen:n,onWatching:o,palette:i}){const d=Z(s),u=ee(s),{snap:t,error:a,writeError:h,setWriteError:c,busy:r,quiet:w,setQuiet:b,saving:f,load:y,dismiss:j,save:C}=je(o),[S,g]=l.useState(!1),[v,P]=l.useState(!1),N=l.useRef(null),k=l.useRef(n);k.current=n,l.useEffect(()=>()=>k.current(Date.now()),[]);const A=J(t==null?void 0:t.browsers),R=((t==null?void 0:t.browsers)??[]).filter(m=>!(m.installed&&m.profiles>0)),$=t?me(t):null,W=A.reduce((m,x)=>m+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",...u,role:"presentation",children:e.jsxs("div",{ref:d,className:"modal bw-modal",onClick:m=>m.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"bw-title",children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"bw-title",children:"Browser watch"}),e.jsx("span",{className:"modal-tool-id",children:t?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:N,className:"glyph-btn",onClick:()=>void y(!0),...se(r),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:r?"…":"↻"}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]})]}),e.jsxs("div",{className:"modal-body bw-body",children:[!t&&!a&&e.jsxs("div",{className:"bw-loading",children:[e.jsx("span",{className:"bw-loading-bar","aria-hidden":!0}),e.jsx("p",{children:"Reading each browser's history. The first look copies the file, which takes a moment."})]}),a&&e.jsx("div",{className:"bw-state",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not read"}),e.jsx("span",{className:"bw-row-detail",children:a})]})}),t&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),t&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(be,{snap:t,watching:A,palette:i}),e.jsx(fe,{watching:A,rest:R}),t.relay&&e.jsx(xe,{relay:t.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(re,{snap:t,dismiss:j,refreshRef:N}),e.jsx(te,{snap:t})]})]}),t&&S&&e.jsx(ge,{snap:t,quiet:w,setQuiet:b,save:C,access:v,setAccess:P}),h&&e.jsx("div",{className:"bw-state bw-write-err",role:"alert",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not save that setting"}),e.jsxs("span",{className:"bw-row-detail",children:[h," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>c(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),t&&e.jsx(pe,{snap:t,saving:f,save:C,why:S,setWhy:g})]})]})})}export{Ne as default};
|
|
1
|
+
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-BKLXyjIh.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-
|
|
1
|
+
import{a as O,f as p,r as i,s as Z,p as _,j as a,b as g,c as I,u as ee,d as se,e as ae,g as te,h as E,i as ne,k as oe}from"./index-BKLXyjIh.js";function le(s){return!s.answer||s.answer.range!==s.want?{phase:"busy",stale:!1}:s.answer.ok?{phase:s.answer.days>0?"chart":"empty",stale:s.loading}:{phase:"error",stale:s.loading}}function q(s){const e=new Map;for(const n of s)for(const o of n.agents??[]){if(!o||typeof o.agent!="string"||o.agent==="")continue;const r=e.get(o.agent)??{id:o.agent,cost:0,tokens:0};r.cost+=Number.isFinite(o.totalCost)?o.totalCost:0,r.tokens+=Number.isFinite(o.totalTokens)?o.totalTokens:0,e.set(o.agent,r)}return[...e.values()].sort((n,o)=>o.cost-n.cost||n.id.localeCompare(o.id))}function re(s){const e=q([{agents:s}]);return e.length<2?null:e.map(n=>`${O(n.id)} ${p(n.cost)}`).join(" · ")}function F(s){switch(s.toLowerCase()){case"claude":return"var(--model-opus)";case"codex":return"var(--model-codex)";case"gemini":return"var(--model-gemini)";case"copilot":return"var(--model-haiku)";default:return"var(--model-other)"}}function ce(s,e){if(!(e>0)||!(s>0))return"0%";const n=s/e*100;return n<.1?"<0.1%":`${n.toFixed(1)}%`}const u=s=>typeof s=="number"&&Number.isFinite(s)?s:0,$=s=>typeof s=="string"?s:"",U=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function ie(s){const e=s??{};return{modelName:$(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ue(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:$(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:$(e.period),totalCost:u(e.totalCost),totalTokens:u(e.totalTokens),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens),modelsUsed:U(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(ie):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:U(e.metadata.agents)}}:{}}}function de(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ue):void 0}}function C(s){const e=s.toLowerCase();return e.includes("opus")?"var(--model-opus)":e.includes("sonnet")?"var(--model-sonnet)":e.includes("haiku")?"var(--model-haiku)":e.includes("gpt-5")||e.includes("gpt5")?"var(--model-gpt5)":e.includes("gpt")?"var(--model-gpt)":e.includes("gemini")?"var(--model-gemini)":e.includes("codex")?"var(--model-codex)":e.includes("fable")||e.includes("mythos")?"var(--model-fable)":"var(--model-other)"}function he(s){let e=0,n=0,o=0,r=0;const m=new Map;for(const l of s){e+=l.totalCost,n+=l.totalTokens,o+=l.inputTokens+l.outputTokens,r+=l.cacheReadTokens;for(const d of l.modelBreakdowns)m.set(d.modelName,(m.get(d.modelName)??0)+d.cost)}return{totalCost:e,totalTok:n,inOut:o,cacheRead:r,modelCosts:m}}function me(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function G(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function v(s,e){return e>0?s/e*100:0}function pe(s){var n,o;return re(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function ge(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function fe(s){const[e,n]=i.useState(null),[o,r]=i.useState(!1),m=i.useState(ge)[0],l=i.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const j=s;r(!0);const h=`/api/ccusage?since=${_(j)}${f?"&refresh=1":""}`;fetch(h).then(c=>c.json()).then(c=>{x()&&n({range:j,resp:de(c)})}).catch(()=>{x()&&n({range:j,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return i.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function xe({selectedDay:s}){const e=pe(s);return a.jsxs("div",{className:"uh-detail",children:[a.jsxs("div",{className:"uh-detail-head",children:[a.jsx("span",{className:"uh-detail-date",children:s.period}),a.jsx("span",{className:"uh-detail-cost",children:p(s.totalCost)}),e&&a.jsx("span",{className:"uh-detail-agents",title:e,children:e})]}),a.jsxs("div",{className:"uh-detail-mini",children:[a.jsx(b,{label:"input",val:g(s.inputTokens)}),a.jsx(b,{label:"output",val:g(s.outputTokens)}),a.jsx(b,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(b,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:G(s.modelBreakdowns).map(n=>{const o=v(n.cost,s.totalCost);return a.jsxs("div",{className:"uh-model-row",title:n.modelName,children:[a.jsxs("span",{className:"uh-model-name",children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:I(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:C(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function b({label:s,val:e}){return a.jsxs("div",{className:"uh-ministat",children:[a.jsx("span",{className:"uh-ministat-val",children:e}),a.jsx("span",{className:"uh-ministat-label",children:s})]})}const je=[7,14,30,90];function Ne({onClose:s,providers:e}){var P;const[n,o]=i.useState(30),[r,m]=i.useState(null),{landed:l,loading:d,reload:f}=fe(n),x=i.useRef(null),j=ee(s,{focusRef:x}),S=se(s),h=le({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((P=l.resp.days)==null?void 0:P.length)??0}}),c=h.phase==="chart"?l.resp.days??[]:[],M=i.useRef(null),H=ae(h.phase!=="error",M),V=i.useMemo(()=>c.reduce((t,k)=>Math.max(t,k.totalCost),0),[c]),A=i.useRef(null);i.useLayoutEffect(()=>{const t=A.current;t&&(t.scrollLeft=t.scrollWidth)},[c]);const{totalCost:T,totalTok:W,inOut:z,cacheRead:D,modelCosts:L}=i.useMemo(()=>he(c),[c]),J=i.useMemo(()=>me(L),[L]),N=i.useMemo(()=>q(c),[c]),K=N.length>1,B=r?c.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",...S,role:"presentation",children:a.jsxs("div",{ref:j,className:"modal uh-modal",onClick:t=>t.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":"Usage history",children:[a.jsxs("header",{className:"uh-head",children:[a.jsxs("div",{className:"uh-titlewrap",children:[a.jsx("div",{className:"uh-title",children:"Usage history"}),a.jsx("div",{className:"uh-sub",children:h.stale?"running ccusage…":te(e,N.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:je.map(t=>a.jsxs("button",{type:"button","aria-pressed":n===t,className:"uh-range-btn",onClick:()=>{o(t),m(null)},children:[t,"d"]},t))}),a.jsx("button",{ref:M,className:"glyph-btn uh-reload",onClick:f,...E(d),title:"Re-run ccusage","aria-label":"Reload",children:d?"…":"↻"}),a.jsx("button",{ref:x,className:"glyph-btn uh-close",onClick:s,"aria-label":"Close",children:"×"})]}),h.phase==="busy"?a.jsxs("div",{className:"uh-status uh-status-wait","aria-busy":"true",children:[a.jsx("span",{className:"uh-wait-word",children:l?"running ccusage…":"running ccusage… (first run downloads the package)"}),a.jsx("span",{className:"uh-wait-axis","aria-hidden":!0})]}):h.phase==="error"?a.jsxs("div",{className:"uh-status uh-err",title:oe(l.resp)||void 0,children:[ne(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{H(),f()},...E(d),children:d?"trying…":"Try again"})})]}):h.phase==="empty"?a.jsx("div",{className:`uh-status${h.stale?" uh-stale":""}`,"aria-busy":h.stale||void 0,children:"no usage in this range"}):a.jsxs("div",{className:h.stale?"uh-stale":void 0,"aria-busy":h.stale||void 0,children:[a.jsxs("div",{className:"uh-totals",children:[a.jsx(y,{label:"total cost",val:p(T),accent:!0}),a.jsx(y,{label:"tokens",val:g(W)}),a.jsx(y,{label:"input+output",val:g(z)}),a.jsx(y,{label:"cache reads",val:g(D)})]}),K&&a.jsxs("div",{className:"uh-agents",role:"group","aria-label":"Cost by CLI",children:[a.jsx("div",{className:"uh-agent-bar","aria-hidden":"true",children:N.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${v(t.cost,T)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:N.map(t=>a.jsxs("span",{className:"uh-agent-key",title:`${t.id} · ${g(t.tokens)} tokens`,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:F(t.id)}}),O(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:ce(t.cost,T)})]},t.id))})]}),a.jsx("div",{ref:A,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:c.map((t,k)=>{const Q=v(t.totalCost,V),w=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${w?" sel":""}`,onClick:()=>m(w?null:t.period),"aria-pressed":w,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/c.length}%`,"--uh-i":k,"--uh-step":`${Math.min(10,200/Math.max(c.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:G(t.modelBreakdowns).map(R=>{const Y=v(R.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:C(R.modelName)}},R.modelName)})}),a.jsx("span",{className:"uh-bar-label",children:t.period.slice(5)})]},t.period)})}),a.jsx("div",{className:"uh-legend",children:J.map(([t,k])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:C(t)}}),I(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(k)})]},t))}),B&&a.jsx(xe,{selectedDay:B})]})]})})}function y({label:s,val:e,accent:n}){return a.jsxs("div",{className:"uh-stat",children:[a.jsx("span",{className:`uh-stat-val${n?" accent":""}`,children:e}),a.jsx("span",{className:"uh-stat-label",children:s})]})}export{Ne as default};
|