@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.
@@ -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 === null || tokenUsage === undefined)
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
- if (typeof tokenUsage === "number") {
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: coerceNumber(tokenUsage),
155
- inputTokens: null,
156
- outputTokens: null,
204
+ totalTokens: null,
205
+ inputTokens: totalInputTokens,
206
+ outputTokens: totalOutputTokens,
157
207
  };
158
208
  }
159
- if (isRecord(tokenUsage)) {
160
- const record = tokenUsage;
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
- todayTokens: (_a = firstNumber(record.todayTokens, record.today, record.today_tokens, record.daily, record.daily_tokens)) !== null && _a !== void 0 ? _a : null,
163
- totalTokens: (_b = firstNumber(record.totalTokens, record.total, record.total_tokens)) !== null && _b !== void 0 ? _b : null,
164
- inputTokens: (_c = firstNumber(record.inputTokens, record.input, record.input_tokens)) !== null && _c !== void 0 ? _c : null,
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
- let profileKey = firstNonEmpty(args.profileKey, envProfile.key, inputProfile ? inputProfile.key : null);
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
  }
@@ -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
- return { version: 1, files };
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
- let deltaInput = stats.inputTokens - prevInput;
668
- let deltaOutput = stats.outputTokens - prevOutput;
669
- let deltaTotal = stats.totalTokens - prevTotal;
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
- deltaInput = stats.inputTokens;
672
- deltaOutput = stats.outputTokens;
673
- deltaTotal = stats.totalTokens;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praeviso/code-env-switch",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Switch between Claude Code and Codex environment variables from a single CLI",
5
5
  "bin": {
6
6
  "codenv": "bin/index.js"
@@ -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 === null || tokenUsage === undefined) return null;
222
- if (typeof tokenUsage === "number") {
223
- return {
224
- todayTokens: null,
225
- totalTokens: coerceNumber(tokenUsage),
226
- inputTokens: null,
227
- outputTokens: null,
228
- };
229
- }
230
- if (isRecord(tokenUsage)) {
231
- const record = tokenUsage as Record<string, unknown>;
232
- return {
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
- let profileKey = firstNonEmpty(
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,
@@ -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
- return { version: 1, files };
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
- let deltaInput = stats.inputTokens - prevInput;
793
- let deltaOutput = stats.outputTokens - prevOutput;
794
- let deltaTotal = stats.totalTokens - prevTotal;
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
- deltaInput = stats.inputTokens;
797
- deltaOutput = stats.outputTokens;
798
- deltaTotal = stats.totalTokens;
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);