clauddy 1.28.2 → 1.29.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 +14 -8
- package/main.js +39 -2
- package/package.json +1 -1
- package/preload.js +2 -1
- package/renderer/index.html +169 -127
- package/renderer/pet.js +224 -22
- package/renderer/pets.css +916 -0
- package/renderer/pets.js +579 -0
- package/renderer/style.css +354 -94
- package/usage.js +57 -26
package/usage.js
CHANGED
|
@@ -162,39 +162,70 @@ function walkJsonl(dir, out, cutoffMs) {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
function ingestLine(entries, line) {
|
|
166
|
+
// most lines are tool results and prompts: skip them without a parse
|
|
167
|
+
if (!line.includes('"usage"')) return
|
|
168
|
+
let obj
|
|
169
|
+
try {
|
|
170
|
+
obj = JSON.parse(line)
|
|
171
|
+
} catch {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
if (obj.type !== 'assistant' || !obj.message || !obj.message.usage) return
|
|
175
|
+
if (obj.message.model === '<synthetic>') return // Claude Code internal messages
|
|
176
|
+
const ts = Date.parse(obj.timestamp || obj.message?.timestamp || 0)
|
|
177
|
+
if (!ts) return
|
|
178
|
+
entries.push({
|
|
179
|
+
ts,
|
|
180
|
+
model: obj.message.model,
|
|
181
|
+
tokens: tokensOf({ usage: obj.message.usage }),
|
|
182
|
+
key: `${obj.message.id || ''}:${obj.requestId || obj.uuid || ''}`,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const CHUNK = 8 * 1024 * 1024
|
|
187
|
+
|
|
188
|
+
// Session logs only ever grow, and a long one passes 500 MB: a poll reads just
|
|
189
|
+
// the bytes appended since the last one, in chunks, so the main process never
|
|
190
|
+
// stalls on a full re-read. A line still being written waits in `rest`.
|
|
165
191
|
function parseFile(full, st) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
let cached = fileCache.get(full)
|
|
193
|
+
// shrunk or replaced: start over
|
|
194
|
+
if (!cached || st.size < cached.offset) {
|
|
195
|
+
cached = { offset: 0, rest: Buffer.alloc(0), entries: [] }
|
|
196
|
+
fileCache.set(full, cached)
|
|
169
197
|
}
|
|
170
|
-
|
|
171
|
-
let
|
|
198
|
+
if (st.size <= cached.offset) return cached.entries
|
|
199
|
+
let fd
|
|
172
200
|
try {
|
|
173
|
-
|
|
201
|
+
fd = fs.openSync(full, 'r')
|
|
174
202
|
} catch {
|
|
175
|
-
return entries
|
|
203
|
+
return cached.entries
|
|
176
204
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
205
|
+
try {
|
|
206
|
+
while (cached.offset < st.size) {
|
|
207
|
+
const chunk = Buffer.alloc(Math.min(CHUNK, st.size - cached.offset))
|
|
208
|
+
const n = fs.readSync(fd, chunk, 0, chunk.length, cached.offset)
|
|
209
|
+
if (n <= 0) break
|
|
210
|
+
cached.offset += n
|
|
211
|
+
const buf = Buffer.concat([cached.rest, chunk.subarray(0, n)])
|
|
212
|
+
// cut at the last newline in bytes, so a split UTF-8 character survives
|
|
213
|
+
const end = buf.lastIndexOf(0x0a)
|
|
214
|
+
if (end < 0) {
|
|
215
|
+
cached.rest = buf
|
|
216
|
+
continue
|
|
217
|
+
}
|
|
218
|
+
cached.rest = Buffer.from(buf.subarray(end + 1))
|
|
219
|
+
for (const line of buf.toString('utf8', 0, end).split('\n')) {
|
|
220
|
+
if (line) ingestLine(cached.entries, line)
|
|
221
|
+
}
|
|
184
222
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
entries.push({
|
|
190
|
-
ts,
|
|
191
|
-
model: obj.message.model,
|
|
192
|
-
tokens: tokensOf({ usage: obj.message.usage }),
|
|
193
|
-
key: `${obj.message.id || ''}:${obj.requestId || obj.uuid || ''}`,
|
|
194
|
-
})
|
|
223
|
+
} catch {
|
|
224
|
+
// unreadable mid-way: keep what we have, retry from the offset next poll
|
|
225
|
+
} finally {
|
|
226
|
+
fs.closeSync(fd)
|
|
195
227
|
}
|
|
196
|
-
|
|
197
|
-
return entries
|
|
228
|
+
return cached.entries
|
|
198
229
|
}
|
|
199
230
|
|
|
200
231
|
const DAYS = 30
|