@praeviso/code-env-switch 0.1.8 → 0.1.10

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.
@@ -95,6 +95,11 @@ interface UsageSessionEntry {
95
95
  profileName?: string | null;
96
96
  }
97
97
 
98
+ interface UsageSessionStateFile {
99
+ version: number;
100
+ session: UsageSessionEntry;
101
+ }
102
+
98
103
  interface UsageStateFile {
99
104
  version: number;
100
105
  files: Record<string, UsageStateEntry>;
@@ -569,15 +574,20 @@ export function syncUsageFromStatuslineInput(
569
574
  cacheWriteTokens;
570
575
  if (!Number.isFinite(totalTokens)) return;
571
576
 
572
- const statePath = getUsageStatePath(usagePath, config);
573
- const lockPath = `${statePath}.lock`;
577
+ const sessionKey = buildSessionKey(normalizedType, sessionId);
578
+ const sessionStatePath = getUsageSessionStatePath(usagePath, sessionKey);
579
+ const legacySessionStatePath = getLegacyUsageSessionStatePath(
580
+ usagePath,
581
+ sessionKey
582
+ );
583
+ const lockPath = `${sessionStatePath}.lock`;
574
584
  const lockFd = acquireLock(lockPath);
575
585
  if (lockFd === null) return;
576
586
  try {
577
- const state = readUsageState(statePath);
578
- const sessions = state.sessions || {};
579
- const key = buildSessionKey(normalizedType, sessionId);
580
- const prev = sessions[key];
587
+ const prev = readUsageSessionStateWithFallback(
588
+ sessionStatePath,
589
+ legacySessionStatePath
590
+ );
581
591
  const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
582
592
  const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
583
593
  const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
@@ -590,12 +600,11 @@ export function syncUsageFromStatuslineInput(
590
600
  let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
591
601
  let deltaTotal = totalTokens - prevTotal;
592
602
  if (deltaTotal < 0) {
593
- // Session reset: treat current totals as fresh usage.
594
- deltaInput = inputTokens;
595
- deltaOutput = outputTokens;
596
- deltaCacheRead = cacheReadTokens;
597
- deltaCacheWrite = cacheWriteTokens;
598
- deltaTotal = totalTokens;
603
+ deltaInput = 0;
604
+ deltaOutput = 0;
605
+ deltaCacheRead = 0;
606
+ deltaCacheWrite = 0;
607
+ deltaTotal = 0;
599
608
  } else {
600
609
  // Clamp negatives caused by reclassification (e.g. cache splits).
601
610
  if (deltaInput < 0) deltaInput = 0;
@@ -629,13 +638,18 @@ export function syncUsageFromStatuslineInput(
629
638
  }
630
639
 
631
640
  const now = new Date().toISOString();
632
- sessions[key] = {
641
+ const nextInput = Math.max(prevInput, inputTokens);
642
+ const nextOutput = Math.max(prevOutput, outputTokens);
643
+ const nextCacheRead = Math.max(prevCacheRead, cacheReadTokens);
644
+ const nextCacheWrite = Math.max(prevCacheWrite, cacheWriteTokens);
645
+ const nextTotal = Math.max(prevTotal, totalTokens);
646
+ const nextSession: UsageSessionEntry = {
633
647
  type: normalizedType,
634
- inputTokens,
635
- outputTokens,
636
- cacheReadTokens,
637
- cacheWriteTokens,
638
- totalTokens,
648
+ inputTokens: nextInput,
649
+ outputTokens: nextOutput,
650
+ cacheReadTokens: nextCacheRead,
651
+ cacheWriteTokens: nextCacheWrite,
652
+ totalTokens: nextTotal,
639
653
  startTs: prev ? prev.startTs : now,
640
654
  endTs: now,
641
655
  cwd: cwd || (prev ? prev.cwd : null),
@@ -643,9 +657,7 @@ export function syncUsageFromStatuslineInput(
643
657
  profileKey: profileKey || null,
644
658
  profileName: profileName || null,
645
659
  };
646
- state.sessions = sessions;
647
- updateUsageStateMetadata(state, usagePath);
648
- writeUsageState(statePath, state);
660
+ writeUsageSessionState(sessionStatePath, nextSession);
649
661
  } finally {
650
662
  releaseLock(lockPath, lockFd);
651
663
  }
@@ -861,6 +873,38 @@ function resolveProfileForSession(
861
873
  return { match: null, ambiguous: false };
862
874
  }
863
875
 
876
+ export function resolveProfileFromLog(
877
+ config: Config,
878
+ configPath: string | null,
879
+ type: ProfileType | null,
880
+ terminalTag: string | null
881
+ ): { profileKey: string | null; profileName: string | null } | null {
882
+ const normalizedType = normalizeType(type || "");
883
+ if (!normalizedType) return null;
884
+ const profileLogPath = getProfileLogPath(config, configPath);
885
+ const entries = readProfileLogEntries([profileLogPath]);
886
+ if (entries.length === 0) return null;
887
+ if (!terminalTag) return null;
888
+ let best: ProfileLogEntry | null = null;
889
+ let bestTime = Number.NEGATIVE_INFINITY;
890
+ for (const entry of entries) {
891
+ if (entry.kind !== "use") continue;
892
+ if (entry.profileType !== normalizedType) continue;
893
+ if (entry.terminalTag !== terminalTag) continue;
894
+ if (!entry.profileKey && !entry.profileName) continue;
895
+ const ts = new Date(entry.timestamp).getTime();
896
+ if (!Number.isFinite(ts)) continue;
897
+ if (ts >= bestTime) {
898
+ bestTime = ts;
899
+ best = entry;
900
+ }
901
+ }
902
+ if (!best) return null;
903
+ const match = normalizeProfileMatch(config, best, normalizedType);
904
+ if (!match.profileKey && !match.profileName) return null;
905
+ return match;
906
+ }
907
+
864
908
  function readUsageState(statePath: string): UsageStateFile {
865
909
  if (!statePath || !fs.existsSync(statePath)) {
866
910
  return { version: 1, files: {}, sessions: {} };
@@ -909,6 +953,62 @@ function writeUsageState(statePath: string, state: UsageStateFile) {
909
953
  }
910
954
  }
911
955
 
956
+ function getUsageSessionStateDir(usagePath: string): string {
957
+ const dir = path.dirname(usagePath);
958
+ const base = path.basename(usagePath, path.extname(usagePath));
959
+ return path.join(dir, `${base}-sessions`);
960
+ }
961
+
962
+ function getLegacyUsageSessionStateDir(usagePath: string): string {
963
+ return `${usagePath}.sessions`;
964
+ }
965
+
966
+ function toSafeSessionKey(value: string): string {
967
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
968
+ }
969
+
970
+ function getUsageSessionStatePath(usagePath: string, sessionKey: string): string {
971
+ const dir = getUsageSessionStateDir(usagePath);
972
+ return path.join(dir, `${toSafeSessionKey(sessionKey)}.json`);
973
+ }
974
+
975
+ function getLegacyUsageSessionStatePath(usagePath: string, sessionKey: string): string {
976
+ const dir = getLegacyUsageSessionStateDir(usagePath);
977
+ return path.join(dir, `${toSafeSessionKey(sessionKey)}.json`);
978
+ }
979
+
980
+ function readUsageSessionState(filePath: string): UsageSessionEntry | null {
981
+ if (!filePath || !fs.existsSync(filePath)) return null;
982
+ try {
983
+ const raw = fs.readFileSync(filePath, "utf8");
984
+ const parsed = JSON.parse(raw);
985
+ if (!parsed || typeof parsed !== "object") return null;
986
+ const session =
987
+ (parsed as UsageSessionStateFile).session ??
988
+ (parsed as UsageSessionEntry);
989
+ if (!session || typeof session !== "object") return null;
990
+ return session as UsageSessionEntry;
991
+ } catch {
992
+ return null;
993
+ }
994
+ }
995
+
996
+ function readUsageSessionStateWithFallback(
997
+ primaryPath: string,
998
+ legacyPath: string
999
+ ): UsageSessionEntry | null {
1000
+ return readUsageSessionState(primaryPath) || readUsageSessionState(legacyPath);
1001
+ }
1002
+
1003
+ function writeUsageSessionState(filePath: string, session: UsageSessionEntry): void {
1004
+ const dir = path.dirname(filePath);
1005
+ if (!fs.existsSync(dir)) {
1006
+ fs.mkdirSync(dir, { recursive: true });
1007
+ }
1008
+ const payload: UsageSessionStateFile = { version: 1, session };
1009
+ fs.writeFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf8");
1010
+ }
1011
+
912
1012
  function addSiblingBackupPaths(targets: Set<string>, filePath: string | null) {
913
1013
  if (!filePath) return;
914
1014
  const dir = path.dirname(filePath);
@@ -937,6 +1037,20 @@ export function clearUsageHistory(
937
1037
  if (usagePath) {
938
1038
  targets.add(usagePath);
939
1039
  addSiblingBackupPaths(targets, usagePath);
1040
+ for (const sessionDir of [
1041
+ getUsageSessionStateDir(usagePath),
1042
+ getLegacyUsageSessionStateDir(usagePath),
1043
+ ]) {
1044
+ try {
1045
+ const entries = fs.readdirSync(sessionDir, { withFileTypes: true });
1046
+ for (const entry of entries) {
1047
+ if (!entry.isFile()) continue;
1048
+ targets.add(path.join(sessionDir, entry.name));
1049
+ }
1050
+ } catch {
1051
+ // ignore session state cleanup failures
1052
+ }
1053
+ }
940
1054
  }
941
1055
  const statePath = usagePath ? getUsageStatePath(usagePath, config) : null;
942
1056
  if (statePath) {