clauddy 1.29.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/bin/clauddy.js +82 -79
- package/package.json +10 -82
- package/accounts.js +0 -181
- package/auth.js +0 -300
- package/codex.js +0 -345
- package/config.json +0 -11
- package/cursor.js +0 -278
- package/main.js +0 -1305
- package/preload.js +0 -43
- package/reminders.js +0 -54
- package/renderer/burn.js +0 -98
- package/renderer/companion.js +0 -52
- package/renderer/index.html +0 -527
- package/renderer/motion.css +0 -646
- package/renderer/pet.js +0 -2394
- package/renderer/pets.css +0 -916
- package/renderer/pets.js +0 -579
- package/renderer/style.css +0 -3758
- package/renderer/voice.js +0 -244
- package/usage.js +0 -539
package/preload.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const { contextBridge, ipcRenderer } = require('electron')
|
|
2
|
-
|
|
3
|
-
contextBridge.exposeInMainWorld('api', {
|
|
4
|
-
watchPetPointer: (enabled) => ipcRenderer.send('pet-pointer-watch', enabled),
|
|
5
|
-
onPetPointer: (cb) => ipcRenderer.on('pet-pointer', (_e, point) => cb(point)),
|
|
6
|
-
setReminder: (provider, enabled) => ipcRenderer.send('set-reminder', provider, enabled),
|
|
7
|
-
onReminders: (cb) => ipcRenderer.on('reminders', (_e, state) => cb(state)),
|
|
8
|
-
onReminderDue: (cb) => ipcRenderer.on('reminder-due', (_e, event) => cb(event)),
|
|
9
|
-
onUsage: (cb) => ipcRenderer.on('usage', (_e, data) => cb(data)),
|
|
10
|
-
onError: (cb) => ipcRenderer.on('usage-error', (_e, msg) => cb(msg)),
|
|
11
|
-
onConfig: (cb) => ipcRenderer.on('config', (_e, cfg) => cb(cfg)),
|
|
12
|
-
saveConfig: (patch) => ipcRenderer.send('save-config', patch),
|
|
13
|
-
resize: (w, h, animate) => ipcRenderer.send('resize', w, h, animate),
|
|
14
|
-
openUsage: (provider) => ipcRenderer.send('open-usage', provider),
|
|
15
|
-
codexDetect: () => ipcRenderer.send('codex-detect'),
|
|
16
|
-
onCodexDetected: (cb) => ipcRenderer.on('codex-detected', (_e, r) => cb(r)),
|
|
17
|
-
codexEnable: (on) => ipcRenderer.send('codex-enable', on),
|
|
18
|
-
onCodex: (cb) => ipcRenderer.on('codex', (_e, c) => cb(c)),
|
|
19
|
-
cursorDetect: () => ipcRenderer.send('cursor-detect'),
|
|
20
|
-
onCursorDetected: (cb) => ipcRenderer.on('cursor-detected', (_e, r) => cb(r)),
|
|
21
|
-
cursorEnable: (on) => ipcRenderer.send('cursor-enable', on),
|
|
22
|
-
onCursor: (cb) => ipcRenderer.on('cursor', (_e, c) => cb(c)),
|
|
23
|
-
onRealUsage: (cb) => ipcRenderer.on('real-usage', (_e, u) => cb(u)),
|
|
24
|
-
onAuthState: (cb) => ipcRenderer.on('auth-state', (_e, s) => cb(s)),
|
|
25
|
-
onProfile: (cb) => ipcRenderer.on('profile', (_e, p) => cb(p)),
|
|
26
|
-
onAuthPending: (cb) => ipcRenderer.on('auth-pending', () => cb()),
|
|
27
|
-
onAuthResult: (cb) => ipcRenderer.on('auth-result', (_e, r) => cb(r)),
|
|
28
|
-
authStart: () => ipcRenderer.send('auth-start'),
|
|
29
|
-
authCode: (code) => ipcRenderer.send('auth-code', code),
|
|
30
|
-
authLogout: () => ipcRenderer.send('auth-logout'),
|
|
31
|
-
onAccounts: (cb) => ipcRenderer.on('accounts', (_e, a) => cb(a)),
|
|
32
|
-
accountSwitch: (id) => ipcRenderer.send('accounts-switch', id),
|
|
33
|
-
accountAdd: () => ipcRenderer.send('accounts-add'),
|
|
34
|
-
accountCancelAdd: () => ipcRenderer.send('accounts-cancel-add'),
|
|
35
|
-
accountRemove: (id) => ipcRenderer.send('accounts-remove', id),
|
|
36
|
-
onDebugState: (cb) => ipcRenderer.on('debug-state', (_e, s) => cb(s)),
|
|
37
|
-
onVersion: (cb) => ipcRenderer.on('version', (_e, v) => cb(v)),
|
|
38
|
-
onUpdateStatus: (cb) => ipcRenderer.on('update-status', (_e, s) => cb(s)),
|
|
39
|
-
onPopoverAnchor: (cb) => ipcRenderer.on('popover-anchor', (_e, x) => cb(x)),
|
|
40
|
-
checkUpdates: () => ipcRenderer.send('check-updates'),
|
|
41
|
-
doUpdate: () => ipcRenderer.send('do-update'),
|
|
42
|
-
quit: () => ipcRenderer.send('quit'),
|
|
43
|
-
})
|
package/reminders.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// One-shot reset reminders, independent of automatic threshold alerts.
|
|
2
|
-
function createReminders({ load, save, now = Date.now }) {
|
|
3
|
-
let pending = []
|
|
4
|
-
let delivered = {}
|
|
5
|
-
const valid = (r) =>
|
|
6
|
-
r &&
|
|
7
|
-
['claude', 'codex', 'cursor'].includes(r.provider) &&
|
|
8
|
-
typeof r.key === 'string' &&
|
|
9
|
-
typeof r.label === 'string' &&
|
|
10
|
-
Number.isFinite(r.at) &&
|
|
11
|
-
Number.isFinite(r.pct)
|
|
12
|
-
try {
|
|
13
|
-
const data = load()
|
|
14
|
-
pending = Array.isArray(data?.pending) ? data.pending.filter(valid) : []
|
|
15
|
-
delivered = data?.delivered || {}
|
|
16
|
-
} catch {}
|
|
17
|
-
function persist(nextPending, nextDelivered = delivered) {
|
|
18
|
-
const recent = Object.fromEntries(
|
|
19
|
-
Object.entries(nextDelivered).filter(([, at]) => Number.isFinite(at) && now() - at < 3600000),
|
|
20
|
-
)
|
|
21
|
-
save({ pending: nextPending, delivered: recent })
|
|
22
|
-
pending = nextPending
|
|
23
|
-
delivered = recent
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
list: () => pending.map((r) => ({ ...r })),
|
|
27
|
-
arm(reminder) {
|
|
28
|
-
// Cursor's budget is a billing cycle, so a reset can be a month out
|
|
29
|
-
if (!valid(reminder) || reminder.at <= now() || reminder.at > now() + 32 * 86400000)
|
|
30
|
-
return false
|
|
31
|
-
persist([...pending.filter((r) => r.key !== reminder.key), { ...reminder }])
|
|
32
|
-
return true
|
|
33
|
-
},
|
|
34
|
-
cancel(key) {
|
|
35
|
-
const found = pending.some((r) => r.key === key)
|
|
36
|
-
if (found) persist(pending.filter((r) => r.key !== key))
|
|
37
|
-
},
|
|
38
|
-
takeDue() {
|
|
39
|
-
const due = pending.filter((r) => r.at <= now())
|
|
40
|
-
if (!due.length) return []
|
|
41
|
-
const nextDelivered = { ...delivered }
|
|
42
|
-
for (const r of due) nextDelivered[r.key] = now()
|
|
43
|
-
// Consume before delivering: restart must not replay it.
|
|
44
|
-
persist(
|
|
45
|
-
pending.filter((r) => r.at > now()),
|
|
46
|
-
nextDelivered,
|
|
47
|
-
)
|
|
48
|
-
return due
|
|
49
|
-
},
|
|
50
|
-
recentlyDelivered: (key) => Number.isFinite(delivered[key]) && now() - delivered[key] < 3600000,
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = { createReminders }
|
package/renderer/burn.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// ---- burn-rate projection ----
|
|
2
|
-
// The panel knows where you are (82%) and when the window resets (1h 12m); it
|
|
3
|
-
// can also say where you're headed — fit a slope through recent usage and
|
|
4
|
-
// project the crossing of 100%. If the reset lands first there's nothing to
|
|
5
|
-
// worry about, which is worth saying out loud rather than leaving blank.
|
|
6
|
-
//
|
|
7
|
-
// The slope is fitted over session *tokens*, not the account %. The % is the
|
|
8
|
-
// number we ultimately care about, but it arrives as a whole number every ~5
|
|
9
|
-
// min: over a short window the whole signal is a single 16 → 17 step, which
|
|
10
|
-
// makes the fitted pace wrong by multiples. Local-log tokens step too — one
|
|
11
|
-
// jump per assistant turn, with plateaus in between — but in increments some
|
|
12
|
-
// 10-20x finer, so they carry a far steadier slope. The account % still anchors
|
|
13
|
-
// it: pct/tokens converts tokens/ms into %/ms and re-calibrates on every poll,
|
|
14
|
-
// so the projection stays tied to the real number.
|
|
15
|
-
//
|
|
16
|
-
// Loaded both as a plain <script> by the renderer (exposing `Burn`) and via
|
|
17
|
-
// require() by the tests — hence the dual export at the bottom.
|
|
18
|
-
;(() => {
|
|
19
|
-
const WINDOW_MS = 45 * 60 * 1000 // only fit recent samples — pace changes
|
|
20
|
-
const MIN_SAMPLES = 4
|
|
21
|
-
const MIN_SPAN_MS = 5 * 60 * 1000 // shorter than a % fit affords, and steadier
|
|
22
|
-
const SAMPLE_EVERY_MS = 30 * 1000 // usage polls every few seconds; thin it out
|
|
23
|
-
|
|
24
|
-
// `now` is injectable so tests can drive the trail without sleeping
|
|
25
|
-
function createBurnTracker() {
|
|
26
|
-
let trail = []
|
|
27
|
-
|
|
28
|
-
function note(tokens, active, now = Date.now()) {
|
|
29
|
-
if (!active || !(tokens > 0)) {
|
|
30
|
-
trail = [] // no session, no trend
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
// session tokens only climb within a window — a drop means it rolled
|
|
34
|
-
// over, and the fresh sample starts the new trail rather than being
|
|
35
|
-
// throttled away
|
|
36
|
-
let last = trail[trail.length - 1]
|
|
37
|
-
if (last && tokens < last.tokens) {
|
|
38
|
-
trail = []
|
|
39
|
-
last = undefined
|
|
40
|
-
}
|
|
41
|
-
if (last && now - last.t < SAMPLE_EVERY_MS) return
|
|
42
|
-
trail.push({ t: now, tokens })
|
|
43
|
-
trail = trail.filter((s) => s.t >= now - WINDOW_MS)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// least-squares slope in tokens per ms, or null when there isn't enough to say
|
|
47
|
-
function slope() {
|
|
48
|
-
if (trail.length < MIN_SAMPLES) return null
|
|
49
|
-
const span = trail[trail.length - 1].t - trail[0].t
|
|
50
|
-
if (span < MIN_SPAN_MS) return null
|
|
51
|
-
const n = trail.length
|
|
52
|
-
const t0 = trail[0].t
|
|
53
|
-
let sx = 0
|
|
54
|
-
let sy = 0
|
|
55
|
-
let sxy = 0
|
|
56
|
-
let sxx = 0
|
|
57
|
-
for (const s of trail) {
|
|
58
|
-
const x = s.t - t0
|
|
59
|
-
sx += x
|
|
60
|
-
sy += s.tokens
|
|
61
|
-
sxy += x * s.tokens
|
|
62
|
-
sxx += x * x
|
|
63
|
-
}
|
|
64
|
-
const denom = n * sxx - sx * sx
|
|
65
|
-
if (denom === 0) return null
|
|
66
|
-
return (n * sxy - sx * sy) / denom
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// → { kind: 'eta', ms } when you'd run out first, { kind: 'safe' } when the
|
|
70
|
-
// reset beats you there, or null when we can't tell yet.
|
|
71
|
-
function project(pct, resetMs, tokens) {
|
|
72
|
-
if (!(pct > 0) || !(tokens > 0)) return null // no anchor to convert tokens → %
|
|
73
|
-
const tokPerMs = slope()
|
|
74
|
-
if (tokPerMs == null || tokPerMs <= 0) return null // idle or flat
|
|
75
|
-
const pctPerMs = tokPerMs * (pct / tokens)
|
|
76
|
-
const eta = (100 - pct) / pctPerMs
|
|
77
|
-
if (!Number.isFinite(eta) || eta <= 0) return null
|
|
78
|
-
if (resetMs != null && eta >= resetMs) return { kind: 'safe' }
|
|
79
|
-
return { kind: 'eta', ms: eta }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
note,
|
|
84
|
-
slope,
|
|
85
|
-
project,
|
|
86
|
-
reset: () => {
|
|
87
|
-
trail = []
|
|
88
|
-
},
|
|
89
|
-
get trail() {
|
|
90
|
-
return trail
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const api = { createBurnTracker, WINDOW_MS, MIN_SAMPLES, MIN_SPAN_MS, SAMPLE_EVERY_MS }
|
|
96
|
-
if (typeof module === 'object' && module.exports) module.exports = api
|
|
97
|
-
else globalThis.Burn = api
|
|
98
|
-
})()
|
package/renderer/companion.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// A quiet transition detector. Startup and reconnects establish a baseline;
|
|
2
|
-
// inactivity is a pause, never proof that an agent finished its task.
|
|
3
|
-
;((root) => {
|
|
4
|
-
function createActivityTracker({ now = Date.now, cooldown = 30000 } = {}) {
|
|
5
|
-
const seen = new Map()
|
|
6
|
-
let lastCue = -Infinity
|
|
7
|
-
return {
|
|
8
|
-
forget(provider) {
|
|
9
|
-
seen.delete(provider)
|
|
10
|
-
},
|
|
11
|
-
observe(provider, data) {
|
|
12
|
-
if (!data) {
|
|
13
|
-
seen.delete(provider)
|
|
14
|
-
return null
|
|
15
|
-
}
|
|
16
|
-
const next = { active: !!data.active, activity: data.activity || 'working' }
|
|
17
|
-
const previous = seen.get(provider)
|
|
18
|
-
seen.set(provider, next)
|
|
19
|
-
if (!previous) return null
|
|
20
|
-
const changed =
|
|
21
|
-
next.active !== previous.active || (next.active && next.activity !== previous.activity)
|
|
22
|
-
if (!changed || now() - lastCue < cooldown) return null
|
|
23
|
-
lastCue = now()
|
|
24
|
-
return { provider, activity: next.active ? next.activity : 'paused' }
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
// Activity freshness is separate from quota freshness: Codex can work while
|
|
29
|
-
// its last rate-limit reading is old. Ignore abandoned activity snapshots.
|
|
30
|
-
const FRESH_MS = 60000
|
|
31
|
-
const SLEEP_MS = 300000 // main's default sleepThresholdMs; Codex and Cursor have no flag of their own
|
|
32
|
-
// each source is its last payload, or null while it isn't connected
|
|
33
|
-
function currentActivity({ claude = null, codex = null, cursor = null } = {}, now = Date.now()) {
|
|
34
|
-
const fresh = (at) => Number.isFinite(at) && now - at <= FRESH_MS
|
|
35
|
-
const providers = []
|
|
36
|
-
if (claude?.active && fresh(claude.ts)) providers.push('claude')
|
|
37
|
-
if (codex?.active && fresh(codex.lastSeen)) providers.push('codex')
|
|
38
|
-
if (cursor?.active && fresh(cursor.lastSeen)) providers.push('cursor')
|
|
39
|
-
// Claude's and Cursor's logs say *what* they're doing; Codex's don't, and
|
|
40
|
-
// two workers share one scene
|
|
41
|
-
const solo = providers.length === 1 ? { claude, cursor }[providers[0]] : null
|
|
42
|
-
const activity = solo?.activity || 'working'
|
|
43
|
-
const idle = (s) => s && !!s.lastSeen && now - s.lastSeen >= SLEEP_MS
|
|
44
|
-
const asleep = [claude && !!claude.sleeping, idle(codex), idle(cursor)].filter(
|
|
45
|
-
(v) => v !== null && v !== undefined,
|
|
46
|
-
)
|
|
47
|
-
return { providers, activity, sleeping: asleep.length > 0 && asleep.every(Boolean) }
|
|
48
|
-
}
|
|
49
|
-
const api = { createActivityTracker, currentActivity }
|
|
50
|
-
if (typeof module === 'object' && module.exports) module.exports = api
|
|
51
|
-
else root.Companion = api
|
|
52
|
-
})(globalThis)
|