@praeviso/code-env-switch 0.1.2 → 0.1.3
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/bin/statusline/index.js +175 -13
- package/bin/usage/index.js +133 -10
- package/package.json +1 -1
- package/src/statusline/index.ts +315 -21
- package/src/usage/index.ts +156 -10
package/bin/statusline/index.js
CHANGED
|
@@ -139,32 +139,169 @@ function getInputProfile(input) {
|
|
|
139
139
|
return input.profile;
|
|
140
140
|
}
|
|
141
141
|
function getInputUsage(input) {
|
|
142
|
-
var _a, _b, _c, _d;
|
|
142
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
143
143
|
if (!input)
|
|
144
144
|
return null;
|
|
145
145
|
if (isRecord(input.usage)) {
|
|
146
146
|
return input.usage;
|
|
147
147
|
}
|
|
148
148
|
const tokenUsage = input.token_usage;
|
|
149
|
-
if (tokenUsage
|
|
149
|
+
if (tokenUsage !== null && tokenUsage !== undefined) {
|
|
150
|
+
if (typeof tokenUsage === "number") {
|
|
151
|
+
return {
|
|
152
|
+
todayTokens: null,
|
|
153
|
+
totalTokens: coerceNumber(tokenUsage),
|
|
154
|
+
inputTokens: null,
|
|
155
|
+
outputTokens: null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (isRecord(tokenUsage)) {
|
|
159
|
+
const record = tokenUsage;
|
|
160
|
+
const todayTokens = (_a = firstNumber(record.todayTokens, record.today, record.today_tokens, record.daily, record.daily_tokens)) !== null && _a !== void 0 ? _a : null;
|
|
161
|
+
const totalTokens = (_b = firstNumber(record.totalTokens, record.total, record.total_tokens)) !== null && _b !== void 0 ? _b : null;
|
|
162
|
+
const inputTokens = (_c = firstNumber(record.inputTokens, record.input, record.input_tokens)) !== null && _c !== void 0 ? _c : null;
|
|
163
|
+
const outputTokens = (_d = firstNumber(record.outputTokens, record.output, record.output_tokens)) !== null && _d !== void 0 ? _d : null;
|
|
164
|
+
const cacheRead = (_e = firstNumber(record.cache_read_input_tokens, record.cacheReadInputTokens, record.cache_read, record.cacheRead)) !== null && _e !== void 0 ? _e : null;
|
|
165
|
+
const cacheWrite = (_f = firstNumber(record.cache_creation_input_tokens, record.cacheCreationInputTokens, record.cache_write_input_tokens, record.cacheWriteInputTokens, record.cache_write, record.cacheWrite)) !== null && _f !== void 0 ? _f : null;
|
|
166
|
+
if (todayTokens === null &&
|
|
167
|
+
totalTokens === null &&
|
|
168
|
+
inputTokens === null &&
|
|
169
|
+
outputTokens === null &&
|
|
170
|
+
cacheRead === null &&
|
|
171
|
+
cacheWrite === null) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
|
|
175
|
+
const computedTotal = hasCacheTokens
|
|
176
|
+
? (inputTokens || 0) +
|
|
177
|
+
(outputTokens || 0) +
|
|
178
|
+
(cacheRead || 0) +
|
|
179
|
+
(cacheWrite || 0)
|
|
180
|
+
: null;
|
|
181
|
+
const resolvedTodayTokens = hasCacheTokens
|
|
182
|
+
? (_g = todayTokens !== null && todayTokens !== void 0 ? todayTokens : totalTokens) !== null && _g !== void 0 ? _g : computedTotal
|
|
183
|
+
: todayTokens;
|
|
184
|
+
return {
|
|
185
|
+
todayTokens: resolvedTodayTokens,
|
|
186
|
+
totalTokens: totalTokens !== null && totalTokens !== void 0 ? totalTokens : null,
|
|
187
|
+
inputTokens,
|
|
188
|
+
outputTokens,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const contextWindow = isRecord(input.context_window)
|
|
193
|
+
? input.context_window
|
|
194
|
+
: isRecord(input.contextWindow)
|
|
195
|
+
? input.contextWindow
|
|
196
|
+
: null;
|
|
197
|
+
if (!contextWindow)
|
|
150
198
|
return null;
|
|
151
|
-
|
|
199
|
+
const totalInputTokens = (_h = firstNumber(contextWindow.total_input_tokens, contextWindow.totalInputTokens)) !== null && _h !== void 0 ? _h : null;
|
|
200
|
+
const totalOutputTokens = (_j = firstNumber(contextWindow.total_output_tokens, contextWindow.totalOutputTokens)) !== null && _j !== void 0 ? _j : null;
|
|
201
|
+
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
152
202
|
return {
|
|
153
203
|
todayTokens: null,
|
|
154
|
-
totalTokens:
|
|
155
|
-
inputTokens:
|
|
156
|
-
outputTokens:
|
|
204
|
+
totalTokens: null,
|
|
205
|
+
inputTokens: totalInputTokens,
|
|
206
|
+
outputTokens: totalOutputTokens,
|
|
157
207
|
};
|
|
158
208
|
}
|
|
159
|
-
|
|
160
|
-
|
|
209
|
+
const currentUsage = isRecord(contextWindow.current_usage)
|
|
210
|
+
? contextWindow.current_usage
|
|
211
|
+
: isRecord(contextWindow.currentUsage)
|
|
212
|
+
? contextWindow.currentUsage
|
|
213
|
+
: null;
|
|
214
|
+
if (!currentUsage)
|
|
215
|
+
return null;
|
|
216
|
+
const inputTokens = (_k = firstNumber(currentUsage.input_tokens, currentUsage.inputTokens)) !== null && _k !== void 0 ? _k : null;
|
|
217
|
+
const outputTokens = (_l = firstNumber(currentUsage.output_tokens, currentUsage.outputTokens)) !== null && _l !== void 0 ? _l : null;
|
|
218
|
+
const cacheRead = (_m = firstNumber(currentUsage.cache_read_input_tokens, currentUsage.cacheReadInputTokens)) !== null && _m !== void 0 ? _m : null;
|
|
219
|
+
const cacheWrite = (_o = firstNumber(currentUsage.cache_creation_input_tokens, currentUsage.cacheCreationInputTokens)) !== null && _o !== void 0 ? _o : null;
|
|
220
|
+
if (inputTokens === null &&
|
|
221
|
+
outputTokens === null &&
|
|
222
|
+
cacheRead === null &&
|
|
223
|
+
cacheWrite === null) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const totalTokens = (inputTokens || 0) +
|
|
227
|
+
(outputTokens || 0) +
|
|
228
|
+
(cacheRead || 0) +
|
|
229
|
+
(cacheWrite || 0);
|
|
230
|
+
return {
|
|
231
|
+
todayTokens: totalTokens,
|
|
232
|
+
totalTokens: null,
|
|
233
|
+
inputTokens,
|
|
234
|
+
outputTokens,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function getSessionId(input) {
|
|
238
|
+
if (!input)
|
|
239
|
+
return null;
|
|
240
|
+
return firstNonEmpty(input.session_id, input.sessionId);
|
|
241
|
+
}
|
|
242
|
+
function parseUsageTotalsRecord(record) {
|
|
243
|
+
var _a, _b, _c, _d, _e;
|
|
244
|
+
const inputTokens = (_a = firstNumber(record.inputTokens, record.input, record.input_tokens)) !== null && _a !== void 0 ? _a : null;
|
|
245
|
+
const outputTokens = (_b = firstNumber(record.outputTokens, record.output, record.output_tokens)) !== null && _b !== void 0 ? _b : null;
|
|
246
|
+
const totalTokens = (_c = firstNumber(record.totalTokens, record.total, record.total_tokens)) !== null && _c !== void 0 ? _c : null;
|
|
247
|
+
const cacheRead = (_d = firstNumber(record.cache_read_input_tokens, record.cacheReadInputTokens, record.cache_read, record.cacheRead)) !== null && _d !== void 0 ? _d : null;
|
|
248
|
+
const cacheWrite = (_e = firstNumber(record.cache_creation_input_tokens, record.cacheCreationInputTokens, record.cache_write_input_tokens, record.cacheWriteInputTokens, record.cache_write, record.cacheWrite)) !== null && _e !== void 0 ? _e : null;
|
|
249
|
+
let computedTotal = null;
|
|
250
|
+
if (inputTokens !== null ||
|
|
251
|
+
outputTokens !== null ||
|
|
252
|
+
cacheRead !== null ||
|
|
253
|
+
cacheWrite !== null) {
|
|
254
|
+
computedTotal =
|
|
255
|
+
(inputTokens || 0) +
|
|
256
|
+
(outputTokens || 0) +
|
|
257
|
+
(cacheRead || 0) +
|
|
258
|
+
(cacheWrite || 0);
|
|
259
|
+
}
|
|
260
|
+
const resolvedTotal = totalTokens !== null && totalTokens !== void 0 ? totalTokens : computedTotal;
|
|
261
|
+
if (inputTokens === null &&
|
|
262
|
+
outputTokens === null &&
|
|
263
|
+
resolvedTotal === null) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
inputTokens,
|
|
268
|
+
outputTokens,
|
|
269
|
+
totalTokens: resolvedTotal,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function getUsageTotalsFromInput(input) {
|
|
273
|
+
var _a, _b;
|
|
274
|
+
if (!input)
|
|
275
|
+
return null;
|
|
276
|
+
const contextWindow = isRecord(input.context_window)
|
|
277
|
+
? input.context_window
|
|
278
|
+
: isRecord(input.contextWindow)
|
|
279
|
+
? input.contextWindow
|
|
280
|
+
: null;
|
|
281
|
+
if (contextWindow) {
|
|
282
|
+
const totalInputTokens = (_a = firstNumber(contextWindow.total_input_tokens, contextWindow.totalInputTokens)) !== null && _a !== void 0 ? _a : null;
|
|
283
|
+
const totalOutputTokens = (_b = firstNumber(contextWindow.total_output_tokens, contextWindow.totalOutputTokens)) !== null && _b !== void 0 ? _b : null;
|
|
284
|
+
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
285
|
+
return {
|
|
286
|
+
inputTokens: totalInputTokens,
|
|
287
|
+
outputTokens: totalOutputTokens,
|
|
288
|
+
totalTokens: (totalInputTokens || 0) + (totalOutputTokens || 0),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (typeof input.token_usage === "number") {
|
|
161
293
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
outputTokens: (_d = firstNumber(record.outputTokens, record.output, record.output_tokens)) !== null && _d !== void 0 ? _d : null,
|
|
294
|
+
inputTokens: null,
|
|
295
|
+
outputTokens: null,
|
|
296
|
+
totalTokens: coerceNumber(input.token_usage),
|
|
166
297
|
};
|
|
167
298
|
}
|
|
299
|
+
if (isRecord(input.token_usage)) {
|
|
300
|
+
return parseUsageTotalsRecord(input.token_usage);
|
|
301
|
+
}
|
|
302
|
+
if (isRecord(input.usage)) {
|
|
303
|
+
return parseUsageTotalsRecord(input.usage);
|
|
304
|
+
}
|
|
168
305
|
return null;
|
|
169
306
|
}
|
|
170
307
|
function getContextUsedTokens(input) {
|
|
@@ -172,6 +309,21 @@ function getContextUsedTokens(input) {
|
|
|
172
309
|
return null;
|
|
173
310
|
return coerceNumber(input.context_window_used_tokens);
|
|
174
311
|
}
|
|
312
|
+
function normalizeInputUsage(inputUsage) {
|
|
313
|
+
if (!inputUsage)
|
|
314
|
+
return null;
|
|
315
|
+
const usage = {
|
|
316
|
+
todayTokens: coerceNumber(inputUsage.todayTokens),
|
|
317
|
+
totalTokens: coerceNumber(inputUsage.totalTokens),
|
|
318
|
+
inputTokens: coerceNumber(inputUsage.inputTokens),
|
|
319
|
+
outputTokens: coerceNumber(inputUsage.outputTokens),
|
|
320
|
+
};
|
|
321
|
+
const hasUsage = usage.todayTokens !== null ||
|
|
322
|
+
usage.totalTokens !== null ||
|
|
323
|
+
usage.inputTokens !== null ||
|
|
324
|
+
usage.outputTokens !== null;
|
|
325
|
+
return hasUsage ? usage : null;
|
|
326
|
+
}
|
|
175
327
|
function getContextLeftPercent(input, type) {
|
|
176
328
|
if (!input)
|
|
177
329
|
return null;
|
|
@@ -383,7 +535,7 @@ function buildStatuslineResult(args, config, configPath) {
|
|
|
383
535
|
}
|
|
384
536
|
let type = normalizeTypeValue(typeCandidate);
|
|
385
537
|
const envProfile = resolveEnvProfile(type);
|
|
386
|
-
|
|
538
|
+
const profileKey = firstNonEmpty(args.profileKey, envProfile.key, inputProfile ? inputProfile.key : null);
|
|
387
539
|
let profileName = firstNonEmpty(args.profileName, envProfile.name, inputProfile ? inputProfile.name : null);
|
|
388
540
|
if (profileKey && !profileName && config.profiles && config.profiles[profileKey]) {
|
|
389
541
|
const profile = config.profiles[profileKey];
|
|
@@ -401,6 +553,12 @@ function buildStatuslineResult(args, config, configPath) {
|
|
|
401
553
|
type = inferred;
|
|
402
554
|
}
|
|
403
555
|
const cwd = firstNonEmpty(args.cwd, process.env.CODE_ENV_CWD, getWorkspaceDir(stdinInput), stdinInput ? stdinInput.cwd : null, process.cwd());
|
|
556
|
+
const sessionId = getSessionId(stdinInput);
|
|
557
|
+
const stdinUsageTotals = getUsageTotalsFromInput(stdinInput);
|
|
558
|
+
if (args.syncUsage && sessionId && stdinUsageTotals) {
|
|
559
|
+
const usageType = (0, type_1.normalizeType)(type || "");
|
|
560
|
+
(0, usage_1.syncUsageFromStatuslineInput)(config, configPath, usageType, profileKey, profileName, sessionId, stdinUsageTotals, cwd);
|
|
561
|
+
}
|
|
404
562
|
const model = firstNonEmpty(args.model, process.env.CODE_ENV_MODEL, getModelFromInput(stdinInput));
|
|
405
563
|
const modelProvider = firstNonEmpty(process.env.CODE_ENV_MODEL_PROVIDER, getModelProviderFromInput(stdinInput));
|
|
406
564
|
const usage = {
|
|
@@ -413,7 +571,11 @@ function buildStatuslineResult(args, config, configPath) {
|
|
|
413
571
|
usage.totalTokens !== null ||
|
|
414
572
|
usage.inputTokens !== null ||
|
|
415
573
|
usage.outputTokens !== null;
|
|
574
|
+
const stdinUsage = normalizeInputUsage(getInputUsage(stdinInput));
|
|
416
575
|
let finalUsage = hasExplicitUsage ? usage : null;
|
|
576
|
+
if (!finalUsage) {
|
|
577
|
+
finalUsage = stdinUsage;
|
|
578
|
+
}
|
|
417
579
|
if (!finalUsage) {
|
|
418
580
|
finalUsage = resolveUsageFromRecords(config, configPath, type, profileKey, profileName, args.syncUsage);
|
|
419
581
|
}
|
package/bin/usage/index.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.formatTokenCount = formatTokenCount;
|
|
|
9
9
|
exports.buildUsageTotals = buildUsageTotals;
|
|
10
10
|
exports.readUsageTotalsIndex = readUsageTotalsIndex;
|
|
11
11
|
exports.resolveUsageTotalsForProfile = resolveUsageTotalsForProfile;
|
|
12
|
+
exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
|
|
12
13
|
exports.logProfileUse = logProfileUse;
|
|
13
14
|
exports.logSessionBinding = logSessionBinding;
|
|
14
15
|
exports.readSessionBindingIndex = readSessionBindingIndex;
|
|
@@ -120,6 +121,18 @@ function normalizeUsageType(type) {
|
|
|
120
121
|
const trimmed = String(type).trim();
|
|
121
122
|
return trimmed ? trimmed : null;
|
|
122
123
|
}
|
|
124
|
+
function buildSessionKey(type, sessionId) {
|
|
125
|
+
const normalized = normalizeUsageType(type || "");
|
|
126
|
+
return normalized ? `${normalized}::${sessionId}` : sessionId;
|
|
127
|
+
}
|
|
128
|
+
function toFiniteNumber(value) {
|
|
129
|
+
if (value === null || value === undefined)
|
|
130
|
+
return null;
|
|
131
|
+
const num = Number(value);
|
|
132
|
+
if (!Number.isFinite(num))
|
|
133
|
+
return null;
|
|
134
|
+
return num;
|
|
135
|
+
}
|
|
123
136
|
function buildUsageLookupKey(type, profileId) {
|
|
124
137
|
if (!profileId)
|
|
125
138
|
return null;
|
|
@@ -147,6 +160,75 @@ function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
|
|
|
147
160
|
(nameLookup && totals.byName.get(nameLookup)) ||
|
|
148
161
|
null);
|
|
149
162
|
}
|
|
163
|
+
function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd) {
|
|
164
|
+
var _a, _b, _c;
|
|
165
|
+
if (!sessionId)
|
|
166
|
+
return;
|
|
167
|
+
if (!totals)
|
|
168
|
+
return;
|
|
169
|
+
if (!profileKey && !profileName)
|
|
170
|
+
return;
|
|
171
|
+
const normalizedType = (0, type_1.normalizeType)(type || "");
|
|
172
|
+
if (!normalizedType)
|
|
173
|
+
return;
|
|
174
|
+
const usagePath = getUsagePath(config, configPath);
|
|
175
|
+
if (!usagePath)
|
|
176
|
+
return;
|
|
177
|
+
const inputTokens = (_a = toFiniteNumber(totals.inputTokens)) !== null && _a !== void 0 ? _a : 0;
|
|
178
|
+
const outputTokens = (_b = toFiniteNumber(totals.outputTokens)) !== null && _b !== void 0 ? _b : 0;
|
|
179
|
+
const totalTokens = (_c = toFiniteNumber(totals.totalTokens)) !== null && _c !== void 0 ? _c : inputTokens + outputTokens;
|
|
180
|
+
if (!Number.isFinite(totalTokens))
|
|
181
|
+
return;
|
|
182
|
+
const statePath = getUsageStatePath(usagePath, config);
|
|
183
|
+
const lockPath = `${statePath}.lock`;
|
|
184
|
+
const lockFd = acquireLock(lockPath);
|
|
185
|
+
if (lockFd === null)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const state = readUsageState(statePath);
|
|
189
|
+
const sessions = state.sessions || {};
|
|
190
|
+
const key = buildSessionKey(normalizedType, sessionId);
|
|
191
|
+
const prev = sessions[key];
|
|
192
|
+
const prevInput = prev ? prev.inputTokens : 0;
|
|
193
|
+
const prevOutput = prev ? prev.outputTokens : 0;
|
|
194
|
+
const prevTotal = prev ? prev.totalTokens : 0;
|
|
195
|
+
let deltaInput = inputTokens - prevInput;
|
|
196
|
+
let deltaOutput = outputTokens - prevOutput;
|
|
197
|
+
let deltaTotal = totalTokens - prevTotal;
|
|
198
|
+
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
199
|
+
deltaInput = inputTokens;
|
|
200
|
+
deltaOutput = outputTokens;
|
|
201
|
+
deltaTotal = totalTokens;
|
|
202
|
+
}
|
|
203
|
+
if (deltaTotal > 0) {
|
|
204
|
+
const record = {
|
|
205
|
+
ts: new Date().toISOString(),
|
|
206
|
+
type: normalizedType,
|
|
207
|
+
profileKey: profileKey || null,
|
|
208
|
+
profileName: profileName || null,
|
|
209
|
+
inputTokens: deltaInput,
|
|
210
|
+
outputTokens: deltaOutput,
|
|
211
|
+
totalTokens: deltaTotal,
|
|
212
|
+
};
|
|
213
|
+
appendUsageRecord(usagePath, record);
|
|
214
|
+
}
|
|
215
|
+
const now = new Date().toISOString();
|
|
216
|
+
sessions[key] = {
|
|
217
|
+
type: normalizedType,
|
|
218
|
+
inputTokens,
|
|
219
|
+
outputTokens,
|
|
220
|
+
totalTokens,
|
|
221
|
+
startTs: prev ? prev.startTs : now,
|
|
222
|
+
endTs: now,
|
|
223
|
+
cwd: cwd || (prev ? prev.cwd : null),
|
|
224
|
+
};
|
|
225
|
+
state.sessions = sessions;
|
|
226
|
+
writeUsageState(statePath, state);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
releaseLock(lockPath, lockFd);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
150
232
|
function logProfileUse(config, configPath, profileKey, requestedType, terminalTag, cwd) {
|
|
151
233
|
const profile = config.profiles && config.profiles[profileKey];
|
|
152
234
|
if (!profile)
|
|
@@ -285,19 +367,20 @@ function resolveProfileForSession(config, logEntries, type, sessionFile, session
|
|
|
285
367
|
}
|
|
286
368
|
function readUsageState(statePath) {
|
|
287
369
|
if (!statePath || !fs.existsSync(statePath)) {
|
|
288
|
-
return { version: 1, files: {} };
|
|
370
|
+
return { version: 1, files: {}, sessions: {} };
|
|
289
371
|
}
|
|
290
372
|
try {
|
|
291
373
|
const raw = fs.readFileSync(statePath, "utf8");
|
|
292
374
|
const parsed = JSON.parse(raw);
|
|
293
375
|
if (!parsed || typeof parsed !== "object") {
|
|
294
|
-
return { version: 1, files: {} };
|
|
376
|
+
return { version: 1, files: {}, sessions: {} };
|
|
295
377
|
}
|
|
296
378
|
const files = parsed.files && typeof parsed.files === "object" ? parsed.files : {};
|
|
297
|
-
|
|
379
|
+
const sessions = parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
|
|
380
|
+
return { version: 1, files, sessions };
|
|
298
381
|
}
|
|
299
382
|
catch {
|
|
300
|
-
return { version: 1, files: {} };
|
|
383
|
+
return { version: 1, files: {}, sessions: {} };
|
|
301
384
|
}
|
|
302
385
|
}
|
|
303
386
|
function writeUsageState(statePath, state) {
|
|
@@ -632,6 +715,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
632
715
|
const logEntries = readProfileLogEntries([profileLogPath]);
|
|
633
716
|
const state = readUsageState(statePath);
|
|
634
717
|
const files = state.files || {};
|
|
718
|
+
const sessions = state.sessions || {};
|
|
635
719
|
const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
|
|
636
720
|
const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
|
|
637
721
|
const processFile = (filePath, type) => {
|
|
@@ -661,16 +745,34 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
661
745
|
const resolved = resolveProfileForSession(config, logEntries, type, filePath, stats.sessionId);
|
|
662
746
|
if (!resolved.match)
|
|
663
747
|
return;
|
|
748
|
+
const sessionKey = stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
|
|
749
|
+
const sessionPrev = sessionKey ? sessions[sessionKey] : null;
|
|
664
750
|
const prevInput = prev ? prev.inputTokens : 0;
|
|
665
751
|
const prevOutput = prev ? prev.outputTokens : 0;
|
|
666
752
|
const prevTotal = prev ? prev.totalTokens : 0;
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
753
|
+
const prevInputMax = sessionPrev
|
|
754
|
+
? Math.max(prevInput, sessionPrev.inputTokens)
|
|
755
|
+
: prevInput;
|
|
756
|
+
const prevOutputMax = sessionPrev
|
|
757
|
+
? Math.max(prevOutput, sessionPrev.outputTokens)
|
|
758
|
+
: prevOutput;
|
|
759
|
+
const prevTotalMax = sessionPrev
|
|
760
|
+
? Math.max(prevTotal, sessionPrev.totalTokens)
|
|
761
|
+
: prevTotal;
|
|
762
|
+
let deltaInput = stats.inputTokens - prevInputMax;
|
|
763
|
+
let deltaOutput = stats.outputTokens - prevOutputMax;
|
|
764
|
+
let deltaTotal = stats.totalTokens - prevTotalMax;
|
|
670
765
|
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
766
|
+
if (sessionPrev) {
|
|
767
|
+
deltaInput = 0;
|
|
768
|
+
deltaOutput = 0;
|
|
769
|
+
deltaTotal = 0;
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
deltaInput = stats.inputTokens;
|
|
773
|
+
deltaOutput = stats.outputTokens;
|
|
774
|
+
deltaTotal = stats.totalTokens;
|
|
775
|
+
}
|
|
674
776
|
}
|
|
675
777
|
if (deltaTotal > 0) {
|
|
676
778
|
const record = {
|
|
@@ -684,6 +786,26 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
684
786
|
};
|
|
685
787
|
appendUsageRecord(usagePath, record);
|
|
686
788
|
}
|
|
789
|
+
if (sessionKey) {
|
|
790
|
+
const nextInput = sessionPrev
|
|
791
|
+
? Math.max(sessionPrev.inputTokens, stats.inputTokens)
|
|
792
|
+
: stats.inputTokens;
|
|
793
|
+
const nextOutput = sessionPrev
|
|
794
|
+
? Math.max(sessionPrev.outputTokens, stats.outputTokens)
|
|
795
|
+
: stats.outputTokens;
|
|
796
|
+
const nextTotal = sessionPrev
|
|
797
|
+
? Math.max(sessionPrev.totalTokens, stats.totalTokens)
|
|
798
|
+
: stats.totalTokens;
|
|
799
|
+
sessions[sessionKey] = {
|
|
800
|
+
type,
|
|
801
|
+
inputTokens: nextInput,
|
|
802
|
+
outputTokens: nextOutput,
|
|
803
|
+
totalTokens: nextTotal,
|
|
804
|
+
startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
|
|
805
|
+
endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
|
|
806
|
+
cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
|
|
807
|
+
};
|
|
808
|
+
}
|
|
687
809
|
files[filePath] = {
|
|
688
810
|
mtimeMs: stat.mtimeMs,
|
|
689
811
|
size: stat.size,
|
|
@@ -701,6 +823,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
701
823
|
for (const filePath of claudeFiles)
|
|
702
824
|
processFile(filePath, "claude");
|
|
703
825
|
state.files = files;
|
|
826
|
+
state.sessions = sessions;
|
|
704
827
|
writeUsageState(statePath, state);
|
|
705
828
|
}
|
|
706
829
|
finally {
|
package/package.json
CHANGED
package/src/statusline/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
formatTokenCount,
|
|
12
12
|
readUsageTotalsIndex,
|
|
13
13
|
resolveUsageTotalsForProfile,
|
|
14
|
+
syncUsageFromStatuslineInput,
|
|
14
15
|
} from "../usage";
|
|
15
16
|
|
|
16
17
|
interface StatuslineInputProfile {
|
|
@@ -26,6 +27,28 @@ interface StatuslineInputUsage {
|
|
|
26
27
|
outputTokens?: number;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
interface StatuslineInputContextWindowUsage {
|
|
31
|
+
input_tokens?: number;
|
|
32
|
+
output_tokens?: number;
|
|
33
|
+
cache_creation_input_tokens?: number;
|
|
34
|
+
cache_read_input_tokens?: number;
|
|
35
|
+
inputTokens?: number;
|
|
36
|
+
outputTokens?: number;
|
|
37
|
+
cacheCreationInputTokens?: number;
|
|
38
|
+
cacheReadInputTokens?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface StatuslineInputContextWindow {
|
|
42
|
+
current_usage?: StatuslineInputContextWindowUsage | null;
|
|
43
|
+
total_input_tokens?: number;
|
|
44
|
+
total_output_tokens?: number;
|
|
45
|
+
context_window_size?: number;
|
|
46
|
+
currentUsage?: StatuslineInputContextWindowUsage | null;
|
|
47
|
+
totalInputTokens?: number;
|
|
48
|
+
totalOutputTokens?: number;
|
|
49
|
+
contextWindowSize?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
interface StatuslineInputModel {
|
|
30
53
|
id?: string;
|
|
31
54
|
displayName?: string;
|
|
@@ -45,6 +68,8 @@ interface StatuslineInput {
|
|
|
45
68
|
review_mode?: boolean;
|
|
46
69
|
context_window_percent?: number;
|
|
47
70
|
context_window_used_tokens?: number;
|
|
71
|
+
context_window?: StatuslineInputContextWindow | Record<string, unknown> | null;
|
|
72
|
+
contextWindow?: StatuslineInputContextWindow | Record<string, unknown> | null;
|
|
48
73
|
workspace?: {
|
|
49
74
|
current_dir?: string;
|
|
50
75
|
project_dir?: string;
|
|
@@ -53,6 +78,7 @@ interface StatuslineInput {
|
|
|
53
78
|
version?: string;
|
|
54
79
|
output_style?: { name?: string };
|
|
55
80
|
session_id?: string;
|
|
81
|
+
sessionId?: string;
|
|
56
82
|
transcript_path?: string;
|
|
57
83
|
hook_event_name?: string;
|
|
58
84
|
}
|
|
@@ -64,6 +90,12 @@ interface StatuslineUsage {
|
|
|
64
90
|
outputTokens: number | null;
|
|
65
91
|
}
|
|
66
92
|
|
|
93
|
+
interface StatuslineUsageTotals {
|
|
94
|
+
inputTokens: number | null;
|
|
95
|
+
outputTokens: number | null;
|
|
96
|
+
totalTokens: number | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
67
99
|
interface GitStatus {
|
|
68
100
|
branch: string | null;
|
|
69
101
|
ahead: number;
|
|
@@ -218,46 +250,269 @@ function getInputUsage(input: StatuslineInput | null): StatuslineInputUsage | nu
|
|
|
218
250
|
return input.usage as StatuslineInputUsage;
|
|
219
251
|
}
|
|
220
252
|
const tokenUsage = input.token_usage;
|
|
221
|
-
if (tokenUsage
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
todayTokens:
|
|
253
|
+
if (tokenUsage !== null && tokenUsage !== undefined) {
|
|
254
|
+
if (typeof tokenUsage === "number") {
|
|
255
|
+
return {
|
|
256
|
+
todayTokens: null,
|
|
257
|
+
totalTokens: coerceNumber(tokenUsage),
|
|
258
|
+
inputTokens: null,
|
|
259
|
+
outputTokens: null,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (isRecord(tokenUsage)) {
|
|
263
|
+
const record = tokenUsage as Record<string, unknown>;
|
|
264
|
+
const todayTokens =
|
|
234
265
|
firstNumber(
|
|
235
266
|
record.todayTokens,
|
|
236
267
|
record.today,
|
|
237
268
|
record.today_tokens,
|
|
238
269
|
record.daily,
|
|
239
270
|
record.daily_tokens
|
|
240
|
-
) ?? null
|
|
241
|
-
totalTokens
|
|
271
|
+
) ?? null;
|
|
272
|
+
const totalTokens =
|
|
242
273
|
firstNumber(
|
|
243
274
|
record.totalTokens,
|
|
244
275
|
record.total,
|
|
245
276
|
record.total_tokens
|
|
246
|
-
) ?? null
|
|
247
|
-
inputTokens
|
|
277
|
+
) ?? null;
|
|
278
|
+
const inputTokens =
|
|
248
279
|
firstNumber(
|
|
249
280
|
record.inputTokens,
|
|
250
281
|
record.input,
|
|
251
282
|
record.input_tokens
|
|
252
|
-
) ?? null
|
|
253
|
-
outputTokens
|
|
283
|
+
) ?? null;
|
|
284
|
+
const outputTokens =
|
|
254
285
|
firstNumber(
|
|
255
286
|
record.outputTokens,
|
|
256
287
|
record.output,
|
|
257
288
|
record.output_tokens
|
|
258
|
-
) ?? null
|
|
289
|
+
) ?? null;
|
|
290
|
+
const cacheRead =
|
|
291
|
+
firstNumber(
|
|
292
|
+
record.cache_read_input_tokens,
|
|
293
|
+
record.cacheReadInputTokens,
|
|
294
|
+
record.cache_read,
|
|
295
|
+
record.cacheRead
|
|
296
|
+
) ?? null;
|
|
297
|
+
const cacheWrite =
|
|
298
|
+
firstNumber(
|
|
299
|
+
record.cache_creation_input_tokens,
|
|
300
|
+
record.cacheCreationInputTokens,
|
|
301
|
+
record.cache_write_input_tokens,
|
|
302
|
+
record.cacheWriteInputTokens,
|
|
303
|
+
record.cache_write,
|
|
304
|
+
record.cacheWrite
|
|
305
|
+
) ?? null;
|
|
306
|
+
if (
|
|
307
|
+
todayTokens === null &&
|
|
308
|
+
totalTokens === null &&
|
|
309
|
+
inputTokens === null &&
|
|
310
|
+
outputTokens === null &&
|
|
311
|
+
cacheRead === null &&
|
|
312
|
+
cacheWrite === null
|
|
313
|
+
) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
|
|
317
|
+
const computedTotal = hasCacheTokens
|
|
318
|
+
? (inputTokens || 0) +
|
|
319
|
+
(outputTokens || 0) +
|
|
320
|
+
(cacheRead || 0) +
|
|
321
|
+
(cacheWrite || 0)
|
|
322
|
+
: null;
|
|
323
|
+
const resolvedTodayTokens = hasCacheTokens
|
|
324
|
+
? todayTokens ?? totalTokens ?? computedTotal
|
|
325
|
+
: todayTokens;
|
|
326
|
+
return {
|
|
327
|
+
todayTokens: resolvedTodayTokens,
|
|
328
|
+
totalTokens: totalTokens ?? null,
|
|
329
|
+
inputTokens,
|
|
330
|
+
outputTokens,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const contextWindow = isRecord(input.context_window)
|
|
335
|
+
? (input.context_window as Record<string, unknown>)
|
|
336
|
+
: isRecord(input.contextWindow)
|
|
337
|
+
? (input.contextWindow as Record<string, unknown>)
|
|
338
|
+
: null;
|
|
339
|
+
if (!contextWindow) return null;
|
|
340
|
+
const totalInputTokens =
|
|
341
|
+
firstNumber(
|
|
342
|
+
contextWindow.total_input_tokens,
|
|
343
|
+
contextWindow.totalInputTokens
|
|
344
|
+
) ?? null;
|
|
345
|
+
const totalOutputTokens =
|
|
346
|
+
firstNumber(
|
|
347
|
+
contextWindow.total_output_tokens,
|
|
348
|
+
contextWindow.totalOutputTokens
|
|
349
|
+
) ?? null;
|
|
350
|
+
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
351
|
+
return {
|
|
352
|
+
todayTokens: null,
|
|
353
|
+
totalTokens: null,
|
|
354
|
+
inputTokens: totalInputTokens,
|
|
355
|
+
outputTokens: totalOutputTokens,
|
|
259
356
|
};
|
|
260
357
|
}
|
|
358
|
+
const currentUsage = isRecord(contextWindow.current_usage)
|
|
359
|
+
? (contextWindow.current_usage as Record<string, unknown>)
|
|
360
|
+
: isRecord(contextWindow.currentUsage)
|
|
361
|
+
? (contextWindow.currentUsage as Record<string, unknown>)
|
|
362
|
+
: null;
|
|
363
|
+
if (!currentUsage) return null;
|
|
364
|
+
const inputTokens =
|
|
365
|
+
firstNumber(
|
|
366
|
+
currentUsage.input_tokens,
|
|
367
|
+
currentUsage.inputTokens
|
|
368
|
+
) ?? null;
|
|
369
|
+
const outputTokens =
|
|
370
|
+
firstNumber(
|
|
371
|
+
currentUsage.output_tokens,
|
|
372
|
+
currentUsage.outputTokens
|
|
373
|
+
) ?? null;
|
|
374
|
+
const cacheRead =
|
|
375
|
+
firstNumber(
|
|
376
|
+
currentUsage.cache_read_input_tokens,
|
|
377
|
+
currentUsage.cacheReadInputTokens
|
|
378
|
+
) ?? null;
|
|
379
|
+
const cacheWrite =
|
|
380
|
+
firstNumber(
|
|
381
|
+
currentUsage.cache_creation_input_tokens,
|
|
382
|
+
currentUsage.cacheCreationInputTokens
|
|
383
|
+
) ?? null;
|
|
384
|
+
if (
|
|
385
|
+
inputTokens === null &&
|
|
386
|
+
outputTokens === null &&
|
|
387
|
+
cacheRead === null &&
|
|
388
|
+
cacheWrite === null
|
|
389
|
+
) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const totalTokens =
|
|
393
|
+
(inputTokens || 0) +
|
|
394
|
+
(outputTokens || 0) +
|
|
395
|
+
(cacheRead || 0) +
|
|
396
|
+
(cacheWrite || 0);
|
|
397
|
+
return {
|
|
398
|
+
todayTokens: totalTokens,
|
|
399
|
+
totalTokens: null,
|
|
400
|
+
inputTokens,
|
|
401
|
+
outputTokens,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function getSessionId(input: StatuslineInput | null): string | null {
|
|
406
|
+
if (!input) return null;
|
|
407
|
+
return firstNonEmpty(input.session_id, input.sessionId);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function parseUsageTotalsRecord(
|
|
411
|
+
record: Record<string, unknown>
|
|
412
|
+
): StatuslineUsageTotals | null {
|
|
413
|
+
const inputTokens =
|
|
414
|
+
firstNumber(
|
|
415
|
+
record.inputTokens,
|
|
416
|
+
record.input,
|
|
417
|
+
record.input_tokens
|
|
418
|
+
) ?? null;
|
|
419
|
+
const outputTokens =
|
|
420
|
+
firstNumber(
|
|
421
|
+
record.outputTokens,
|
|
422
|
+
record.output,
|
|
423
|
+
record.output_tokens
|
|
424
|
+
) ?? null;
|
|
425
|
+
const totalTokens =
|
|
426
|
+
firstNumber(
|
|
427
|
+
record.totalTokens,
|
|
428
|
+
record.total,
|
|
429
|
+
record.total_tokens
|
|
430
|
+
) ?? null;
|
|
431
|
+
const cacheRead =
|
|
432
|
+
firstNumber(
|
|
433
|
+
record.cache_read_input_tokens,
|
|
434
|
+
record.cacheReadInputTokens,
|
|
435
|
+
record.cache_read,
|
|
436
|
+
record.cacheRead
|
|
437
|
+
) ?? null;
|
|
438
|
+
const cacheWrite =
|
|
439
|
+
firstNumber(
|
|
440
|
+
record.cache_creation_input_tokens,
|
|
441
|
+
record.cacheCreationInputTokens,
|
|
442
|
+
record.cache_write_input_tokens,
|
|
443
|
+
record.cacheWriteInputTokens,
|
|
444
|
+
record.cache_write,
|
|
445
|
+
record.cacheWrite
|
|
446
|
+
) ?? null;
|
|
447
|
+
let computedTotal: number | null = null;
|
|
448
|
+
if (
|
|
449
|
+
inputTokens !== null ||
|
|
450
|
+
outputTokens !== null ||
|
|
451
|
+
cacheRead !== null ||
|
|
452
|
+
cacheWrite !== null
|
|
453
|
+
) {
|
|
454
|
+
computedTotal =
|
|
455
|
+
(inputTokens || 0) +
|
|
456
|
+
(outputTokens || 0) +
|
|
457
|
+
(cacheRead || 0) +
|
|
458
|
+
(cacheWrite || 0);
|
|
459
|
+
}
|
|
460
|
+
const resolvedTotal = totalTokens ?? computedTotal;
|
|
461
|
+
if (
|
|
462
|
+
inputTokens === null &&
|
|
463
|
+
outputTokens === null &&
|
|
464
|
+
resolvedTotal === null
|
|
465
|
+
) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
inputTokens,
|
|
470
|
+
outputTokens,
|
|
471
|
+
totalTokens: resolvedTotal,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function getUsageTotalsFromInput(
|
|
476
|
+
input: StatuslineInput | null
|
|
477
|
+
): StatuslineUsageTotals | null {
|
|
478
|
+
if (!input) return null;
|
|
479
|
+
const contextWindow = isRecord(input.context_window)
|
|
480
|
+
? (input.context_window as Record<string, unknown>)
|
|
481
|
+
: isRecord(input.contextWindow)
|
|
482
|
+
? (input.contextWindow as Record<string, unknown>)
|
|
483
|
+
: null;
|
|
484
|
+
if (contextWindow) {
|
|
485
|
+
const totalInputTokens =
|
|
486
|
+
firstNumber(
|
|
487
|
+
contextWindow.total_input_tokens,
|
|
488
|
+
contextWindow.totalInputTokens
|
|
489
|
+
) ?? null;
|
|
490
|
+
const totalOutputTokens =
|
|
491
|
+
firstNumber(
|
|
492
|
+
contextWindow.total_output_tokens,
|
|
493
|
+
contextWindow.totalOutputTokens
|
|
494
|
+
) ?? null;
|
|
495
|
+
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
496
|
+
return {
|
|
497
|
+
inputTokens: totalInputTokens,
|
|
498
|
+
outputTokens: totalOutputTokens,
|
|
499
|
+
totalTokens: (totalInputTokens || 0) + (totalOutputTokens || 0),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (typeof input.token_usage === "number") {
|
|
504
|
+
return {
|
|
505
|
+
inputTokens: null,
|
|
506
|
+
outputTokens: null,
|
|
507
|
+
totalTokens: coerceNumber(input.token_usage),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (isRecord(input.token_usage)) {
|
|
511
|
+
return parseUsageTotalsRecord(input.token_usage as Record<string, unknown>);
|
|
512
|
+
}
|
|
513
|
+
if (isRecord(input.usage)) {
|
|
514
|
+
return parseUsageTotalsRecord(input.usage as Record<string, unknown>);
|
|
515
|
+
}
|
|
261
516
|
return null;
|
|
262
517
|
}
|
|
263
518
|
|
|
@@ -266,6 +521,24 @@ function getContextUsedTokens(input: StatuslineInput | null): number | null {
|
|
|
266
521
|
return coerceNumber(input.context_window_used_tokens);
|
|
267
522
|
}
|
|
268
523
|
|
|
524
|
+
function normalizeInputUsage(
|
|
525
|
+
inputUsage: StatuslineInputUsage | null
|
|
526
|
+
): StatuslineUsage | null {
|
|
527
|
+
if (!inputUsage) return null;
|
|
528
|
+
const usage: StatuslineUsage = {
|
|
529
|
+
todayTokens: coerceNumber(inputUsage.todayTokens),
|
|
530
|
+
totalTokens: coerceNumber(inputUsage.totalTokens),
|
|
531
|
+
inputTokens: coerceNumber(inputUsage.inputTokens),
|
|
532
|
+
outputTokens: coerceNumber(inputUsage.outputTokens),
|
|
533
|
+
};
|
|
534
|
+
const hasUsage =
|
|
535
|
+
usage.todayTokens !== null ||
|
|
536
|
+
usage.totalTokens !== null ||
|
|
537
|
+
usage.inputTokens !== null ||
|
|
538
|
+
usage.outputTokens !== null;
|
|
539
|
+
return hasUsage ? usage : null;
|
|
540
|
+
}
|
|
541
|
+
|
|
269
542
|
function getContextLeftPercent(
|
|
270
543
|
input: StatuslineInput | null,
|
|
271
544
|
type: string | null
|
|
@@ -496,7 +769,7 @@ export function buildStatuslineResult(
|
|
|
496
769
|
let type = normalizeTypeValue(typeCandidate);
|
|
497
770
|
const envProfile = resolveEnvProfile(type);
|
|
498
771
|
|
|
499
|
-
|
|
772
|
+
const profileKey = firstNonEmpty(
|
|
500
773
|
args.profileKey,
|
|
501
774
|
envProfile.key,
|
|
502
775
|
inputProfile ? inputProfile.key : null
|
|
@@ -530,6 +803,22 @@ export function buildStatuslineResult(
|
|
|
530
803
|
process.cwd()
|
|
531
804
|
)!;
|
|
532
805
|
|
|
806
|
+
const sessionId = getSessionId(stdinInput);
|
|
807
|
+
const stdinUsageTotals = getUsageTotalsFromInput(stdinInput);
|
|
808
|
+
if (args.syncUsage && sessionId && stdinUsageTotals) {
|
|
809
|
+
const usageType = normalizeType(type || "");
|
|
810
|
+
syncUsageFromStatuslineInput(
|
|
811
|
+
config,
|
|
812
|
+
configPath,
|
|
813
|
+
usageType,
|
|
814
|
+
profileKey,
|
|
815
|
+
profileName,
|
|
816
|
+
sessionId,
|
|
817
|
+
stdinUsageTotals,
|
|
818
|
+
cwd
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
533
822
|
const model = firstNonEmpty(
|
|
534
823
|
args.model,
|
|
535
824
|
process.env.CODE_ENV_MODEL,
|
|
@@ -565,7 +854,12 @@ export function buildStatuslineResult(
|
|
|
565
854
|
usage.inputTokens !== null ||
|
|
566
855
|
usage.outputTokens !== null;
|
|
567
856
|
|
|
857
|
+
const stdinUsage = normalizeInputUsage(getInputUsage(stdinInput));
|
|
858
|
+
|
|
568
859
|
let finalUsage: StatuslineUsage | null = hasExplicitUsage ? usage : null;
|
|
860
|
+
if (!finalUsage) {
|
|
861
|
+
finalUsage = stdinUsage;
|
|
862
|
+
}
|
|
569
863
|
if (!finalUsage) {
|
|
570
864
|
finalUsage = resolveUsageFromRecords(
|
|
571
865
|
config,
|
package/src/usage/index.ts
CHANGED
|
@@ -40,9 +40,20 @@ interface UsageStateEntry {
|
|
|
40
40
|
cwd: string | null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
interface UsageSessionEntry {
|
|
44
|
+
type: ProfileType;
|
|
45
|
+
inputTokens: number;
|
|
46
|
+
outputTokens: number;
|
|
47
|
+
totalTokens: number;
|
|
48
|
+
startTs: string | null;
|
|
49
|
+
endTs: string | null;
|
|
50
|
+
cwd: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
interface UsageStateFile {
|
|
44
54
|
version: number;
|
|
45
55
|
files: Record<string, UsageStateEntry>;
|
|
56
|
+
sessions?: Record<string, UsageSessionEntry>;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
interface ProfileLogEntry {
|
|
@@ -78,6 +89,12 @@ interface SessionStats {
|
|
|
78
89
|
sessionId: string | null;
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
interface UsageTotalsInput {
|
|
93
|
+
inputTokens: number | null;
|
|
94
|
+
outputTokens: number | null;
|
|
95
|
+
totalTokens: number | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
function resolveDefaultConfigDir(configPath: string | null): string {
|
|
82
99
|
if (configPath) return path.dirname(configPath);
|
|
83
100
|
return path.join(os.homedir(), ".config", "code-env");
|
|
@@ -172,6 +189,18 @@ function normalizeUsageType(type: string | null | undefined): string | null {
|
|
|
172
189
|
return trimmed ? trimmed : null;
|
|
173
190
|
}
|
|
174
191
|
|
|
192
|
+
function buildSessionKey(type: ProfileType | null, sessionId: string): string {
|
|
193
|
+
const normalized = normalizeUsageType(type || "");
|
|
194
|
+
return normalized ? `${normalized}::${sessionId}` : sessionId;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toFiniteNumber(value: number | null | undefined): number | null {
|
|
198
|
+
if (value === null || value === undefined) return null;
|
|
199
|
+
const num = Number(value);
|
|
200
|
+
if (!Number.isFinite(num)) return null;
|
|
201
|
+
return num;
|
|
202
|
+
}
|
|
203
|
+
|
|
175
204
|
function buildUsageLookupKey(
|
|
176
205
|
type: string | null | undefined,
|
|
177
206
|
profileId: string | null | undefined
|
|
@@ -212,6 +241,81 @@ export function resolveUsageTotalsForProfile(
|
|
|
212
241
|
);
|
|
213
242
|
}
|
|
214
243
|
|
|
244
|
+
export function syncUsageFromStatuslineInput(
|
|
245
|
+
config: Config,
|
|
246
|
+
configPath: string | null,
|
|
247
|
+
type: ProfileType | null,
|
|
248
|
+
profileKey: string | null,
|
|
249
|
+
profileName: string | null,
|
|
250
|
+
sessionId: string | null,
|
|
251
|
+
totals: UsageTotalsInput | null,
|
|
252
|
+
cwd: string | null
|
|
253
|
+
): void {
|
|
254
|
+
if (!sessionId) return;
|
|
255
|
+
if (!totals) return;
|
|
256
|
+
if (!profileKey && !profileName) return;
|
|
257
|
+
const normalizedType = normalizeType(type || "");
|
|
258
|
+
if (!normalizedType) return;
|
|
259
|
+
const usagePath = getUsagePath(config, configPath);
|
|
260
|
+
if (!usagePath) return;
|
|
261
|
+
const inputTokens = toFiniteNumber(totals.inputTokens) ?? 0;
|
|
262
|
+
const outputTokens = toFiniteNumber(totals.outputTokens) ?? 0;
|
|
263
|
+
const totalTokens =
|
|
264
|
+
toFiniteNumber(totals.totalTokens) ?? inputTokens + outputTokens;
|
|
265
|
+
if (!Number.isFinite(totalTokens)) return;
|
|
266
|
+
|
|
267
|
+
const statePath = getUsageStatePath(usagePath, config);
|
|
268
|
+
const lockPath = `${statePath}.lock`;
|
|
269
|
+
const lockFd = acquireLock(lockPath);
|
|
270
|
+
if (lockFd === null) return;
|
|
271
|
+
try {
|
|
272
|
+
const state = readUsageState(statePath);
|
|
273
|
+
const sessions = state.sessions || {};
|
|
274
|
+
const key = buildSessionKey(normalizedType, sessionId);
|
|
275
|
+
const prev = sessions[key];
|
|
276
|
+
const prevInput = prev ? prev.inputTokens : 0;
|
|
277
|
+
const prevOutput = prev ? prev.outputTokens : 0;
|
|
278
|
+
const prevTotal = prev ? prev.totalTokens : 0;
|
|
279
|
+
|
|
280
|
+
let deltaInput = inputTokens - prevInput;
|
|
281
|
+
let deltaOutput = outputTokens - prevOutput;
|
|
282
|
+
let deltaTotal = totalTokens - prevTotal;
|
|
283
|
+
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
284
|
+
deltaInput = inputTokens;
|
|
285
|
+
deltaOutput = outputTokens;
|
|
286
|
+
deltaTotal = totalTokens;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (deltaTotal > 0) {
|
|
290
|
+
const record: UsageRecord = {
|
|
291
|
+
ts: new Date().toISOString(),
|
|
292
|
+
type: normalizedType,
|
|
293
|
+
profileKey: profileKey || null,
|
|
294
|
+
profileName: profileName || null,
|
|
295
|
+
inputTokens: deltaInput,
|
|
296
|
+
outputTokens: deltaOutput,
|
|
297
|
+
totalTokens: deltaTotal,
|
|
298
|
+
};
|
|
299
|
+
appendUsageRecord(usagePath, record);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const now = new Date().toISOString();
|
|
303
|
+
sessions[key] = {
|
|
304
|
+
type: normalizedType,
|
|
305
|
+
inputTokens,
|
|
306
|
+
outputTokens,
|
|
307
|
+
totalTokens,
|
|
308
|
+
startTs: prev ? prev.startTs : now,
|
|
309
|
+
endTs: now,
|
|
310
|
+
cwd: cwd || (prev ? prev.cwd : null),
|
|
311
|
+
};
|
|
312
|
+
state.sessions = sessions;
|
|
313
|
+
writeUsageState(statePath, state);
|
|
314
|
+
} finally {
|
|
315
|
+
releaseLock(lockPath, lockFd);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
215
319
|
export function logProfileUse(
|
|
216
320
|
config: Config,
|
|
217
321
|
configPath: string | null,
|
|
@@ -424,19 +528,21 @@ function resolveProfileForSession(
|
|
|
424
528
|
|
|
425
529
|
function readUsageState(statePath: string): UsageStateFile {
|
|
426
530
|
if (!statePath || !fs.existsSync(statePath)) {
|
|
427
|
-
return { version: 1, files: {} };
|
|
531
|
+
return { version: 1, files: {}, sessions: {} };
|
|
428
532
|
}
|
|
429
533
|
try {
|
|
430
534
|
const raw = fs.readFileSync(statePath, "utf8");
|
|
431
535
|
const parsed = JSON.parse(raw);
|
|
432
536
|
if (!parsed || typeof parsed !== "object") {
|
|
433
|
-
return { version: 1, files: {} };
|
|
537
|
+
return { version: 1, files: {}, sessions: {} };
|
|
434
538
|
}
|
|
435
539
|
const files =
|
|
436
540
|
parsed.files && typeof parsed.files === "object" ? parsed.files : {};
|
|
437
|
-
|
|
541
|
+
const sessions =
|
|
542
|
+
parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
|
|
543
|
+
return { version: 1, files, sessions };
|
|
438
544
|
} catch {
|
|
439
|
-
return { version: 1, files: {} };
|
|
545
|
+
return { version: 1, files: {}, sessions: {} };
|
|
440
546
|
}
|
|
441
547
|
}
|
|
442
548
|
|
|
@@ -754,6 +860,7 @@ export function syncUsageFromSessions(
|
|
|
754
860
|
|
|
755
861
|
const state = readUsageState(statePath);
|
|
756
862
|
const files = state.files || {};
|
|
863
|
+
const sessions = state.sessions || {};
|
|
757
864
|
const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
|
|
758
865
|
const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
|
|
759
866
|
|
|
@@ -786,16 +893,34 @@ export function syncUsageFromSessions(
|
|
|
786
893
|
stats.sessionId
|
|
787
894
|
);
|
|
788
895
|
if (!resolved.match) return;
|
|
896
|
+
const sessionKey =
|
|
897
|
+
stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
|
|
898
|
+
const sessionPrev = sessionKey ? sessions[sessionKey] : null;
|
|
789
899
|
const prevInput = prev ? prev.inputTokens : 0;
|
|
790
900
|
const prevOutput = prev ? prev.outputTokens : 0;
|
|
791
901
|
const prevTotal = prev ? prev.totalTokens : 0;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
902
|
+
const prevInputMax = sessionPrev
|
|
903
|
+
? Math.max(prevInput, sessionPrev.inputTokens)
|
|
904
|
+
: prevInput;
|
|
905
|
+
const prevOutputMax = sessionPrev
|
|
906
|
+
? Math.max(prevOutput, sessionPrev.outputTokens)
|
|
907
|
+
: prevOutput;
|
|
908
|
+
const prevTotalMax = sessionPrev
|
|
909
|
+
? Math.max(prevTotal, sessionPrev.totalTokens)
|
|
910
|
+
: prevTotal;
|
|
911
|
+
let deltaInput = stats.inputTokens - prevInputMax;
|
|
912
|
+
let deltaOutput = stats.outputTokens - prevOutputMax;
|
|
913
|
+
let deltaTotal = stats.totalTokens - prevTotalMax;
|
|
795
914
|
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
915
|
+
if (sessionPrev) {
|
|
916
|
+
deltaInput = 0;
|
|
917
|
+
deltaOutput = 0;
|
|
918
|
+
deltaTotal = 0;
|
|
919
|
+
} else {
|
|
920
|
+
deltaInput = stats.inputTokens;
|
|
921
|
+
deltaOutput = stats.outputTokens;
|
|
922
|
+
deltaTotal = stats.totalTokens;
|
|
923
|
+
}
|
|
799
924
|
}
|
|
800
925
|
if (deltaTotal > 0) {
|
|
801
926
|
const record: UsageRecord = {
|
|
@@ -809,6 +934,26 @@ export function syncUsageFromSessions(
|
|
|
809
934
|
};
|
|
810
935
|
appendUsageRecord(usagePath, record);
|
|
811
936
|
}
|
|
937
|
+
if (sessionKey) {
|
|
938
|
+
const nextInput = sessionPrev
|
|
939
|
+
? Math.max(sessionPrev.inputTokens, stats.inputTokens)
|
|
940
|
+
: stats.inputTokens;
|
|
941
|
+
const nextOutput = sessionPrev
|
|
942
|
+
? Math.max(sessionPrev.outputTokens, stats.outputTokens)
|
|
943
|
+
: stats.outputTokens;
|
|
944
|
+
const nextTotal = sessionPrev
|
|
945
|
+
? Math.max(sessionPrev.totalTokens, stats.totalTokens)
|
|
946
|
+
: stats.totalTokens;
|
|
947
|
+
sessions[sessionKey] = {
|
|
948
|
+
type,
|
|
949
|
+
inputTokens: nextInput,
|
|
950
|
+
outputTokens: nextOutput,
|
|
951
|
+
totalTokens: nextTotal,
|
|
952
|
+
startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
|
|
953
|
+
endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
|
|
954
|
+
cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
812
957
|
files[filePath] = {
|
|
813
958
|
mtimeMs: stat.mtimeMs,
|
|
814
959
|
size: stat.size,
|
|
@@ -826,6 +971,7 @@ export function syncUsageFromSessions(
|
|
|
826
971
|
for (const filePath of claudeFiles) processFile(filePath, "claude");
|
|
827
972
|
|
|
828
973
|
state.files = files;
|
|
974
|
+
state.sessions = sessions;
|
|
829
975
|
writeUsageState(statePath, state);
|
|
830
976
|
} finally {
|
|
831
977
|
releaseLock(lockPath, lockFd);
|