@usagefleet/cli 1.2.151 → 1.2.152
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/dist/collector.js +17 -7
- package/dist/release.js +1 -1
- package/dist/tailer.js +54 -15
- package/package.json +1 -1
package/dist/collector.js
CHANGED
|
@@ -75,7 +75,7 @@ export async function runOnce(cfg, log = () => {
|
|
|
75
75
|
if (!tail || tail.consumedBytes === 0) {
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
|
-
if (tail.
|
|
78
|
+
if (tail.segments.length === 0) {
|
|
79
79
|
// Consumed only non-usage lines — safe to advance immediately.
|
|
80
80
|
state.files[fp] = tail.nextState;
|
|
81
81
|
advanced = true;
|
|
@@ -83,8 +83,8 @@ export async function runOnce(cfg, log = () => {
|
|
|
83
83
|
}
|
|
84
84
|
// sendChunk absorbs "invalid" by bisecting, so only auth/plan/transient escape.
|
|
85
85
|
let outcome = 'ok';
|
|
86
|
-
for (
|
|
87
|
-
outcome = await sendChunk(
|
|
86
|
+
for (const chunk of chunks(tail.segments, step)) {
|
|
87
|
+
outcome = await sendChunk(chunk.records, chunk.accountExtId, cfg, result, log);
|
|
88
88
|
if (outcome !== 'ok') {
|
|
89
89
|
break;
|
|
90
90
|
}
|
|
@@ -151,9 +151,9 @@ export async function runOnce(cfg, log = () => {
|
|
|
151
151
|
* anything that survives that many splits is not record-shaped.
|
|
152
152
|
*/
|
|
153
153
|
const MAX_DROPPED_PER_CHUNK = 2;
|
|
154
|
-
async function sendChunk(records, cfg, result, log, dropCeiling = result.dropped + MAX_DROPPED_PER_CHUNK) {
|
|
154
|
+
async function sendChunk(records, accountExtId, cfg, result, log, dropCeiling = result.dropped + MAX_DROPPED_PER_CHUNK) {
|
|
155
155
|
const res = await uploadBatch({
|
|
156
|
-
accountExtId: detectClaudeAccount()?.extId,
|
|
156
|
+
accountExtId: accountExtId ?? detectClaudeAccount()?.extId,
|
|
157
157
|
collectorVersion: RELEASE_VERSION,
|
|
158
158
|
hostname: hostname(),
|
|
159
159
|
os: detectOs(),
|
|
@@ -185,8 +185,18 @@ async function sendChunk(records, cfg, result, log, dropCeiling = result.dropped
|
|
|
185
185
|
return 'ok';
|
|
186
186
|
}
|
|
187
187
|
const mid = Math.ceil(records.length / 2);
|
|
188
|
-
const head = await sendChunk(records.slice(0, mid), cfg, result, log, dropCeiling);
|
|
189
|
-
return head === 'ok' ? sendChunk(records.slice(mid), cfg, result, log, dropCeiling) : head;
|
|
188
|
+
const head = await sendChunk(records.slice(0, mid), accountExtId, cfg, result, log, dropCeiling);
|
|
189
|
+
return head === 'ok' ? sendChunk(records.slice(mid), accountExtId, cfg, result, log, dropCeiling) : head;
|
|
190
|
+
}
|
|
191
|
+
/** Upload-sized slices of each segment; a chunk never spans two accounts. */
|
|
192
|
+
function chunks(segments, step) {
|
|
193
|
+
const out = [];
|
|
194
|
+
for (const { accountExtId, records } of segments) {
|
|
195
|
+
for (let i = 0; i < records.length; i += step) {
|
|
196
|
+
out.push({ accountExtId, records: records.slice(i, i + step) });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
190
200
|
}
|
|
191
201
|
/**
|
|
192
202
|
* Drop offsets for logs that no longer exist on disk, so a long-lived install
|
package/dist/release.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by .github/workflows/release.yml.
|
|
2
|
-
export const RELEASE_VERSION = "1.2.
|
|
2
|
+
export const RELEASE_VERSION = "1.2.152";
|
package/dist/tailer.js
CHANGED
|
@@ -5,6 +5,8 @@ import { dim, line, tilde, yellow } from './ui.js';
|
|
|
5
5
|
const MAX_READ = 16 * 1024 * 1024;
|
|
6
6
|
// Both parsers need a `usage` object, and about 3/4 of log bytes are lines without one.
|
|
7
7
|
const USAGE_KEY = Buffer.from('"usage"');
|
|
8
|
+
// Unescaped quotes: the same text quoted inside a message is `\"customType\"` and never matches.
|
|
9
|
+
const ACCOUNT_KEY = Buffer.from('"customType":"claude-account"');
|
|
8
10
|
/**
|
|
9
11
|
* The working directory of a pi session, read from the file's first line
|
|
10
12
|
* (`{"type":"session",…,"cwd":"/path"}`). pi's message lines carry no cwd, and
|
|
@@ -26,6 +28,18 @@ function piSessionCwd(fd) {
|
|
|
26
28
|
return null;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
/** The account a pi session pinned itself to, from the dotfiles claude-accounts extension's
|
|
32
|
+
* `{"type":"custom","customType":"claude-account","data":{"email","uuid"}}` line. Every
|
|
33
|
+
* later line in the file ran on it, until the next such line. */
|
|
34
|
+
function piSessionAccount(line) {
|
|
35
|
+
try {
|
|
36
|
+
const uuid = JSON.parse(line)?.data?.uuid;
|
|
37
|
+
return typeof uuid === 'string' && uuid !== '' ? uuid.slice(0, 100) : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
29
43
|
/**
|
|
30
44
|
* Read new, newline-terminated lines from `filePath` starting at the previously
|
|
31
45
|
* stored offset. Handles rotation/truncation (inode change or size < offset →
|
|
@@ -41,9 +55,10 @@ export function tailFile(filePath, prev, source = 'cli') {
|
|
|
41
55
|
}
|
|
42
56
|
const rotated = prev !== undefined && (st.ino !== prev.inode || st.size < prev.offset);
|
|
43
57
|
const start = rotated || prev === undefined ? 0 : prev.offset;
|
|
44
|
-
const
|
|
58
|
+
const startAccount = rotated ? undefined : prev?.accountExtId;
|
|
59
|
+
const base = { accountExtId: startAccount, inode: Number(st.ino), offset: start };
|
|
45
60
|
if (st.size <= start) {
|
|
46
|
-
return { consumedBytes: 0, nextState: base,
|
|
61
|
+
return { consumedBytes: 0, nextState: base, segments: [] };
|
|
47
62
|
}
|
|
48
63
|
// Cap the per-cycle read so a huge backlog can't OOM; the next cycle resumes
|
|
49
64
|
// from the committed offset.
|
|
@@ -70,29 +85,53 @@ export function tailFile(filePath, prev, source = 'cli') {
|
|
|
70
85
|
return {
|
|
71
86
|
consumedBytes: length,
|
|
72
87
|
nextState: { ...base, offset: start + length },
|
|
73
|
-
|
|
88
|
+
segments: [],
|
|
74
89
|
};
|
|
75
90
|
}
|
|
76
|
-
return { consumedBytes: 0, nextState: base,
|
|
91
|
+
return { consumedBytes: 0, nextState: base, segments: [] };
|
|
77
92
|
}
|
|
78
93
|
const consumed = buf.subarray(0, lastNl + 1);
|
|
79
|
-
// Decode only the lines that can hold usage: decoding and
|
|
80
|
-
// 16 MB read peaked a first-run backlog at ~490 MB RSS.
|
|
81
|
-
const
|
|
82
|
-
let
|
|
83
|
-
|
|
94
|
+
// Decode only the lines that can hold usage or a pi account switch: decoding and
|
|
95
|
+
// splitting the whole 16 MB read peaked a first-run backlog at ~490 MB RSS.
|
|
96
|
+
const segments = [];
|
|
97
|
+
let account = startAccount;
|
|
98
|
+
let usageAt = consumed.indexOf(USAGE_KEY);
|
|
99
|
+
let accountAt = source === 'pi' ? consumed.indexOf(ACCOUNT_KEY) : -1;
|
|
100
|
+
while (usageAt !== -1 || accountAt !== -1) {
|
|
101
|
+
const isAccountLine = accountAt !== -1 && (usageAt === -1 || accountAt < usageAt);
|
|
102
|
+
const at = isAccountLine ? accountAt : usageAt;
|
|
84
103
|
const lineStart = consumed.lastIndexOf(0x0a, at) + 1;
|
|
85
104
|
const lineEnd = consumed.indexOf(0x0a, at);
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
105
|
+
const text = consumed.toString('utf-8', lineStart, lineEnd);
|
|
106
|
+
if (isAccountLine) {
|
|
107
|
+
account = piSessionAccount(text);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const rec = parseLine(text, source, sessionCwd);
|
|
111
|
+
if (rec) {
|
|
112
|
+
addRecord(segments, account, rec);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (usageAt !== -1 && usageAt < lineEnd) {
|
|
116
|
+
usageAt = consumed.indexOf(USAGE_KEY, lineEnd);
|
|
117
|
+
}
|
|
118
|
+
if (accountAt !== -1 && accountAt < lineEnd) {
|
|
119
|
+
accountAt = consumed.indexOf(ACCOUNT_KEY, lineEnd);
|
|
89
120
|
}
|
|
90
|
-
at = consumed.indexOf(USAGE_KEY, lineEnd);
|
|
91
121
|
}
|
|
92
122
|
const consumedBytes = consumed.length;
|
|
93
123
|
return {
|
|
94
124
|
consumedBytes,
|
|
95
|
-
nextState: {
|
|
96
|
-
|
|
125
|
+
nextState: { accountExtId: account, inode: base.inode, offset: start + consumedBytes },
|
|
126
|
+
segments,
|
|
97
127
|
};
|
|
98
128
|
}
|
|
129
|
+
function addRecord(segments, accountExtId, record) {
|
|
130
|
+
const last = segments.at(-1);
|
|
131
|
+
if (last && last.accountExtId === accountExtId) {
|
|
132
|
+
last.records.push(record);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
segments.push({ accountExtId, records: [record] });
|
|
136
|
+
}
|
|
137
|
+
}
|