@praeviso/code-env-switch 0.1.2 → 0.1.4

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.
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readStdinJson = readStdinJson;
4
+ exports.normalizeTypeValue = normalizeTypeValue;
5
+ exports.detectTypeFromEnv = detectTypeFromEnv;
6
+ exports.resolveEnvProfile = resolveEnvProfile;
7
+ exports.getModelFromInput = getModelFromInput;
8
+ exports.getModelProviderFromInput = getModelProviderFromInput;
9
+ exports.getInputProfile = getInputProfile;
10
+ exports.getInputUsage = getInputUsage;
11
+ exports.getSessionId = getSessionId;
12
+ exports.getContextUsedTokens = getContextUsedTokens;
13
+ exports.getContextLeftPercent = getContextLeftPercent;
14
+ exports.getWorkspaceDir = getWorkspaceDir;
15
+ exports.getGitStatusFromInput = getGitStatusFromInput;
16
+ const fs = require("fs");
17
+ const constants_1 = require("../constants");
18
+ const type_1 = require("../profile/type");
19
+ const utils_1 = require("./utils");
20
+ function readStdinJson() {
21
+ if (process.stdin.isTTY)
22
+ return null;
23
+ try {
24
+ const raw = fs.readFileSync(0, "utf8");
25
+ const trimmed = raw.trim();
26
+ if (!trimmed)
27
+ return null;
28
+ const parsed = JSON.parse(trimmed);
29
+ if (!(0, utils_1.isRecord)(parsed))
30
+ return null;
31
+ return parsed;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function normalizeTypeValue(value) {
38
+ if (!value)
39
+ return null;
40
+ const normalized = (0, type_1.normalizeType)(value);
41
+ if (normalized)
42
+ return normalized;
43
+ const trimmed = String(value).trim();
44
+ return trimmed ? trimmed : null;
45
+ }
46
+ function detectTypeFromEnv() {
47
+ const matches = constants_1.DEFAULT_PROFILE_TYPES.filter((type) => {
48
+ const suffix = type.toUpperCase();
49
+ return (process.env[`CODE_ENV_PROFILE_KEY_${suffix}`] ||
50
+ process.env[`CODE_ENV_PROFILE_NAME_${suffix}`]);
51
+ });
52
+ if (matches.length === 1)
53
+ return matches[0];
54
+ return null;
55
+ }
56
+ function resolveEnvProfile(type) {
57
+ const genericKey = process.env.CODE_ENV_PROFILE_KEY || null;
58
+ const genericName = process.env.CODE_ENV_PROFILE_NAME || null;
59
+ if (!type) {
60
+ return { key: genericKey, name: genericName };
61
+ }
62
+ const suffix = type.toUpperCase();
63
+ const key = process.env[`CODE_ENV_PROFILE_KEY_${suffix}`] || genericKey;
64
+ const name = process.env[`CODE_ENV_PROFILE_NAME_${suffix}`] || genericName;
65
+ return { key: key || null, name: name || null };
66
+ }
67
+ function getModelFromInput(input) {
68
+ if (!input)
69
+ return null;
70
+ const raw = input.model;
71
+ if (!raw)
72
+ return null;
73
+ if (typeof raw === "string")
74
+ return raw;
75
+ if ((0, utils_1.isRecord)(raw)) {
76
+ const displayName = raw.displayName || raw.display_name;
77
+ if (displayName)
78
+ return String(displayName);
79
+ if (raw.id)
80
+ return String(raw.id);
81
+ }
82
+ return null;
83
+ }
84
+ function getModelProviderFromInput(input) {
85
+ if (!input || !input.model_provider)
86
+ return null;
87
+ const provider = String(input.model_provider).trim();
88
+ return provider ? provider : null;
89
+ }
90
+ function getInputProfile(input) {
91
+ if (!input || !(0, utils_1.isRecord)(input.profile))
92
+ return null;
93
+ return input.profile;
94
+ }
95
+ function getInputUsage(input) {
96
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
97
+ if (!input)
98
+ return null;
99
+ if ((0, utils_1.isRecord)(input.usage)) {
100
+ return input.usage;
101
+ }
102
+ const tokenUsage = input.token_usage;
103
+ if (tokenUsage !== null && tokenUsage !== undefined) {
104
+ if (typeof tokenUsage === "number") {
105
+ return {
106
+ todayTokens: null,
107
+ totalTokens: (0, utils_1.coerceNumber)(tokenUsage),
108
+ inputTokens: null,
109
+ outputTokens: null,
110
+ };
111
+ }
112
+ if ((0, utils_1.isRecord)(tokenUsage)) {
113
+ const record = tokenUsage;
114
+ const todayTokens = (_a = (0, utils_1.firstNumber)(record.todayTokens, record.today, record.today_tokens, record.daily, record.daily_tokens)) !== null && _a !== void 0 ? _a : null;
115
+ const totalTokens = (_b = (0, utils_1.firstNumber)(record.totalTokens, record.total, record.total_tokens)) !== null && _b !== void 0 ? _b : null;
116
+ const inputTokens = (_c = (0, utils_1.firstNumber)(record.inputTokens, record.input, record.input_tokens)) !== null && _c !== void 0 ? _c : null;
117
+ const outputTokens = (_d = (0, utils_1.firstNumber)(record.outputTokens, record.output, record.output_tokens)) !== null && _d !== void 0 ? _d : null;
118
+ const cacheRead = (_e = (0, utils_1.firstNumber)(record.cache_read_input_tokens, record.cacheReadInputTokens, record.cache_read, record.cacheRead)) !== null && _e !== void 0 ? _e : null;
119
+ const cacheWrite = (_f = (0, utils_1.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;
120
+ if (todayTokens === null &&
121
+ totalTokens === null &&
122
+ inputTokens === null &&
123
+ outputTokens === null &&
124
+ cacheRead === null &&
125
+ cacheWrite === null) {
126
+ return null;
127
+ }
128
+ const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
129
+ const computedTotal = hasCacheTokens
130
+ ? (inputTokens || 0) +
131
+ (outputTokens || 0) +
132
+ (cacheRead || 0) +
133
+ (cacheWrite || 0)
134
+ : null;
135
+ const resolvedTodayTokens = hasCacheTokens
136
+ ? (_g = todayTokens !== null && todayTokens !== void 0 ? todayTokens : totalTokens) !== null && _g !== void 0 ? _g : computedTotal
137
+ : todayTokens;
138
+ return {
139
+ todayTokens: resolvedTodayTokens,
140
+ totalTokens: totalTokens !== null && totalTokens !== void 0 ? totalTokens : null,
141
+ inputTokens,
142
+ outputTokens,
143
+ };
144
+ }
145
+ }
146
+ const contextWindow = (0, utils_1.isRecord)(input.context_window)
147
+ ? input.context_window
148
+ : (0, utils_1.isRecord)(input.contextWindow)
149
+ ? input.contextWindow
150
+ : null;
151
+ if (!contextWindow)
152
+ return null;
153
+ const totalInputTokens = (_h = (0, utils_1.firstNumber)(contextWindow.total_input_tokens, contextWindow.totalInputTokens)) !== null && _h !== void 0 ? _h : null;
154
+ const totalOutputTokens = (_j = (0, utils_1.firstNumber)(contextWindow.total_output_tokens, contextWindow.totalOutputTokens)) !== null && _j !== void 0 ? _j : null;
155
+ if (totalInputTokens !== null || totalOutputTokens !== null) {
156
+ return {
157
+ todayTokens: null,
158
+ totalTokens: null,
159
+ inputTokens: totalInputTokens,
160
+ outputTokens: totalOutputTokens,
161
+ };
162
+ }
163
+ const currentUsage = (0, utils_1.isRecord)(contextWindow.current_usage)
164
+ ? contextWindow.current_usage
165
+ : (0, utils_1.isRecord)(contextWindow.currentUsage)
166
+ ? contextWindow.currentUsage
167
+ : null;
168
+ if (!currentUsage)
169
+ return null;
170
+ const inputTokens = (_k = (0, utils_1.firstNumber)(currentUsage.input_tokens, currentUsage.inputTokens)) !== null && _k !== void 0 ? _k : null;
171
+ const outputTokens = (_l = (0, utils_1.firstNumber)(currentUsage.output_tokens, currentUsage.outputTokens)) !== null && _l !== void 0 ? _l : null;
172
+ const cacheRead = (_m = (0, utils_1.firstNumber)(currentUsage.cache_read_input_tokens, currentUsage.cacheReadInputTokens)) !== null && _m !== void 0 ? _m : null;
173
+ const cacheWrite = (_o = (0, utils_1.firstNumber)(currentUsage.cache_creation_input_tokens, currentUsage.cacheCreationInputTokens)) !== null && _o !== void 0 ? _o : null;
174
+ if (inputTokens === null &&
175
+ outputTokens === null &&
176
+ cacheRead === null &&
177
+ cacheWrite === null) {
178
+ return null;
179
+ }
180
+ const totalTokens = (inputTokens || 0) +
181
+ (outputTokens || 0) +
182
+ (cacheRead || 0) +
183
+ (cacheWrite || 0);
184
+ return {
185
+ todayTokens: totalTokens,
186
+ totalTokens: null,
187
+ inputTokens,
188
+ outputTokens,
189
+ };
190
+ }
191
+ function getSessionId(input) {
192
+ if (!input)
193
+ return null;
194
+ return (0, utils_1.firstNonEmpty)(input.session_id, input.sessionId);
195
+ }
196
+ function getContextUsedTokens(input) {
197
+ if (!input)
198
+ return null;
199
+ return (0, utils_1.coerceNumber)(input.context_window_used_tokens);
200
+ }
201
+ function getContextLeftPercent(input, type) {
202
+ if (!input)
203
+ return null;
204
+ const raw = (0, utils_1.coerceNumber)(input.context_window_percent);
205
+ if (raw === null || raw < 0)
206
+ return null;
207
+ const percent = raw <= 1 ? raw * 100 : raw;
208
+ if (percent > 100)
209
+ return null;
210
+ const usedTokens = getContextUsedTokens(input);
211
+ const normalizedType = normalizeTypeValue(type);
212
+ const preferRemaining = normalizedType === "codex" ||
213
+ normalizedType === "claude" ||
214
+ usedTokens === null ||
215
+ (usedTokens <= 0 && percent >= 99);
216
+ const left = preferRemaining ? percent : 100 - percent;
217
+ return Math.max(0, Math.min(100, left));
218
+ }
219
+ function getWorkspaceDir(input) {
220
+ if (!input || !(0, utils_1.isRecord)(input.workspace))
221
+ return null;
222
+ const currentDir = input.workspace.current_dir;
223
+ if (currentDir) {
224
+ const trimmed = String(currentDir).trim();
225
+ if (trimmed)
226
+ return trimmed;
227
+ }
228
+ const projectDir = input.workspace.project_dir;
229
+ if (!projectDir)
230
+ return null;
231
+ const trimmed = String(projectDir).trim();
232
+ return trimmed ? trimmed : null;
233
+ }
234
+ function getGitStatusFromInput(input) {
235
+ if (!input || !input.git_branch)
236
+ return null;
237
+ const branch = String(input.git_branch).trim();
238
+ if (!branch)
239
+ return null;
240
+ return {
241
+ branch,
242
+ ahead: 0,
243
+ behind: 0,
244
+ staged: 0,
245
+ unstaged: 0,
246
+ untracked: 0,
247
+ conflicted: 0,
248
+ };
249
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ICON_CWD = exports.ICON_REVIEW = exports.ICON_CONTEXT = exports.ICON_USAGE = exports.ICON_MODEL = exports.ICON_PROFILE = exports.ICON_GIT = void 0;
4
+ exports.colorize = colorize;
5
+ exports.dim = dim;
6
+ const COLOR_ENABLED = !process.env.NO_COLOR && process.env.TERM !== "dumb";
7
+ const ANSI_RESET = "\x1b[0m";
8
+ exports.ICON_GIT = "⎇";
9
+ exports.ICON_PROFILE = "👤";
10
+ exports.ICON_MODEL = "⚙";
11
+ exports.ICON_USAGE = "⚡";
12
+ exports.ICON_CONTEXT = "🧠";
13
+ exports.ICON_REVIEW = "📝";
14
+ exports.ICON_CWD = "📁";
15
+ function colorize(text, colorCode) {
16
+ if (!COLOR_ENABLED)
17
+ return text;
18
+ return `\x1b[${colorCode}m${text}${ANSI_RESET}`;
19
+ }
20
+ function dim(text) {
21
+ return colorize(text, "2");
22
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseUsageTotalsRecord = parseUsageTotalsRecord;
4
+ exports.getUsageTotalsFromInput = getUsageTotalsFromInput;
5
+ exports.normalizeInputUsage = normalizeInputUsage;
6
+ exports.resolveUsageFromRecords = resolveUsageFromRecords;
7
+ const type_1 = require("../profile/type");
8
+ const usage_1 = require("../usage");
9
+ const utils_1 = require("./utils");
10
+ function parseUsageTotalsRecord(record) {
11
+ var _a, _b, _c, _d, _e;
12
+ const inputTokens = (_a = (0, utils_1.firstNumber)(record.inputTokens, record.input, record.input_tokens)) !== null && _a !== void 0 ? _a : null;
13
+ const outputTokens = (_b = (0, utils_1.firstNumber)(record.outputTokens, record.output, record.output_tokens)) !== null && _b !== void 0 ? _b : null;
14
+ const totalTokens = (_c = (0, utils_1.firstNumber)(record.totalTokens, record.total, record.total_tokens)) !== null && _c !== void 0 ? _c : null;
15
+ const cacheRead = (_d = (0, utils_1.firstNumber)(record.cache_read_input_tokens, record.cacheReadInputTokens, record.cache_read, record.cacheRead)) !== null && _d !== void 0 ? _d : null;
16
+ const cacheWrite = (_e = (0, utils_1.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;
17
+ let computedTotal = null;
18
+ if (inputTokens !== null ||
19
+ outputTokens !== null ||
20
+ cacheRead !== null ||
21
+ cacheWrite !== null) {
22
+ computedTotal =
23
+ (inputTokens || 0) +
24
+ (outputTokens || 0) +
25
+ (cacheRead || 0) +
26
+ (cacheWrite || 0);
27
+ }
28
+ const resolvedTotal = totalTokens !== null && totalTokens !== void 0 ? totalTokens : computedTotal;
29
+ if (inputTokens === null &&
30
+ outputTokens === null &&
31
+ resolvedTotal === null) {
32
+ return null;
33
+ }
34
+ return {
35
+ inputTokens,
36
+ outputTokens,
37
+ totalTokens: resolvedTotal,
38
+ };
39
+ }
40
+ function getUsageTotalsFromInput(input) {
41
+ var _a, _b;
42
+ if (!input)
43
+ return null;
44
+ const contextWindow = (0, utils_1.isRecord)(input.context_window)
45
+ ? input.context_window
46
+ : (0, utils_1.isRecord)(input.contextWindow)
47
+ ? input.contextWindow
48
+ : null;
49
+ if (contextWindow) {
50
+ const totalInputTokens = (_a = (0, utils_1.firstNumber)(contextWindow.total_input_tokens, contextWindow.totalInputTokens)) !== null && _a !== void 0 ? _a : null;
51
+ const totalOutputTokens = (_b = (0, utils_1.firstNumber)(contextWindow.total_output_tokens, contextWindow.totalOutputTokens)) !== null && _b !== void 0 ? _b : null;
52
+ if (totalInputTokens !== null || totalOutputTokens !== null) {
53
+ return {
54
+ inputTokens: totalInputTokens,
55
+ outputTokens: totalOutputTokens,
56
+ totalTokens: (totalInputTokens || 0) + (totalOutputTokens || 0),
57
+ };
58
+ }
59
+ }
60
+ if (typeof input.token_usage === "number") {
61
+ return {
62
+ inputTokens: null,
63
+ outputTokens: null,
64
+ totalTokens: (0, utils_1.coerceNumber)(input.token_usage),
65
+ };
66
+ }
67
+ if ((0, utils_1.isRecord)(input.token_usage)) {
68
+ const tokenUsage = input.token_usage;
69
+ const totalUsage = ((0, utils_1.isRecord)(tokenUsage.total_token_usage)
70
+ ? tokenUsage.total_token_usage
71
+ : null) ||
72
+ ((0, utils_1.isRecord)(tokenUsage.totalTokenUsage)
73
+ ? tokenUsage.totalTokenUsage
74
+ : null);
75
+ if (totalUsage) {
76
+ const parsed = parseUsageTotalsRecord(totalUsage);
77
+ if (parsed)
78
+ return parsed;
79
+ }
80
+ return parseUsageTotalsRecord(tokenUsage);
81
+ }
82
+ if ((0, utils_1.isRecord)(input.usage)) {
83
+ return parseUsageTotalsRecord(input.usage);
84
+ }
85
+ return null;
86
+ }
87
+ function normalizeInputUsage(inputUsage) {
88
+ if (!inputUsage)
89
+ return null;
90
+ const usage = {
91
+ todayTokens: (0, utils_1.coerceNumber)(inputUsage.todayTokens),
92
+ totalTokens: (0, utils_1.coerceNumber)(inputUsage.totalTokens),
93
+ inputTokens: (0, utils_1.coerceNumber)(inputUsage.inputTokens),
94
+ outputTokens: (0, utils_1.coerceNumber)(inputUsage.outputTokens),
95
+ };
96
+ const hasUsage = usage.todayTokens !== null ||
97
+ usage.totalTokens !== null ||
98
+ usage.inputTokens !== null ||
99
+ usage.outputTokens !== null;
100
+ return hasUsage ? usage : null;
101
+ }
102
+ function resolveUsageFromRecords(config, configPath, type, profileKey, profileName, syncUsage) {
103
+ try {
104
+ const normalized = (0, type_1.normalizeType)(type || "");
105
+ if (!normalized || (!profileKey && !profileName))
106
+ return null;
107
+ const totals = (0, usage_1.readUsageTotalsIndex)(config, configPath, syncUsage);
108
+ if (!totals)
109
+ return null;
110
+ const usage = (0, usage_1.resolveUsageTotalsForProfile)(totals, normalized, profileKey, profileName);
111
+ if (!usage)
112
+ return null;
113
+ return {
114
+ todayTokens: usage.today,
115
+ totalTokens: usage.total,
116
+ inputTokens: null,
117
+ outputTokens: null,
118
+ };
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRecord = isRecord;
4
+ exports.firstNonEmpty = firstNonEmpty;
5
+ exports.coerceNumber = coerceNumber;
6
+ exports.firstNumber = firstNumber;
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ function firstNonEmpty(...values) {
11
+ for (const value of values) {
12
+ if (value === null || value === undefined)
13
+ continue;
14
+ const text = String(value).trim();
15
+ if (text)
16
+ return text;
17
+ }
18
+ return null;
19
+ }
20
+ function coerceNumber(value) {
21
+ if (value === null || value === undefined || value === "")
22
+ return null;
23
+ const num = Number(value);
24
+ if (!Number.isFinite(num))
25
+ return null;
26
+ return num;
27
+ }
28
+ function firstNumber(...values) {
29
+ for (const value of values) {
30
+ const num = coerceNumber(value);
31
+ if (num !== null)
32
+ return num;
33
+ }
34
+ return null;
35
+ }
@@ -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.4",
4
4
  "description": "Switch between Claude Code and Codex environment variables from a single CLI",
5
5
  "bin": {
6
6
  "codenv": "bin/index.js"