@praeviso/code-env-switch 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -9
- package/README_zh.md +5 -9
- package/bin/commands/list.js +10 -1
- package/bin/statusline/codex.js +160 -195
- package/bin/statusline/index.js +14 -5
- package/bin/usage/index.js +123 -21
- package/code-env.example.json +1 -4
- package/docs/usage.md +3 -0
- package/docs/usage_zh.md +2 -0
- package/package.json +4 -4
- package/src/commands/list.ts +11 -1
- package/src/statusline/codex.ts +198 -202
- package/src/statusline/index.ts +26 -5
- package/src/types.ts +1 -4
- package/src/usage/index.ts +135 -21
package/src/usage/index.ts
CHANGED
|
@@ -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
|
|
573
|
-
const
|
|
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
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|