clauddy 1.23.0 → 1.24.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 +34 -16
- package/auth.js +9 -1
- package/codex.js +220 -0
- package/main.js +87 -7
- package/package.json +3 -1
- package/preload.js +5 -1
- package/renderer/index.html +132 -68
- package/renderer/motion.css +120 -2
- package/renderer/pet.js +301 -43
- package/renderer/style.css +583 -52
package/renderer/pet.js
CHANGED
|
@@ -136,6 +136,9 @@ function fmtTokens(t) {
|
|
|
136
136
|
}
|
|
137
137
|
function fmtReset(ms) {
|
|
138
138
|
if (!ms || ms <= 0) return 'now'
|
|
139
|
+
// past a day, hours are the useful grain: "6d 16h", not "160h 57m"
|
|
140
|
+
if (ms >= 86400000)
|
|
141
|
+
return `${Math.floor(ms / 86400000)}d ${Math.floor((ms % 86400000) / 3600000)}h`
|
|
139
142
|
const h = Math.floor(ms / 3600000)
|
|
140
143
|
const m = Math.floor((ms % 3600000) / 60000)
|
|
141
144
|
return h > 0 ? `${h}h ${m}m` : `${m}m`
|
|
@@ -468,28 +471,203 @@ function celebrate() {
|
|
|
468
471
|
for (let i = 0; i < 24; i++) spawnConfetti(i)
|
|
469
472
|
}
|
|
470
473
|
|
|
474
|
+
// ---- services ---------------------------------------------------------------
|
|
475
|
+
// Claude and Codex are monitored side by side, but the panel shows one at a
|
|
476
|
+
// time: the tabs under the pet pick which one owns the chip, the pet, the
|
|
477
|
+
// meters, the breakdowns and the Usage arrow.
|
|
478
|
+
const PROVIDER_KEY = 'clauddy.provider'
|
|
479
|
+
let codexData = null // last Codex payload; null while Codex isn't enabled
|
|
480
|
+
let pickedProvider = (() => {
|
|
481
|
+
try {
|
|
482
|
+
return localStorage.getItem(PROVIDER_KEY) === 'codex' ? 'codex' : 'claude'
|
|
483
|
+
} catch {
|
|
484
|
+
return 'claude'
|
|
485
|
+
}
|
|
486
|
+
})()
|
|
487
|
+
|
|
488
|
+
const claudeOn = () => document.body.classList.contains('auth-on')
|
|
489
|
+
const codexOn = () => !!currentConfig.codex
|
|
490
|
+
const bothOn = () => claudeOn() && codexOn()
|
|
491
|
+
// the service on screen: the pick, unless that one isn't there any more
|
|
492
|
+
function viewProvider() {
|
|
493
|
+
if (!codexOn()) return 'claude'
|
|
494
|
+
if (!claudeOn()) return 'codex'
|
|
495
|
+
return pickedProvider
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function pickProvider(p) {
|
|
499
|
+
if (p === pickedProvider) return
|
|
500
|
+
pickedProvider = p
|
|
501
|
+
try {
|
|
502
|
+
localStorage.setItem(PROVIDER_KEY, p)
|
|
503
|
+
} catch {}
|
|
504
|
+
paint()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const STALE_MS = 15 * 60000
|
|
508
|
+
const CODEX_SLEEP_MS = 5 * 60000 // main's default sleepThresholdMs
|
|
509
|
+
// Codex logs are only written while it runs, so a reading can be hours old
|
|
510
|
+
const codexStale = (c) =>
|
|
511
|
+
!!c && (c.session?.expired || (c.limitsAt != null && Date.now() - c.limitsAt > STALE_MS))
|
|
512
|
+
|
|
513
|
+
// Codex's numbers in the shape the panel draws; its logs have no activity or tok/min
|
|
514
|
+
function codexView(c) {
|
|
515
|
+
const idleFor = c.lastSeen ? Date.now() - c.lastSeen : null
|
|
516
|
+
return {
|
|
517
|
+
d: {
|
|
518
|
+
active: c.active,
|
|
519
|
+
activity: null,
|
|
520
|
+
// no data is not the same as asleep
|
|
521
|
+
sleeping: !c.active && idleFor != null && idleFor >= CODEX_SLEEP_MS,
|
|
522
|
+
tokensPerMin: 0,
|
|
523
|
+
today: { tokens: c.tokensToday || 0 },
|
|
524
|
+
session: { tokens: c.tokens5h || 0 },
|
|
525
|
+
week: { tokens: c.tokensWeek || 0 },
|
|
526
|
+
byModel: c.byModel || [],
|
|
527
|
+
byProject: c.byProject || [],
|
|
528
|
+
days30: c.days30 || [],
|
|
529
|
+
monthTokens: c.monthTokens ?? null,
|
|
530
|
+
},
|
|
531
|
+
sess: c.session,
|
|
532
|
+
week: c.weekly,
|
|
533
|
+
stale: c.limitsAt != null && Date.now() - c.limitsAt > STALE_MS ? c.limitsAt : null,
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// a Codex meter caption: its reset, or how old the reading is when that's what matters
|
|
538
|
+
function codexSub(w, tokens, stale) {
|
|
539
|
+
const t = `${fmtTokens(tokens)} tokens`
|
|
540
|
+
if (!w) return `limits not recorded yet · ${t}`
|
|
541
|
+
if (w.pct == null) return `waiting for a fresh reading · ${t}`
|
|
542
|
+
if (stale) {
|
|
543
|
+
const at = w.resetMs != null ? `resets ${fmtResetClock(w.resetMs)} · ` : ''
|
|
544
|
+
return `${at}read ${fmtReset(Date.now() - stale)} ago`
|
|
545
|
+
}
|
|
546
|
+
const reset = w.resetMs != null ? `resets in ${fmtResetIn(w.resetMs)} · ` : ''
|
|
547
|
+
return `${reset}${t}`
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const pctText = (v) => (v == null ? '—' : `${Math.round(v)}%`)
|
|
551
|
+
const warnAt = () => Math.min(...(currentConfig.alertThresholds || [80, 95]))
|
|
552
|
+
|
|
553
|
+
// tabs + the collapsed pair: both services' session %, whichever is on screen
|
|
554
|
+
function paintServices(view) {
|
|
555
|
+
const dual = bothOn()
|
|
556
|
+
document.body.classList.toggle('dual', dual)
|
|
557
|
+
document.body.classList.toggle('view-codex', view === 'codex')
|
|
558
|
+
// collapsed with both: when the selected service's session resets
|
|
559
|
+
const resetMs = view === 'codex' ? codexData?.session?.resetMs : realUsage?.session?.resetMs
|
|
560
|
+
el('mini-reset').hidden = !dual || resetMs == null
|
|
561
|
+
el('mini-reset').textContent = resetMs == null ? '' : `resets ${fmtResetClock(resetMs)}`
|
|
562
|
+
el('service-panel').setAttribute('role', dual ? 'tabpanel' : 'region')
|
|
563
|
+
if (dual) {
|
|
564
|
+
el('service-panel').setAttribute('aria-labelledby', `tab-${view}`)
|
|
565
|
+
el('service-panel').removeAttribute('aria-label')
|
|
566
|
+
} else {
|
|
567
|
+
el('service-panel').removeAttribute('aria-labelledby')
|
|
568
|
+
el('service-panel').setAttribute('aria-label', `${view === 'codex' ? 'Codex' : 'Claude'} usage`)
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
const cl = realUsage
|
|
572
|
+
const cx = codexData
|
|
573
|
+
const rows = {
|
|
574
|
+
claude: {
|
|
575
|
+
pct: cl?.session?.pct ?? null,
|
|
576
|
+
week: cl?.week?.pct ?? null,
|
|
577
|
+
active: !!lastData?.active,
|
|
578
|
+
},
|
|
579
|
+
codex: {
|
|
580
|
+
pct: cx?.session?.pct ?? null,
|
|
581
|
+
week: cx?.weekly?.pct ?? null,
|
|
582
|
+
active: !!cx?.active,
|
|
583
|
+
stale: codexStale(cx),
|
|
584
|
+
},
|
|
585
|
+
}
|
|
586
|
+
for (const [p, r] of Object.entries(rows)) {
|
|
587
|
+
const on = p === view
|
|
588
|
+
const urgent = Math.max(r.pct ?? 0, r.week ?? 0) >= warnAt()
|
|
589
|
+
const tab = el(`tab-${p}`)
|
|
590
|
+
tab.setAttribute('aria-selected', String(on))
|
|
591
|
+
tab.tabIndex = on ? 0 : -1
|
|
592
|
+
tab.classList.toggle('active', r.active)
|
|
593
|
+
tab.classList.toggle('urgent', urgent)
|
|
594
|
+
tab.querySelector('.tab-alert').hidden = !urgent
|
|
595
|
+
el(`tab-${p}-value`).textContent = pctText(r.pct)
|
|
596
|
+
const name = p === 'claude' ? 'Claude' : 'Codex'
|
|
597
|
+
tab.title = `${name} session ${pctText(r.pct)} · weekly ${pctText(r.week)}${r.active ? ' · working' : ''}${r.stale ? ' · not updated recently' : ''}`
|
|
598
|
+
const line = document.querySelector(`.mini-source[data-provider="${p}"]`)
|
|
599
|
+
line.setAttribute('aria-pressed', String(on))
|
|
600
|
+
line.title = tab.title
|
|
601
|
+
line.setAttribute('aria-label', `Show ${tab.title}`)
|
|
602
|
+
line.classList.toggle('urgent', urgent)
|
|
603
|
+
tab.setAttribute('aria-label', `Show ${tab.title}`)
|
|
604
|
+
el(`mini-${p}-value`).textContent = pctText(r.pct)
|
|
605
|
+
const bar = el(`mini-${p}-bar`)
|
|
606
|
+
bar.style.width = `${Math.min(r.pct ?? 0, 100)}%`
|
|
607
|
+
bar.classList.toggle('hot', (r.pct ?? 0) >= 80)
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
for (const b of document.querySelectorAll('#harness-tabs button, .mini-source')) {
|
|
612
|
+
b.addEventListener('click', (e) => {
|
|
613
|
+
e.stopPropagation()
|
|
614
|
+
pickProvider(b.dataset.provider)
|
|
615
|
+
})
|
|
616
|
+
}
|
|
617
|
+
el('harness-tabs').addEventListener('keydown', (e) => {
|
|
618
|
+
const providers = ['claude', 'codex']
|
|
619
|
+
const current = providers.indexOf(e.target.dataset.provider)
|
|
620
|
+
if (current < 0) return
|
|
621
|
+
let next
|
|
622
|
+
if (e.key === 'ArrowRight') next = (current + 1) % providers.length
|
|
623
|
+
else if (e.key === 'ArrowLeft') next = (current + providers.length - 1) % providers.length
|
|
624
|
+
else if (e.key === 'Home') next = 0
|
|
625
|
+
else if (e.key === 'End') next = providers.length - 1
|
|
626
|
+
else return
|
|
627
|
+
e.preventDefault()
|
|
628
|
+
pickProvider(providers[next])
|
|
629
|
+
el(`tab-${providers[next]}`).focus()
|
|
630
|
+
})
|
|
631
|
+
|
|
471
632
|
// main render
|
|
472
633
|
let prevState = null
|
|
473
|
-
|
|
634
|
+
const prevPct = {} // per service: a switch must not read as a reset
|
|
474
635
|
let lastData = null
|
|
475
636
|
function render(d) {
|
|
476
637
|
lastData = d
|
|
477
|
-
//
|
|
478
|
-
const
|
|
638
|
+
// the burn trail is Claude's, fed on every Claude tick whatever is on screen
|
|
639
|
+
const cl = realUsage
|
|
640
|
+
burn.note(d.session.tokens, !!cl && cl.session.resetMs != null)
|
|
641
|
+
paint()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function paint() {
|
|
645
|
+
setPlan(el('codex-plan'), codexData?.plan)
|
|
646
|
+
const d0 = lastData
|
|
647
|
+
if (!d0) return
|
|
648
|
+
const view = viewProvider()
|
|
649
|
+
paintServices(view)
|
|
650
|
+
const isCodex = view === 'codex' && !!codexData
|
|
651
|
+
const cv = isCodex ? codexView(codexData) : null
|
|
652
|
+
const d = isCodex ? cv.d : d0
|
|
653
|
+
// % comes only from the connected account (or Codex's own logs) — no estimates
|
|
654
|
+
const liveOn = isCodex || !!realUsage
|
|
479
655
|
document.body.classList.toggle('live', liveOn)
|
|
480
|
-
const sessPct = liveOn ? realUsage.session.pct : 0
|
|
481
|
-
const sessReset = liveOn ? realUsage.session.resetMs : null
|
|
656
|
+
const sessPct = isCodex ? (cv.sess?.pct ?? null) : liveOn ? realUsage.session.pct : 0
|
|
657
|
+
const sessReset = isCodex ? (cv.sess?.resetMs ?? null) : liveOn ? realUsage.session.resetMs : null
|
|
482
658
|
const sessActive = liveOn && sessReset != null
|
|
483
|
-
const wkPct = liveOn ? realUsage.week.pct : 0
|
|
484
|
-
const wkReset = liveOn ? realUsage.week.resetMs : null
|
|
659
|
+
const wkPct = isCodex ? (cv.week?.pct ?? null) : liveOn ? realUsage.week.pct : 0
|
|
660
|
+
const wkReset = isCodex ? (cv.week?.resetMs ?? null) : liveOn ? realUsage.week.resetMs : null
|
|
661
|
+
const sp = sessPct ?? 0
|
|
662
|
+
const wp = wkPct ?? 0
|
|
485
663
|
|
|
486
664
|
const fireAt = currentConfig.fireThreshold ?? 90
|
|
487
665
|
let st =
|
|
488
|
-
liveOn &&
|
|
666
|
+
liveOn && sp >= 100
|
|
489
667
|
? 'tired'
|
|
490
668
|
: d.active
|
|
491
669
|
? 'working'
|
|
492
|
-
: liveOn &&
|
|
670
|
+
: liveOn && sp >= fireAt
|
|
493
671
|
? 'stressed'
|
|
494
672
|
: d.sleeping
|
|
495
673
|
? 'sleeping'
|
|
@@ -542,32 +720,34 @@ function render(d) {
|
|
|
542
720
|
? `${fmtTokens(d.tokensPerMin)} tok/min`
|
|
543
721
|
: `${fmtTokens(d.today.tokens)} tokens today`
|
|
544
722
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
723
|
+
const was = prevPct[view]
|
|
724
|
+
if (liveOn && was != null && sessActive && sessPct != null && was - sessPct > 25) celebrate()
|
|
725
|
+
prevPct[view] = sessPct
|
|
726
|
+
el('session-pct').textContent = pctText(liveOn ? sessPct : 0)
|
|
727
|
+
setLevel(el('session-pct'), liveOn ? sp : 0)
|
|
549
728
|
const mini = el('mini-pct')
|
|
550
|
-
mini.textContent = liveOn ?
|
|
551
|
-
mini.classList.toggle('high', liveOn &&
|
|
729
|
+
mini.textContent = liveOn ? pctText(sessPct) : '—'
|
|
730
|
+
mini.classList.toggle('high', liveOn && sp >= 80)
|
|
552
731
|
// the collapsed ring: how much of the session is already spent
|
|
553
732
|
const rf = el('ring-fill')
|
|
554
|
-
rf.style.strokeDashoffset = `${RING_LEN * (1 - Math.min(
|
|
555
|
-
rf.classList.toggle('high',
|
|
733
|
+
rf.style.strokeDashoffset = `${RING_LEN * (1 - Math.min(sp, 100) / 100)}`
|
|
734
|
+
rf.classList.toggle('high', sp >= 80)
|
|
556
735
|
const rp = el('ring-pct')
|
|
557
736
|
rp.textContent = mini.textContent
|
|
558
|
-
rp.classList.toggle('high', liveOn &&
|
|
737
|
+
rp.classList.toggle('high', liveOn && sp >= 80)
|
|
559
738
|
const sf = el('session-fill')
|
|
560
|
-
sf.style.width = `${
|
|
561
|
-
sf.classList.toggle('high',
|
|
562
|
-
setLevel(sf,
|
|
563
|
-
el('session-sub').textContent =
|
|
564
|
-
?
|
|
565
|
-
:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
739
|
+
sf.style.width = `${sp}%`
|
|
740
|
+
sf.classList.toggle('high', sp >= 80)
|
|
741
|
+
setLevel(sf, sp)
|
|
742
|
+
el('session-sub').textContent = isCodex
|
|
743
|
+
? codexSub(cv.sess, d.session.tokens, cv.stale)
|
|
744
|
+
: sessActive
|
|
745
|
+
? `resets in ${fmtResetIn(sessReset)} · ${fmtTokens(d.session.tokens)} tokens`
|
|
746
|
+
: 'no active session'
|
|
747
|
+
|
|
748
|
+
// where this pace is taking you — Claude's only: Codex has no trail to read
|
|
569
749
|
const proj =
|
|
570
|
-
sessActive &&
|
|
750
|
+
!isCodex && sessActive && sp < 100 ? burn.project(sp, sessReset, d.session.tokens) : null
|
|
571
751
|
const pe = el('session-proj')
|
|
572
752
|
pe.hidden = !proj
|
|
573
753
|
pe.classList.toggle('tight', proj?.kind === 'eta')
|
|
@@ -576,22 +756,24 @@ function render(d) {
|
|
|
576
756
|
proj.kind === 'eta' ? `~${fmtReset(proj.ms)} left at this pace` : 'resets before you run out'
|
|
577
757
|
}
|
|
578
758
|
|
|
579
|
-
el('week-pct').textContent =
|
|
580
|
-
setLevel(el('week-pct'),
|
|
759
|
+
el('week-pct').textContent = pctText(liveOn ? wkPct : 0)
|
|
760
|
+
setLevel(el('week-pct'), wp)
|
|
581
761
|
const wf = el('week-fill')
|
|
582
|
-
wf.style.width = `${
|
|
583
|
-
wf.classList.toggle('high',
|
|
584
|
-
setLevel(wf,
|
|
585
|
-
el('week-sub').textContent =
|
|
586
|
-
|
|
762
|
+
wf.style.width = `${wp}%`
|
|
763
|
+
wf.classList.toggle('high', wp >= 80)
|
|
764
|
+
setLevel(wf, wp)
|
|
765
|
+
el('week-sub').textContent = isCodex
|
|
766
|
+
? codexSub(cv.week, d.week.tokens, cv.stale)
|
|
767
|
+
: wkReset != null
|
|
587
768
|
? `resets in ${fmtResetIn(wkReset)} · ${fmtTokens(d.week.tokens)} tokens`
|
|
588
769
|
: `${fmtTokens(d.week.tokens)} tokens · last 7 days`
|
|
589
770
|
|
|
590
|
-
renderScoped(liveOn ? realUsage.scoped || [] : [], d.byModel || [])
|
|
771
|
+
renderScoped(!isCodex && liveOn ? realUsage.scoped || [] : [], d.byModel || [])
|
|
591
772
|
renderModels(d.byModel || [])
|
|
592
773
|
renderProjects(d.byProject || [])
|
|
593
774
|
renderHeat(d.days30 || [])
|
|
594
|
-
el('month-total').textContent = `${fmtTokens(d.monthTokens)} tokens`
|
|
775
|
+
el('month-total').textContent = d.monthTokens == null ? '—' : `${fmtTokens(d.monthTokens)} tokens`
|
|
776
|
+
paintChip()
|
|
595
777
|
|
|
596
778
|
currentRate = d.tokensPerMin || 0
|
|
597
779
|
if (st === 'working') {
|
|
@@ -614,7 +796,9 @@ function fitSize() {
|
|
|
614
796
|
requestAnimationFrame(() => {
|
|
615
797
|
const collapsed = document.body.classList.contains('collapsed')
|
|
616
798
|
const zoom = Number.parseFloat(document.body.style.zoom) || 1
|
|
617
|
-
|
|
799
|
+
// The dual-service dock trades height for a little horizontal room.
|
|
800
|
+
const dual = document.body.classList.contains('dual')
|
|
801
|
+
const w = (collapsed ? (dual ? 240 : 168) : 304) * zoom
|
|
618
802
|
// offsetHeight never reflects a CSS zoom applied to an ancestor (confirmed
|
|
619
803
|
// empirically against this Electron build) — so measure the unzoomed content
|
|
620
804
|
// height, then scale the whole thing (content + margin) ourselves
|
|
@@ -651,9 +835,16 @@ window.api.onError((msg) => {
|
|
|
651
835
|
window.api.onConfig((cfg) => {
|
|
652
836
|
currentConfig = cfg || {}
|
|
653
837
|
document.body.classList.toggle('is-menubar', currentConfig.mode === 'menubar')
|
|
838
|
+
document.body.classList.toggle('codex-on', !!currentConfig.codex)
|
|
839
|
+
if (!currentConfig.codex) codexData = null
|
|
840
|
+
paint()
|
|
654
841
|
applyZoom(currentConfig.zoom)
|
|
655
842
|
fitSize()
|
|
656
843
|
})
|
|
844
|
+
window.api.onCodex((c) => {
|
|
845
|
+
codexData = c || null
|
|
846
|
+
paint()
|
|
847
|
+
})
|
|
657
848
|
window.api.onRealUsage((u) => {
|
|
658
849
|
realUsage = u || null
|
|
659
850
|
if (lastData) render(lastData)
|
|
@@ -669,6 +860,7 @@ window.api.onAuthState((s) => {
|
|
|
669
860
|
endLogin()
|
|
670
861
|
showProfile(null)
|
|
671
862
|
}
|
|
863
|
+
paint() // losing Claude can leave Codex as the only service
|
|
672
864
|
if (document.body.classList.contains('settings-open')) fitSize()
|
|
673
865
|
})
|
|
674
866
|
|
|
@@ -689,12 +881,24 @@ function paintChip() {
|
|
|
689
881
|
const active = (a?.accounts || []).find((x) => x.id === a?.active)
|
|
690
882
|
const email = p?.email || (active?.connected ? active.label : null)
|
|
691
883
|
// the settings block says who is connected, not just that someone is: the
|
|
692
|
-
//
|
|
884
|
+
// logo identifies the service, followed by the same plan badge and email.
|
|
693
885
|
el('acc-ok').textContent = email || 'Connected'
|
|
694
|
-
el('acc-sub')
|
|
695
|
-
el('acc-
|
|
886
|
+
setPlan(el('acc-sub'), p?.plan)
|
|
887
|
+
el('acc-ok').title = email || 'Connected'
|
|
696
888
|
const chip = el('account-chip')
|
|
697
889
|
const mini = el('mini-acct')
|
|
890
|
+
// the Codex view never borrows Claude's identity: its logs carry no email
|
|
891
|
+
if (viewProvider() === 'codex') {
|
|
892
|
+
const plan = codexData?.plan || null
|
|
893
|
+
el('ac-email').textContent = 'Codex'
|
|
894
|
+
chip.title = 'Codex on this computer'
|
|
895
|
+
setPlan(el('ac-plan'), plan)
|
|
896
|
+
chip.hidden = false
|
|
897
|
+
el('mini-acct-name').textContent = 'Codex'
|
|
898
|
+
setPlan(el('mini-acct-plan'), plan)
|
|
899
|
+
mini.hidden = false
|
|
900
|
+
return
|
|
901
|
+
}
|
|
698
902
|
if (!email) {
|
|
699
903
|
chip.hidden = true
|
|
700
904
|
mini.hidden = true
|
|
@@ -832,6 +1036,8 @@ function toggleAccountMenu() {
|
|
|
832
1036
|
|
|
833
1037
|
el('account-chip').addEventListener('click', (e) => {
|
|
834
1038
|
e.stopPropagation()
|
|
1039
|
+
// Codex has no account switcher: its chip leads to its Settings block
|
|
1040
|
+
if (viewProvider() === 'codex') return openSettings()
|
|
835
1041
|
toggleAccountMenu()
|
|
836
1042
|
})
|
|
837
1043
|
// clicking anywhere else — the pet, the gear, another app — puts it away
|
|
@@ -890,7 +1096,7 @@ window.api.onAuthResult((r) => {
|
|
|
890
1096
|
}, 6000)
|
|
891
1097
|
celebrate()
|
|
892
1098
|
} else {
|
|
893
|
-
// failed:
|
|
1099
|
+
// failed: restore the connection card so a retry is one click away
|
|
894
1100
|
document.body.classList.remove('awaiting')
|
|
895
1101
|
const e = r?.error || ''
|
|
896
1102
|
el('acc-msg').textContent = /429|rate_limit/i.test(e)
|
|
@@ -900,6 +1106,26 @@ window.api.onAuthResult((r) => {
|
|
|
900
1106
|
fitSize()
|
|
901
1107
|
})
|
|
902
1108
|
|
|
1109
|
+
// Keep the main view focused on limits; history is an explicit, remembered choice.
|
|
1110
|
+
const DETAILS_KEY = 'clauddy.details-open'
|
|
1111
|
+
function setDetailsOpen(open) {
|
|
1112
|
+
el('usage-details').hidden = !open
|
|
1113
|
+
el('details-toggle').setAttribute('aria-expanded', String(open))
|
|
1114
|
+
fitSize()
|
|
1115
|
+
}
|
|
1116
|
+
try {
|
|
1117
|
+
setDetailsOpen(localStorage.getItem(DETAILS_KEY) === 'true')
|
|
1118
|
+
} catch {
|
|
1119
|
+
setDetailsOpen(false)
|
|
1120
|
+
}
|
|
1121
|
+
el('details-toggle').addEventListener('click', () => {
|
|
1122
|
+
const open = el('usage-details').hidden
|
|
1123
|
+
setDetailsOpen(open)
|
|
1124
|
+
try {
|
|
1125
|
+
localStorage.setItem(DETAILS_KEY, String(open))
|
|
1126
|
+
} catch {}
|
|
1127
|
+
})
|
|
1128
|
+
|
|
903
1129
|
// collapsible panel sections — the widget is a desktop pet, not a dashboard, so
|
|
904
1130
|
// each breakdown can be folded away and the choice is remembered per machine
|
|
905
1131
|
const SEC_KEY = 'clauddy.folded'
|
|
@@ -955,7 +1181,36 @@ for (const head of document.querySelectorAll('.sec-head')) {
|
|
|
955
1181
|
for (const id of readFolded()) toggleSection(id, true)
|
|
956
1182
|
|
|
957
1183
|
el('close').addEventListener('click', () => window.api.quit())
|
|
958
|
-
el('usage').addEventListener('click', () => window.api.openUsage())
|
|
1184
|
+
el('usage').addEventListener('click', () => window.api.openUsage(viewProvider()))
|
|
1185
|
+
|
|
1186
|
+
// Settings → Codex: Connect looks for a local session and turns monitoring on if found
|
|
1187
|
+
function endCodexSetup() {
|
|
1188
|
+
document.body.classList.remove('codex-setup')
|
|
1189
|
+
el('codex-setup').hidden = true
|
|
1190
|
+
el('codex-hint').textContent = 'Track Codex alongside Claude, in this pet.'
|
|
1191
|
+
}
|
|
1192
|
+
el('codex-connect').addEventListener('click', () => {
|
|
1193
|
+
el('codex-hint').textContent = 'Looking…'
|
|
1194
|
+
window.api.codexDetect()
|
|
1195
|
+
})
|
|
1196
|
+
el('codex-retry').addEventListener('click', () => window.api.codexDetect())
|
|
1197
|
+
// Connect is the consent: found → monitoring starts, not found → say why
|
|
1198
|
+
window.api.onCodexDetected((r) => {
|
|
1199
|
+
if (r?.found) {
|
|
1200
|
+
endCodexSetup()
|
|
1201
|
+
window.api.codexEnable(true)
|
|
1202
|
+
return
|
|
1203
|
+
}
|
|
1204
|
+
document.body.classList.add('codex-setup')
|
|
1205
|
+
el('codex-setup').hidden = false
|
|
1206
|
+
el('codex-hint').textContent = 'No session — sign in to Codex'
|
|
1207
|
+
fitSize()
|
|
1208
|
+
})
|
|
1209
|
+
el('codex-setup-cancel').addEventListener('click', () => {
|
|
1210
|
+
endCodexSetup()
|
|
1211
|
+
fitSize()
|
|
1212
|
+
})
|
|
1213
|
+
el('codex-disconnect').addEventListener('click', () => window.api.codexEnable(false))
|
|
959
1214
|
|
|
960
1215
|
// account login (browser flow)
|
|
961
1216
|
el('acc-connect').addEventListener('click', () => window.api.authStart())
|
|
@@ -1105,6 +1360,7 @@ let closingTimer = null
|
|
|
1105
1360
|
function closeSettings() {
|
|
1106
1361
|
if (!document.body.classList.contains('settings-open')) return
|
|
1107
1362
|
abandonLogin()
|
|
1363
|
+
endCodexSetup()
|
|
1108
1364
|
document.body.classList.remove('settings-open')
|
|
1109
1365
|
document.body.classList.add('settings-closing')
|
|
1110
1366
|
clearTimeout(closingTimer)
|
|
@@ -1217,6 +1473,8 @@ if (typeof module === 'object' && module.exports) {
|
|
|
1217
1473
|
showProfile,
|
|
1218
1474
|
renderAccounts,
|
|
1219
1475
|
renderAccountMenu,
|
|
1476
|
+
paint,
|
|
1477
|
+
pickProvider,
|
|
1220
1478
|
burn,
|
|
1221
1479
|
}
|
|
1222
1480
|
}
|