@praeviso/code-env-switch 0.1.8 → 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.
@@ -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 statePath = getUsageStatePath(usagePath, config);
379
- const lockPath = `${statePath}.lock`;
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 state = readUsageState(statePath);
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
- // Session reset: treat current totals as fresh usage.
400
- deltaInput = inputTokens;
401
- deltaOutput = outputTokens;
402
- deltaCacheRead = cacheReadTokens;
403
- deltaCacheWrite = cacheWriteTokens;
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
- sessions[key] = {
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
- state.sessions = sessions;
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) {
@@ -5,10 +5,7 @@
5
5
  "claude": "default"
6
6
  },
7
7
  "codexStatusline": {
8
- "command": ["codenv", "statusline", "--type", "codex", "--sync-usage"],
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
@@ -56,6 +56,8 @@
56
56
  可通过设置 `CODE_ENV_STATUSLINE_DEBUG=1`,在 `statusline-debug.jsonl`
57
57
  (或 `CODE_ENV_STATUSLINE_DEBUG_PATH` 指定路径)看到实际 JSON。
58
58
 
59
+ 注意:使用官方 Codex 状态栏配置(`tui.status_line`)时,Codex 默认不会调用外部状态栏命令。
60
+
59
61
  ### Codex(token_usage totals)
60
62
 
61
63
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praeviso/code-env-switch",
3
- "version": "0.1.8",
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": "^6.21.0",
21
- "@typescript-eslint/parser": "^6.21.0",
22
- "eslint": "^8.57.0",
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
  }
@@ -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 usageTotals = readUsageTotalsIndex(config, configPath, true);
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) {