@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.
- package/bin/statusline/debug.js +42 -0
- package/bin/statusline/format.js +60 -0
- package/bin/statusline/git.js +96 -0
- package/bin/statusline/index.js +73 -400
- package/bin/statusline/input.js +249 -0
- package/bin/statusline/style.js +22 -0
- package/bin/statusline/types.js +2 -0
- package/bin/statusline/usage.js +123 -0
- package/bin/statusline/utils.js +35 -0
- package/bin/usage/index.js +133 -10
- package/package.json +1 -1
- package/src/statusline/debug.ts +40 -0
- package/src/statusline/format.ts +68 -0
- package/src/statusline/git.ts +82 -0
- package/src/statusline/index.ts +93 -470
- package/src/statusline/input.ts +300 -0
- package/src/statusline/style.ts +19 -0
- package/src/statusline/types.ts +105 -0
- package/src/statusline/usage.ts +175 -0
- package/src/statusline/utils.ts +27 -0
- package/src/usage/index.ts +156 -10
package/src/usage/index.ts
CHANGED
|
@@ -40,9 +40,20 @@ interface UsageStateEntry {
|
|
|
40
40
|
cwd: string | null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
interface UsageSessionEntry {
|
|
44
|
+
type: ProfileType;
|
|
45
|
+
inputTokens: number;
|
|
46
|
+
outputTokens: number;
|
|
47
|
+
totalTokens: number;
|
|
48
|
+
startTs: string | null;
|
|
49
|
+
endTs: string | null;
|
|
50
|
+
cwd: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
interface UsageStateFile {
|
|
44
54
|
version: number;
|
|
45
55
|
files: Record<string, UsageStateEntry>;
|
|
56
|
+
sessions?: Record<string, UsageSessionEntry>;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
interface ProfileLogEntry {
|
|
@@ -78,6 +89,12 @@ interface SessionStats {
|
|
|
78
89
|
sessionId: string | null;
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
interface UsageTotalsInput {
|
|
93
|
+
inputTokens: number | null;
|
|
94
|
+
outputTokens: number | null;
|
|
95
|
+
totalTokens: number | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
function resolveDefaultConfigDir(configPath: string | null): string {
|
|
82
99
|
if (configPath) return path.dirname(configPath);
|
|
83
100
|
return path.join(os.homedir(), ".config", "code-env");
|
|
@@ -172,6 +189,18 @@ function normalizeUsageType(type: string | null | undefined): string | null {
|
|
|
172
189
|
return trimmed ? trimmed : null;
|
|
173
190
|
}
|
|
174
191
|
|
|
192
|
+
function buildSessionKey(type: ProfileType | null, sessionId: string): string {
|
|
193
|
+
const normalized = normalizeUsageType(type || "");
|
|
194
|
+
return normalized ? `${normalized}::${sessionId}` : sessionId;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toFiniteNumber(value: number | null | undefined): number | null {
|
|
198
|
+
if (value === null || value === undefined) return null;
|
|
199
|
+
const num = Number(value);
|
|
200
|
+
if (!Number.isFinite(num)) return null;
|
|
201
|
+
return num;
|
|
202
|
+
}
|
|
203
|
+
|
|
175
204
|
function buildUsageLookupKey(
|
|
176
205
|
type: string | null | undefined,
|
|
177
206
|
profileId: string | null | undefined
|
|
@@ -212,6 +241,81 @@ export function resolveUsageTotalsForProfile(
|
|
|
212
241
|
);
|
|
213
242
|
}
|
|
214
243
|
|
|
244
|
+
export function syncUsageFromStatuslineInput(
|
|
245
|
+
config: Config,
|
|
246
|
+
configPath: string | null,
|
|
247
|
+
type: ProfileType | null,
|
|
248
|
+
profileKey: string | null,
|
|
249
|
+
profileName: string | null,
|
|
250
|
+
sessionId: string | null,
|
|
251
|
+
totals: UsageTotalsInput | null,
|
|
252
|
+
cwd: string | null
|
|
253
|
+
): void {
|
|
254
|
+
if (!sessionId) return;
|
|
255
|
+
if (!totals) return;
|
|
256
|
+
if (!profileKey && !profileName) return;
|
|
257
|
+
const normalizedType = normalizeType(type || "");
|
|
258
|
+
if (!normalizedType) return;
|
|
259
|
+
const usagePath = getUsagePath(config, configPath);
|
|
260
|
+
if (!usagePath) return;
|
|
261
|
+
const inputTokens = toFiniteNumber(totals.inputTokens) ?? 0;
|
|
262
|
+
const outputTokens = toFiniteNumber(totals.outputTokens) ?? 0;
|
|
263
|
+
const totalTokens =
|
|
264
|
+
toFiniteNumber(totals.totalTokens) ?? inputTokens + outputTokens;
|
|
265
|
+
if (!Number.isFinite(totalTokens)) return;
|
|
266
|
+
|
|
267
|
+
const statePath = getUsageStatePath(usagePath, config);
|
|
268
|
+
const lockPath = `${statePath}.lock`;
|
|
269
|
+
const lockFd = acquireLock(lockPath);
|
|
270
|
+
if (lockFd === null) return;
|
|
271
|
+
try {
|
|
272
|
+
const state = readUsageState(statePath);
|
|
273
|
+
const sessions = state.sessions || {};
|
|
274
|
+
const key = buildSessionKey(normalizedType, sessionId);
|
|
275
|
+
const prev = sessions[key];
|
|
276
|
+
const prevInput = prev ? prev.inputTokens : 0;
|
|
277
|
+
const prevOutput = prev ? prev.outputTokens : 0;
|
|
278
|
+
const prevTotal = prev ? prev.totalTokens : 0;
|
|
279
|
+
|
|
280
|
+
let deltaInput = inputTokens - prevInput;
|
|
281
|
+
let deltaOutput = outputTokens - prevOutput;
|
|
282
|
+
let deltaTotal = totalTokens - prevTotal;
|
|
283
|
+
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
284
|
+
deltaInput = inputTokens;
|
|
285
|
+
deltaOutput = outputTokens;
|
|
286
|
+
deltaTotal = totalTokens;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (deltaTotal > 0) {
|
|
290
|
+
const record: UsageRecord = {
|
|
291
|
+
ts: new Date().toISOString(),
|
|
292
|
+
type: normalizedType,
|
|
293
|
+
profileKey: profileKey || null,
|
|
294
|
+
profileName: profileName || null,
|
|
295
|
+
inputTokens: deltaInput,
|
|
296
|
+
outputTokens: deltaOutput,
|
|
297
|
+
totalTokens: deltaTotal,
|
|
298
|
+
};
|
|
299
|
+
appendUsageRecord(usagePath, record);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const now = new Date().toISOString();
|
|
303
|
+
sessions[key] = {
|
|
304
|
+
type: normalizedType,
|
|
305
|
+
inputTokens,
|
|
306
|
+
outputTokens,
|
|
307
|
+
totalTokens,
|
|
308
|
+
startTs: prev ? prev.startTs : now,
|
|
309
|
+
endTs: now,
|
|
310
|
+
cwd: cwd || (prev ? prev.cwd : null),
|
|
311
|
+
};
|
|
312
|
+
state.sessions = sessions;
|
|
313
|
+
writeUsageState(statePath, state);
|
|
314
|
+
} finally {
|
|
315
|
+
releaseLock(lockPath, lockFd);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
215
319
|
export function logProfileUse(
|
|
216
320
|
config: Config,
|
|
217
321
|
configPath: string | null,
|
|
@@ -424,19 +528,21 @@ function resolveProfileForSession(
|
|
|
424
528
|
|
|
425
529
|
function readUsageState(statePath: string): UsageStateFile {
|
|
426
530
|
if (!statePath || !fs.existsSync(statePath)) {
|
|
427
|
-
return { version: 1, files: {} };
|
|
531
|
+
return { version: 1, files: {}, sessions: {} };
|
|
428
532
|
}
|
|
429
533
|
try {
|
|
430
534
|
const raw = fs.readFileSync(statePath, "utf8");
|
|
431
535
|
const parsed = JSON.parse(raw);
|
|
432
536
|
if (!parsed || typeof parsed !== "object") {
|
|
433
|
-
return { version: 1, files: {} };
|
|
537
|
+
return { version: 1, files: {}, sessions: {} };
|
|
434
538
|
}
|
|
435
539
|
const files =
|
|
436
540
|
parsed.files && typeof parsed.files === "object" ? parsed.files : {};
|
|
437
|
-
|
|
541
|
+
const sessions =
|
|
542
|
+
parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
|
|
543
|
+
return { version: 1, files, sessions };
|
|
438
544
|
} catch {
|
|
439
|
-
return { version: 1, files: {} };
|
|
545
|
+
return { version: 1, files: {}, sessions: {} };
|
|
440
546
|
}
|
|
441
547
|
}
|
|
442
548
|
|
|
@@ -754,6 +860,7 @@ export function syncUsageFromSessions(
|
|
|
754
860
|
|
|
755
861
|
const state = readUsageState(statePath);
|
|
756
862
|
const files = state.files || {};
|
|
863
|
+
const sessions = state.sessions || {};
|
|
757
864
|
const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
|
|
758
865
|
const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
|
|
759
866
|
|
|
@@ -786,16 +893,34 @@ export function syncUsageFromSessions(
|
|
|
786
893
|
stats.sessionId
|
|
787
894
|
);
|
|
788
895
|
if (!resolved.match) return;
|
|
896
|
+
const sessionKey =
|
|
897
|
+
stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
|
|
898
|
+
const sessionPrev = sessionKey ? sessions[sessionKey] : null;
|
|
789
899
|
const prevInput = prev ? prev.inputTokens : 0;
|
|
790
900
|
const prevOutput = prev ? prev.outputTokens : 0;
|
|
791
901
|
const prevTotal = prev ? prev.totalTokens : 0;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
902
|
+
const prevInputMax = sessionPrev
|
|
903
|
+
? Math.max(prevInput, sessionPrev.inputTokens)
|
|
904
|
+
: prevInput;
|
|
905
|
+
const prevOutputMax = sessionPrev
|
|
906
|
+
? Math.max(prevOutput, sessionPrev.outputTokens)
|
|
907
|
+
: prevOutput;
|
|
908
|
+
const prevTotalMax = sessionPrev
|
|
909
|
+
? Math.max(prevTotal, sessionPrev.totalTokens)
|
|
910
|
+
: prevTotal;
|
|
911
|
+
let deltaInput = stats.inputTokens - prevInputMax;
|
|
912
|
+
let deltaOutput = stats.outputTokens - prevOutputMax;
|
|
913
|
+
let deltaTotal = stats.totalTokens - prevTotalMax;
|
|
795
914
|
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
915
|
+
if (sessionPrev) {
|
|
916
|
+
deltaInput = 0;
|
|
917
|
+
deltaOutput = 0;
|
|
918
|
+
deltaTotal = 0;
|
|
919
|
+
} else {
|
|
920
|
+
deltaInput = stats.inputTokens;
|
|
921
|
+
deltaOutput = stats.outputTokens;
|
|
922
|
+
deltaTotal = stats.totalTokens;
|
|
923
|
+
}
|
|
799
924
|
}
|
|
800
925
|
if (deltaTotal > 0) {
|
|
801
926
|
const record: UsageRecord = {
|
|
@@ -809,6 +934,26 @@ export function syncUsageFromSessions(
|
|
|
809
934
|
};
|
|
810
935
|
appendUsageRecord(usagePath, record);
|
|
811
936
|
}
|
|
937
|
+
if (sessionKey) {
|
|
938
|
+
const nextInput = sessionPrev
|
|
939
|
+
? Math.max(sessionPrev.inputTokens, stats.inputTokens)
|
|
940
|
+
: stats.inputTokens;
|
|
941
|
+
const nextOutput = sessionPrev
|
|
942
|
+
? Math.max(sessionPrev.outputTokens, stats.outputTokens)
|
|
943
|
+
: stats.outputTokens;
|
|
944
|
+
const nextTotal = sessionPrev
|
|
945
|
+
? Math.max(sessionPrev.totalTokens, stats.totalTokens)
|
|
946
|
+
: stats.totalTokens;
|
|
947
|
+
sessions[sessionKey] = {
|
|
948
|
+
type,
|
|
949
|
+
inputTokens: nextInput,
|
|
950
|
+
outputTokens: nextOutput,
|
|
951
|
+
totalTokens: nextTotal,
|
|
952
|
+
startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
|
|
953
|
+
endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
|
|
954
|
+
cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
812
957
|
files[filePath] = {
|
|
813
958
|
mtimeMs: stat.mtimeMs,
|
|
814
959
|
size: stat.size,
|
|
@@ -826,6 +971,7 @@ export function syncUsageFromSessions(
|
|
|
826
971
|
for (const filePath of claudeFiles) processFile(filePath, "claude");
|
|
827
972
|
|
|
828
973
|
state.files = files;
|
|
974
|
+
state.sessions = sessions;
|
|
829
975
|
writeUsageState(statePath, state);
|
|
830
976
|
} finally {
|
|
831
977
|
releaseLock(lockPath, lockFd);
|