ccdeck 3.36.8 → 3.37.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 +15 -7
- package/bin/agent-dag.js +19 -6
- package/bin/cli/help.js +1 -0
- package/bin/cli/leaving.js +54 -0
- package/bin/cli/login-item.js +44 -20
- package/bin/cli/one-shot.js +15 -5
- package/bin/cli/screen.js +10 -5
- package/bin/cli/startup.js +6 -2
- package/bin/cli/uninstall.js +45 -0
- package/bin/deck.js +50 -8
- package/dist/web/assets/BrowserWatchModal-DCHV09J8.js +1 -0
- package/dist/web/assets/UsageHistoryModal-oDjpHw4z.js +1 -0
- package/dist/web/assets/index-B0SOa-XB.css +1 -0
- package/dist/web/assets/index-wCbqcRJd.js +306 -0
- package/dist/web/index.html +2 -2
- package/hook/hook.js +80 -9
- package/package.json +1 -1
- package/release-notes.json +3 -1
- package/src/server/account-origins.mjs +8 -1
- package/src/server/account-projects.mjs +34 -9
- package/src/server/activation.mjs +39 -10
- package/src/server/app-host.mjs +7 -2
- package/src/server/args.mjs +3 -0
- package/src/server/browser-watch-routes.mjs +23 -16
- package/src/server/browser-watch.mjs +9 -3
- package/src/server/claude-accounts.mjs +27 -7
- package/src/server/claude-verdicts.mjs +12 -1
- package/src/server/cli-versions.mjs +6 -4
- package/src/server/codex-auth.mjs +29 -0
- package/src/server/codex-quota.mjs +57 -7
- package/src/server/deck-home.mjs +23 -1
- package/src/server/deck-prefs.mjs +70 -7
- package/src/server/depth-facts.mjs +178 -0
- package/src/server/detach.mjs +61 -33
- package/src/server/event-log.mjs +15 -0
- package/src/server/event-pipeline.mjs +5 -2
- package/src/server/event-routes.mjs +22 -0
- package/src/server/feedback-form.mjs +36 -13
- package/src/server/index.mjs +6 -2
- package/src/server/install-layout.mjs +35 -0
- package/src/server/install-ref.mjs +39 -0
- package/src/server/installer.mjs +20 -2
- package/src/server/lan-beacon.mjs +18 -3
- package/src/server/lan-deck.mjs +7 -1
- package/src/server/lan-engine.mjs +10 -1
- package/src/server/lan-manifest.mjs +9 -6
- package/src/server/lan-requests.mjs +18 -2
- package/src/server/lan-serve.mjs +4 -2
- package/src/server/lan-sync.mjs +97 -6
- package/src/server/lifecycle.mjs +18 -2
- package/src/server/log-replay.mjs +9 -3
- package/src/server/login-service.mjs +95 -12
- package/src/server/npm-upgrade.mjs +12 -4
- package/src/server/pinned-build.mjs +3 -0
- package/src/server/prefs-routes.mjs +5 -8
- package/src/server/prefs-state.mjs +9 -1
- package/src/server/quota-oauth.mjs +88 -13
- package/src/server/quota-shape.mjs +28 -0
- package/src/server/quota.mjs +68 -17
- package/src/server/rating.mjs +87 -0
- package/src/server/reports-routes.mjs +21 -2
- package/src/server/reports.mjs +704 -63
- package/src/server/ring-bounds.mjs +11 -0
- package/src/server/running-deck.mjs +48 -0
- package/src/server/self-update.mjs +1 -1
- package/src/server/session-enrichment.mjs +14 -6
- package/src/server/stable-node.mjs +68 -0
- package/src/server/static-serve.mjs +5 -0
- package/src/server/supervisor.mjs +21 -15
- package/src/server/tailscale.mjs +8 -4
- package/src/server/transcript-scan.mjs +7 -4
- package/src/server/usage-day.mjs +76 -11
- package/dist/web/assets/BrowserWatchModal-CeVGLslQ.js +0 -1
- package/dist/web/assets/UsageHistoryModal-BcUzGS2-.js +0 -1
- package/dist/web/assets/index-4ecbC_Cy.js +0 -306
- package/dist/web/assets/index-BkXTN4dZ.css +0 -1
package/bin/deck.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
|
-
import { RESTART_CODE, UPGRADE_CODE, dieWithParent } from "../src/server/supervisor.mjs";
|
|
16
|
+
import { RESTART_CODE, UPGRADE_CODE, dieWithParent, signalExitAction } from "../src/server/supervisor.mjs";
|
|
17
17
|
import { parseArgs, startPort, wantsCli } from "../src/server/args.mjs";
|
|
18
18
|
import { unregisteredDetail } from "../src/server/pulse-line.mjs";
|
|
19
19
|
import { PRODUCT } from "../src/server/brand.mjs";
|
|
@@ -33,7 +33,7 @@ import { offerLoginItem } from "./cli/login-item.js";
|
|
|
33
33
|
import { oneShot } from "./cli/one-shot.js";
|
|
34
34
|
// The terminal the boot draws in: palette, glyphs, rows, the wordmark and the spinner.
|
|
35
35
|
import {
|
|
36
|
-
G, P, fileLink, printBanner, showCursor, step, takeCursor, tty, write,
|
|
36
|
+
G, P, fileLink, onHangup, printBanner, showCursor, step, takeCursor, tty, write,
|
|
37
37
|
} from "./cli/screen.js";
|
|
38
38
|
// The once-per-session work and the rows that report it.
|
|
39
39
|
import { reportReady, reportRestarted, reportStartup, respawnHooks, startupWork } from "./cli/startup.js";
|
|
@@ -200,7 +200,11 @@ const { installHooks, keepDiscovery, removeDiscovery, hasCodexInstalled, leftove
|
|
|
200
200
|
const { startServer, hookToken, releaseRestart, markDeckReady, CODEX_SESSIONS_DIR, canonicalWorkspace } =
|
|
201
201
|
await import(pathToFileURL(join(PKG_ROOT, "src/server/index.mjs")).href);
|
|
202
202
|
// What this boot set up, for the usage reports — see activation.mjs.
|
|
203
|
-
const { noteSetup } = await import(pathToFileURL(join(PKG_ROOT, "src/server/activation.mjs")).href);
|
|
203
|
+
const { noteSetup, respawnHooksJob } = await import(pathToFileURL(join(PKG_ROOT, "src/server/activation.mjs")).href);
|
|
204
|
+
// And the site page the command came from, which only a new install's first
|
|
205
|
+
// report carries — see install-ref.mjs.
|
|
206
|
+
const { noteRef } = await import(pathToFileURL(join(PKG_ROOT, "src/server/install-ref.mjs")).href);
|
|
207
|
+
noteRef(flags.ref);
|
|
204
208
|
|
|
205
209
|
// Resolved here rather than left as typed, for the reason the events log above
|
|
206
210
|
// is: the discovery file publishes this path, and the hook that reads it runs in
|
|
@@ -398,15 +402,33 @@ const starting = startServer({
|
|
|
398
402
|
// the one the session started on — see respawnHooks.
|
|
399
403
|
if (!RESPAWN) {
|
|
400
404
|
const jobs = startupWork({ wantClaude, installHooks, leftoverCodexHooks });
|
|
401
|
-
noteSetup({ claude: jobs.hooks, codex: wantCodex });
|
|
405
|
+
handDownSetup(noteSetup({ claude: jobs.hooks, codex: wantCodex }));
|
|
402
406
|
jobs.cswapQuiet.then(settleCswap);
|
|
403
407
|
await printBanner();
|
|
404
408
|
await reportStartup(jobs, { workspace, wantClaude, wantCodex, CODEX_SESSIONS_DIR });
|
|
405
409
|
} else {
|
|
406
410
|
settleCswap();
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
411
|
+
// What a respawn says about the hooks is what it saw: its own re-install when
|
|
412
|
+
// the package changed under the session, else the first boot's answer as the
|
|
413
|
+
// supervisor handed it down — never an "ok" it assumed. See respawnHooksJob.
|
|
414
|
+
const reinstall = respawnHooks({ wantClaude, installHooks, bootVersion: process.env.AGENTS_DECK_BOOT_VERSION });
|
|
415
|
+
handDownSetup(noteSetup({
|
|
416
|
+
claude: respawnHooksJob({ wantClaude, reinstall, carried: process.env.AGENTS_DECK_BOOT_HOOKS }),
|
|
417
|
+
codex: wantCodex,
|
|
418
|
+
}));
|
|
419
|
+
await reinstall;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** The hooks' answer, told to the supervisor, which hands it to the next
|
|
423
|
+
* respawn in AGENTS_DECK_BOOT_HOOKS: a respawn on the same version installs
|
|
424
|
+
* nothing, and this is how it can still say what the session's hooks are.
|
|
425
|
+
* Guarded, and given a callback, like every send down a channel the other end
|
|
426
|
+
* may have closed. */
|
|
427
|
+
function handDownSetup(said) {
|
|
428
|
+
said.then(({ claudeHooks }) => {
|
|
429
|
+
if (!claudeHooks || !process.connected) return;
|
|
430
|
+
try { process.send({ type: "setup", claudeHooks }, () => {}); } catch { /* nobody supervising */ }
|
|
431
|
+
}).catch(() => {});
|
|
410
432
|
}
|
|
411
433
|
|
|
412
434
|
// Usually settled long ago by the time we get here, which is the point: `step`
|
|
@@ -558,7 +580,9 @@ async function shutdown(code = 0) {
|
|
|
558
580
|
// Before anything that can take time: a Ctrl+C the user has to watch for a
|
|
559
581
|
// second and a half is a second and a half without a cursor.
|
|
560
582
|
showCursor();
|
|
561
|
-
|
|
583
|
+
// Nothing for a terminal that hung up (onHangup, below): it is not there.
|
|
584
|
+
const hungUp = code === hangupCode();
|
|
585
|
+
if (tty && code !== RESTART_CODE && code !== UPGRADE_CODE && !hungUp) {
|
|
562
586
|
write(`\n\n ${P.warn}${G.stop} shutting down${G.ellipsis}${P.reset}\n`);
|
|
563
587
|
}
|
|
564
588
|
// WHAT THIS DECK STARTED, ENDED WITH IT. Every deadline in exec.mjs lives
|
|
@@ -626,6 +650,17 @@ async function shutdown(code = 0) {
|
|
|
626
650
|
}
|
|
627
651
|
process.on("SIGINT", () => shutdown(0));
|
|
628
652
|
process.on("SIGTERM", () => shutdown(0));
|
|
653
|
+
// A terminal closed under a `--foreground` deck, or a console window closed on
|
|
654
|
+
// Windows, which Node reports as SIGHUP. Dying of it on the spot dropped every
|
|
655
|
+
// append already acknowledged, left the discovery record for the hooks and
|
|
656
|
+
// gave the LAN no goodbye, so it takes the way out the other two take. With
|
|
657
|
+
// the hangup's own exit code, so the supervisor reads what it read before; and
|
|
658
|
+
// with nothing written to the terminal that is gone — a write there fails, and
|
|
659
|
+
// an unhandled 'error' on stdout would end the shutdown halfway.
|
|
660
|
+
onHangup(() => {
|
|
661
|
+
for (const out of [process.stdout, process.stderr]) out.on("error", () => {});
|
|
662
|
+
shutdown(hangupCode());
|
|
663
|
+
});
|
|
629
664
|
process.on("beforeExit", () => { discovery?.stop(); if (discoveryFile) removeDiscovery(discoveryFile); });
|
|
630
665
|
// The boot lock's `exit` handler is NOT here with its siblings. It is armed up
|
|
631
666
|
// beside `let bootLock = null;`, above the start gate, because the three ways
|
|
@@ -634,6 +669,13 @@ process.on("beforeExit", () => { discovery?.stop(); if (discoveryFile) removeDis
|
|
|
634
669
|
|
|
635
670
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
636
671
|
|
|
672
|
+
/** The code a death by SIGHUP is reported as, 129: what a hangup that runs
|
|
673
|
+
* shutdown instead of dying of it exits with. A function, like shutdown, so
|
|
674
|
+
* it is there at any instant of the boot. */
|
|
675
|
+
function hangupCode() {
|
|
676
|
+
return signalExitAction("SIGHUP").code;
|
|
677
|
+
}
|
|
678
|
+
|
|
637
679
|
// The deck is up and the discovery file could not be written. Said in full —
|
|
638
680
|
// the path, the reason — because the alternative is what this replaced: an
|
|
639
681
|
// ordinary-looking deck that simply never shows a session.
|
|
@@ -0,0 +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-wCbqcRJd.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};
|
|
@@ -0,0 +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-wCbqcRJd.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};
|