@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/bin/usage/index.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
|
|
|
16
16
|
exports.logProfileUse = logProfileUse;
|
|
17
17
|
exports.logSessionBinding = logSessionBinding;
|
|
18
18
|
exports.readSessionBindingIndex = readSessionBindingIndex;
|
|
19
|
+
exports.resolveProfileFromLog = resolveProfileFromLog;
|
|
19
20
|
exports.clearUsageHistory = clearUsageHistory;
|
|
20
21
|
exports.readUsageRecords = readUsageRecords;
|
|
21
22
|
exports.syncUsageFromSessions = syncUsageFromSessions;
|
|
@@ -375,16 +376,15 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
375
376
|
cacheWriteTokens;
|
|
376
377
|
if (!Number.isFinite(totalTokens))
|
|
377
378
|
return;
|
|
378
|
-
const
|
|
379
|
-
const
|
|
379
|
+
const sessionKey = buildSessionKey(normalizedType, sessionId);
|
|
380
|
+
const sessionStatePath = getUsageSessionStatePath(usagePath, sessionKey);
|
|
381
|
+
const legacySessionStatePath = getLegacyUsageSessionStatePath(usagePath, sessionKey);
|
|
382
|
+
const lockPath = `${sessionStatePath}.lock`;
|
|
380
383
|
const lockFd = acquireLock(lockPath);
|
|
381
384
|
if (lockFd === null)
|
|
382
385
|
return;
|
|
383
386
|
try {
|
|
384
|
-
const
|
|
385
|
-
const sessions = state.sessions || {};
|
|
386
|
-
const key = buildSessionKey(normalizedType, sessionId);
|
|
387
|
-
const prev = sessions[key];
|
|
387
|
+
const prev = readUsageSessionStateWithFallback(sessionStatePath, legacySessionStatePath);
|
|
388
388
|
const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
|
|
389
389
|
const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
|
|
390
390
|
const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
|
|
@@ -396,12 +396,11 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
396
396
|
let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
|
|
397
397
|
let deltaTotal = totalTokens - prevTotal;
|
|
398
398
|
if (deltaTotal < 0) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
deltaTotal = totalTokens;
|
|
399
|
+
deltaInput = 0;
|
|
400
|
+
deltaOutput = 0;
|
|
401
|
+
deltaCacheRead = 0;
|
|
402
|
+
deltaCacheWrite = 0;
|
|
403
|
+
deltaTotal = 0;
|
|
405
404
|
}
|
|
406
405
|
else {
|
|
407
406
|
// Clamp negatives caused by reclassification (e.g. cache splits).
|
|
@@ -438,13 +437,18 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
438
437
|
appendUsageRecord(usagePath, record);
|
|
439
438
|
}
|
|
440
439
|
const now = new Date().toISOString();
|
|
441
|
-
|
|
440
|
+
const nextInput = Math.max(prevInput, inputTokens);
|
|
441
|
+
const nextOutput = Math.max(prevOutput, outputTokens);
|
|
442
|
+
const nextCacheRead = Math.max(prevCacheRead, cacheReadTokens);
|
|
443
|
+
const nextCacheWrite = Math.max(prevCacheWrite, cacheWriteTokens);
|
|
444
|
+
const nextTotal = Math.max(prevTotal, totalTokens);
|
|
445
|
+
const nextSession = {
|
|
442
446
|
type: normalizedType,
|
|
443
|
-
inputTokens,
|
|
444
|
-
outputTokens,
|
|
445
|
-
cacheReadTokens,
|
|
446
|
-
cacheWriteTokens,
|
|
447
|
-
totalTokens,
|
|
447
|
+
inputTokens: nextInput,
|
|
448
|
+
outputTokens: nextOutput,
|
|
449
|
+
cacheReadTokens: nextCacheRead,
|
|
450
|
+
cacheWriteTokens: nextCacheWrite,
|
|
451
|
+
totalTokens: nextTotal,
|
|
448
452
|
startTs: prev ? prev.startTs : now,
|
|
449
453
|
endTs: now,
|
|
450
454
|
cwd: cwd || (prev ? prev.cwd : null),
|
|
@@ -452,9 +456,7 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
452
456
|
profileKey: profileKey || null,
|
|
453
457
|
profileName: profileName || null,
|
|
454
458
|
};
|
|
455
|
-
|
|
456
|
-
updateUsageStateMetadata(state, usagePath);
|
|
457
|
-
writeUsageState(statePath, state);
|
|
459
|
+
writeUsageSessionState(sessionStatePath, nextSession);
|
|
458
460
|
}
|
|
459
461
|
finally {
|
|
460
462
|
releaseLock(lockPath, lockFd);
|
|
@@ -596,6 +598,42 @@ function resolveProfileForSession(config, logEntries, type, sessionFile, session
|
|
|
596
598
|
}
|
|
597
599
|
return { match: null, ambiguous: false };
|
|
598
600
|
}
|
|
601
|
+
function resolveProfileFromLog(config, configPath, type, terminalTag) {
|
|
602
|
+
const normalizedType = (0, type_1.normalizeType)(type || "");
|
|
603
|
+
if (!normalizedType)
|
|
604
|
+
return null;
|
|
605
|
+
const profileLogPath = getProfileLogPath(config, configPath);
|
|
606
|
+
const entries = readProfileLogEntries([profileLogPath]);
|
|
607
|
+
if (entries.length === 0)
|
|
608
|
+
return null;
|
|
609
|
+
if (!terminalTag)
|
|
610
|
+
return null;
|
|
611
|
+
let best = null;
|
|
612
|
+
let bestTime = Number.NEGATIVE_INFINITY;
|
|
613
|
+
for (const entry of entries) {
|
|
614
|
+
if (entry.kind !== "use")
|
|
615
|
+
continue;
|
|
616
|
+
if (entry.profileType !== normalizedType)
|
|
617
|
+
continue;
|
|
618
|
+
if (entry.terminalTag !== terminalTag)
|
|
619
|
+
continue;
|
|
620
|
+
if (!entry.profileKey && !entry.profileName)
|
|
621
|
+
continue;
|
|
622
|
+
const ts = new Date(entry.timestamp).getTime();
|
|
623
|
+
if (!Number.isFinite(ts))
|
|
624
|
+
continue;
|
|
625
|
+
if (ts >= bestTime) {
|
|
626
|
+
bestTime = ts;
|
|
627
|
+
best = entry;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (!best)
|
|
631
|
+
return null;
|
|
632
|
+
const match = normalizeProfileMatch(config, best, normalizedType);
|
|
633
|
+
if (!match.profileKey && !match.profileName)
|
|
634
|
+
return null;
|
|
635
|
+
return match;
|
|
636
|
+
}
|
|
599
637
|
function readUsageState(statePath) {
|
|
600
638
|
if (!statePath || !fs.existsSync(statePath)) {
|
|
601
639
|
return { version: 1, files: {}, sessions: {} };
|
|
@@ -644,6 +682,54 @@ function writeUsageState(statePath, state) {
|
|
|
644
682
|
fs.writeFileSync(statePath, payload, "utf8");
|
|
645
683
|
}
|
|
646
684
|
}
|
|
685
|
+
function getUsageSessionStateDir(usagePath) {
|
|
686
|
+
const dir = path.dirname(usagePath);
|
|
687
|
+
const base = path.basename(usagePath, path.extname(usagePath));
|
|
688
|
+
return path.join(dir, `${base}-sessions`);
|
|
689
|
+
}
|
|
690
|
+
function getLegacyUsageSessionStateDir(usagePath) {
|
|
691
|
+
return `${usagePath}.sessions`;
|
|
692
|
+
}
|
|
693
|
+
function toSafeSessionKey(value) {
|
|
694
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
695
|
+
}
|
|
696
|
+
function getUsageSessionStatePath(usagePath, sessionKey) {
|
|
697
|
+
const dir = getUsageSessionStateDir(usagePath);
|
|
698
|
+
return path.join(dir, `${toSafeSessionKey(sessionKey)}.json`);
|
|
699
|
+
}
|
|
700
|
+
function getLegacyUsageSessionStatePath(usagePath, sessionKey) {
|
|
701
|
+
const dir = getLegacyUsageSessionStateDir(usagePath);
|
|
702
|
+
return path.join(dir, `${toSafeSessionKey(sessionKey)}.json`);
|
|
703
|
+
}
|
|
704
|
+
function readUsageSessionState(filePath) {
|
|
705
|
+
var _a;
|
|
706
|
+
if (!filePath || !fs.existsSync(filePath))
|
|
707
|
+
return null;
|
|
708
|
+
try {
|
|
709
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
710
|
+
const parsed = JSON.parse(raw);
|
|
711
|
+
if (!parsed || typeof parsed !== "object")
|
|
712
|
+
return null;
|
|
713
|
+
const session = (_a = parsed.session) !== null && _a !== void 0 ? _a : parsed;
|
|
714
|
+
if (!session || typeof session !== "object")
|
|
715
|
+
return null;
|
|
716
|
+
return session;
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function readUsageSessionStateWithFallback(primaryPath, legacyPath) {
|
|
723
|
+
return readUsageSessionState(primaryPath) || readUsageSessionState(legacyPath);
|
|
724
|
+
}
|
|
725
|
+
function writeUsageSessionState(filePath, session) {
|
|
726
|
+
const dir = path.dirname(filePath);
|
|
727
|
+
if (!fs.existsSync(dir)) {
|
|
728
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
729
|
+
}
|
|
730
|
+
const payload = { version: 1, session };
|
|
731
|
+
fs.writeFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
732
|
+
}
|
|
647
733
|
function addSiblingBackupPaths(targets, filePath) {
|
|
648
734
|
if (!filePath)
|
|
649
735
|
return;
|
|
@@ -672,6 +758,22 @@ function clearUsageHistory(config, configPath) {
|
|
|
672
758
|
if (usagePath) {
|
|
673
759
|
targets.add(usagePath);
|
|
674
760
|
addSiblingBackupPaths(targets, usagePath);
|
|
761
|
+
for (const sessionDir of [
|
|
762
|
+
getUsageSessionStateDir(usagePath),
|
|
763
|
+
getLegacyUsageSessionStateDir(usagePath),
|
|
764
|
+
]) {
|
|
765
|
+
try {
|
|
766
|
+
const entries = fs.readdirSync(sessionDir, { withFileTypes: true });
|
|
767
|
+
for (const entry of entries) {
|
|
768
|
+
if (!entry.isFile())
|
|
769
|
+
continue;
|
|
770
|
+
targets.add(path.join(sessionDir, entry.name));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
// ignore session state cleanup failures
|
|
775
|
+
}
|
|
776
|
+
}
|
|
675
777
|
}
|
|
676
778
|
const statePath = usagePath ? getUsageStatePath(usagePath, config) : null;
|
|
677
779
|
if (statePath) {
|
package/code-env.example.json
CHANGED
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
"claude": "default"
|
|
6
6
|
},
|
|
7
7
|
"codexStatusline": {
|
|
8
|
-
"
|
|
9
|
-
"showHints": false,
|
|
10
|
-
"updateIntervalMs": 300,
|
|
11
|
-
"timeoutMs": 1000
|
|
8
|
+
"items": ["model-with-reasoning", "context-remaining", "current-dir", "git-branch"]
|
|
12
9
|
},
|
|
13
10
|
"claudeStatusline": {
|
|
14
11
|
"command": "codenv statusline --type claude --sync-usage",
|
package/docs/usage.md
CHANGED
|
@@ -56,6 +56,9 @@ Each line is a JSON object (fields may be null/omitted depending on the source):
|
|
|
56
56
|
To see real payloads, set `CODE_ENV_STATUSLINE_DEBUG=1` and read the JSONL entries in
|
|
57
57
|
`statusline-debug.jsonl` (or the path from `CODE_ENV_STATUSLINE_DEBUG_PATH`).
|
|
58
58
|
|
|
59
|
+
Note: with official Codex status line configuration (`tui.status_line`), Codex does not invoke
|
|
60
|
+
an external status line command by default.
|
|
61
|
+
|
|
59
62
|
### Codex (token_usage totals)
|
|
60
63
|
|
|
61
64
|
```json
|
package/docs/usage_zh.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praeviso/code-env-switch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Switch between Claude Code and Codex environment variables from a single CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codenv": "bin/index.js"
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^20.11.0",
|
|
20
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
21
|
-
"@typescript-eslint/parser": "^
|
|
22
|
-
"eslint": "^
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
21
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
22
|
+
"eslint": "^10.0.2",
|
|
23
23
|
"typescript": "^5.9.3"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -6,10 +6,12 @@ import { buildListRows } from "../profile/display";
|
|
|
6
6
|
import { getResolvedDefaultProfileKeys } from "../config/defaults";
|
|
7
7
|
import {
|
|
8
8
|
formatTokenCount,
|
|
9
|
+
getUsagePath,
|
|
9
10
|
readUsageCostIndex,
|
|
10
11
|
readUsageTotalsIndex,
|
|
11
12
|
resolveUsageCostForProfile,
|
|
12
13
|
resolveUsageTotalsForProfile,
|
|
14
|
+
syncUsageFromSessions,
|
|
13
15
|
} from "../usage";
|
|
14
16
|
import { formatUsdAmount } from "../usage/pricing";
|
|
15
17
|
|
|
@@ -20,7 +22,15 @@ export function printList(config: Config, configPath: string | null): void {
|
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
try {
|
|
23
|
-
const
|
|
25
|
+
const usagePath = getUsagePath(config, configPath);
|
|
26
|
+
if (usagePath) {
|
|
27
|
+
syncUsageFromSessions(config, configPath, usagePath);
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore usage sync errors
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const usageTotals = readUsageTotalsIndex(config, configPath, false);
|
|
24
34
|
const usageCosts = readUsageCostIndex(config, configPath, false);
|
|
25
35
|
if (usageTotals) {
|
|
26
36
|
for (const row of rows) {
|