@praeviso/code-env-switch 0.1.4 → 0.1.5

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,263 @@
1
+ import type {
2
+ StatuslineInput,
3
+ StatuslineInputUsage,
4
+ StatuslineUsageTotals,
5
+ } from "../types";
6
+ import { coerceNumber, firstNumber, isRecord } from "../utils";
7
+
8
+ function resolveOutputTokens(record: Record<string, unknown>): number | null {
9
+ const outputTokens =
10
+ firstNumber(
11
+ record.outputTokens,
12
+ record.output,
13
+ record.output_tokens
14
+ ) ?? null;
15
+ const reasoningTokens =
16
+ firstNumber(
17
+ record.reasoning_output_tokens,
18
+ record.reasoningOutputTokens,
19
+ record.reasoning_output
20
+ ) ?? null;
21
+ if (outputTokens === null && reasoningTokens === null) return null;
22
+ if (reasoningTokens === null) return outputTokens;
23
+ return (outputTokens || 0) + reasoningTokens;
24
+ }
25
+
26
+ function parseCodexUsageTotalsRecord(
27
+ record: Record<string, unknown>
28
+ ): StatuslineUsageTotals | null {
29
+ const inputTokens =
30
+ firstNumber(
31
+ record.inputTokens,
32
+ record.input,
33
+ record.input_tokens
34
+ ) ?? null;
35
+ const outputTokens = resolveOutputTokens(record);
36
+ const cacheRead =
37
+ firstNumber(
38
+ record.cached_input_tokens,
39
+ record.cachedInputTokens,
40
+ record.cache_read_input_tokens,
41
+ record.cacheReadInputTokens,
42
+ record.cache_read,
43
+ record.cacheRead
44
+ ) ?? null;
45
+ const cacheWrite =
46
+ firstNumber(
47
+ record.cache_creation_input_tokens,
48
+ record.cacheCreationInputTokens,
49
+ record.cache_write_input_tokens,
50
+ record.cacheWriteInputTokens,
51
+ record.cache_write,
52
+ record.cacheWrite
53
+ ) ?? null;
54
+ const totalTokens =
55
+ firstNumber(
56
+ record.totalTokens,
57
+ record.total,
58
+ record.total_tokens
59
+ ) ?? null;
60
+ let computedTotal: number | null = null;
61
+ if (
62
+ inputTokens !== null ||
63
+ outputTokens !== null ||
64
+ cacheRead !== null ||
65
+ cacheWrite !== null
66
+ ) {
67
+ computedTotal =
68
+ (inputTokens || 0) +
69
+ (outputTokens || 0) +
70
+ (cacheRead || 0) +
71
+ (cacheWrite || 0);
72
+ }
73
+ const resolvedTotal = totalTokens ?? computedTotal;
74
+ if (
75
+ inputTokens === null &&
76
+ outputTokens === null &&
77
+ cacheRead === null &&
78
+ cacheWrite === null &&
79
+ resolvedTotal === null
80
+ ) {
81
+ return null;
82
+ }
83
+ return {
84
+ inputTokens,
85
+ outputTokens,
86
+ cacheReadTokens: cacheRead,
87
+ cacheWriteTokens: cacheWrite,
88
+ totalTokens: resolvedTotal,
89
+ };
90
+ }
91
+
92
+ function parseCodexInputUsageRecord(
93
+ record: Record<string, unknown>
94
+ ): StatuslineInputUsage | null {
95
+ const todayTokens =
96
+ firstNumber(
97
+ record.todayTokens,
98
+ record.today,
99
+ record.today_tokens,
100
+ record.daily,
101
+ record.daily_tokens
102
+ ) ?? null;
103
+ const totalTokens =
104
+ firstNumber(
105
+ record.totalTokens,
106
+ record.total,
107
+ record.total_tokens
108
+ ) ?? null;
109
+ const inputTokens =
110
+ firstNumber(
111
+ record.inputTokens,
112
+ record.input,
113
+ record.input_tokens
114
+ ) ?? null;
115
+ const outputTokens = resolveOutputTokens(record);
116
+ const cacheRead =
117
+ firstNumber(
118
+ record.cached_input_tokens,
119
+ record.cachedInputTokens,
120
+ record.cache_read_input_tokens,
121
+ record.cacheReadInputTokens,
122
+ record.cache_read,
123
+ record.cacheRead
124
+ ) ?? null;
125
+ const cacheWrite =
126
+ firstNumber(
127
+ record.cache_creation_input_tokens,
128
+ record.cacheCreationInputTokens,
129
+ record.cache_write_input_tokens,
130
+ record.cacheWriteInputTokens,
131
+ record.cache_write,
132
+ record.cacheWrite
133
+ ) ?? null;
134
+ if (
135
+ todayTokens === null &&
136
+ totalTokens === null &&
137
+ inputTokens === null &&
138
+ outputTokens === null &&
139
+ cacheRead === null &&
140
+ cacheWrite === null
141
+ ) {
142
+ return null;
143
+ }
144
+ const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
145
+ const computedTotal = hasCacheTokens
146
+ ? (inputTokens || 0) +
147
+ (outputTokens || 0) +
148
+ (cacheRead || 0) +
149
+ (cacheWrite || 0)
150
+ : null;
151
+ const resolvedTodayTokens = hasCacheTokens
152
+ ? todayTokens ?? totalTokens ?? computedTotal
153
+ : todayTokens;
154
+ return {
155
+ todayTokens: resolvedTodayTokens,
156
+ totalTokens: totalTokens ?? null,
157
+ inputTokens,
158
+ outputTokens,
159
+ cacheReadTokens: cacheRead,
160
+ cacheWriteTokens: cacheWrite,
161
+ };
162
+ }
163
+
164
+ function resolveNestedRecord(
165
+ record: Record<string, unknown>,
166
+ ...keys: string[]
167
+ ): Record<string, unknown> | null {
168
+ for (const key of keys) {
169
+ if (isRecord(record[key])) {
170
+ return record[key] as Record<string, unknown>;
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+
176
+ export function getCodexUsageTotalsFromInput(
177
+ input: StatuslineInput | null
178
+ ): StatuslineUsageTotals | null {
179
+ if (!input) return null;
180
+ const tokenUsage = input.token_usage;
181
+ if (typeof tokenUsage === "number") {
182
+ return {
183
+ inputTokens: null,
184
+ outputTokens: null,
185
+ cacheReadTokens: null,
186
+ cacheWriteTokens: null,
187
+ totalTokens: coerceNumber(tokenUsage),
188
+ };
189
+ }
190
+ if (isRecord(tokenUsage)) {
191
+ const totalUsage = resolveNestedRecord(
192
+ tokenUsage,
193
+ "total_token_usage",
194
+ "totalTokenUsage"
195
+ );
196
+ if (totalUsage) {
197
+ const parsed = parseCodexUsageTotalsRecord(totalUsage);
198
+ if (parsed) return parsed;
199
+ }
200
+ const lastUsage = resolveNestedRecord(
201
+ tokenUsage,
202
+ "last_token_usage",
203
+ "lastTokenUsage"
204
+ );
205
+ if (lastUsage) {
206
+ const parsed = parseCodexUsageTotalsRecord(lastUsage);
207
+ if (parsed) return parsed;
208
+ }
209
+ const parsed = parseCodexUsageTotalsRecord(tokenUsage as Record<string, unknown>);
210
+ if (parsed) return parsed;
211
+ }
212
+ if (isRecord(input.usage)) {
213
+ return parseCodexUsageTotalsRecord(input.usage as Record<string, unknown>);
214
+ }
215
+ return null;
216
+ }
217
+
218
+ export function getCodexInputUsage(
219
+ input: StatuslineInput | null
220
+ ): StatuslineInputUsage | null {
221
+ if (!input) return null;
222
+ if (isRecord(input.usage)) {
223
+ const parsed = parseCodexInputUsageRecord(input.usage as Record<string, unknown>);
224
+ if (parsed) return parsed;
225
+ return input.usage as StatuslineInputUsage;
226
+ }
227
+ const tokenUsage = input.token_usage;
228
+ if (tokenUsage !== null && tokenUsage !== undefined) {
229
+ if (typeof tokenUsage === "number") {
230
+ return {
231
+ todayTokens: null,
232
+ totalTokens: coerceNumber(tokenUsage),
233
+ inputTokens: null,
234
+ outputTokens: null,
235
+ cacheReadTokens: null,
236
+ cacheWriteTokens: null,
237
+ };
238
+ }
239
+ if (isRecord(tokenUsage)) {
240
+ const totalUsage = resolveNestedRecord(
241
+ tokenUsage,
242
+ "total_token_usage",
243
+ "totalTokenUsage"
244
+ );
245
+ if (totalUsage) {
246
+ const parsed = parseCodexInputUsageRecord(totalUsage);
247
+ if (parsed) return parsed;
248
+ }
249
+ const lastUsage = resolveNestedRecord(
250
+ tokenUsage,
251
+ "last_token_usage",
252
+ "lastTokenUsage"
253
+ );
254
+ if (lastUsage) {
255
+ const parsed = parseCodexInputUsageRecord(lastUsage);
256
+ if (parsed) return parsed;
257
+ }
258
+ const parsed = parseCodexInputUsageRecord(tokenUsage as Record<string, unknown>);
259
+ if (parsed) return parsed;
260
+ }
261
+ }
262
+ return null;
263
+ }
@@ -2,127 +2,26 @@ import type { Config } from "../types";
2
2
  import { normalizeType } from "../profile/type";
3
3
  import { readUsageTotalsIndex, resolveUsageTotalsForProfile } from "../usage";
4
4
  import type { StatuslineInput, StatuslineInputUsage, StatuslineUsage, StatuslineUsageTotals } from "./types";
5
- import { coerceNumber, firstNumber, isRecord } from "./utils";
6
-
7
- export function parseUsageTotalsRecord(
8
- record: Record<string, unknown>
9
- ): StatuslineUsageTotals | null {
10
- const inputTokens =
11
- firstNumber(
12
- record.inputTokens,
13
- record.input,
14
- record.input_tokens
15
- ) ?? null;
16
- const outputTokens =
17
- firstNumber(
18
- record.outputTokens,
19
- record.output,
20
- record.output_tokens
21
- ) ?? null;
22
- const totalTokens =
23
- firstNumber(
24
- record.totalTokens,
25
- record.total,
26
- record.total_tokens
27
- ) ?? null;
28
- const cacheRead =
29
- firstNumber(
30
- record.cache_read_input_tokens,
31
- record.cacheReadInputTokens,
32
- record.cache_read,
33
- record.cacheRead
34
- ) ?? null;
35
- const cacheWrite =
36
- firstNumber(
37
- record.cache_creation_input_tokens,
38
- record.cacheCreationInputTokens,
39
- record.cache_write_input_tokens,
40
- record.cacheWriteInputTokens,
41
- record.cache_write,
42
- record.cacheWrite
43
- ) ?? null;
44
- let computedTotal: number | null = null;
45
- if (
46
- inputTokens !== null ||
47
- outputTokens !== null ||
48
- cacheRead !== null ||
49
- cacheWrite !== null
50
- ) {
51
- computedTotal =
52
- (inputTokens || 0) +
53
- (outputTokens || 0) +
54
- (cacheRead || 0) +
55
- (cacheWrite || 0);
56
- }
57
- const resolvedTotal = totalTokens ?? computedTotal;
58
- if (
59
- inputTokens === null &&
60
- outputTokens === null &&
61
- resolvedTotal === null
62
- ) {
63
- return null;
64
- }
65
- return {
66
- inputTokens,
67
- outputTokens,
68
- totalTokens: resolvedTotal,
69
- };
70
- }
5
+ import { coerceNumber } from "./utils";
6
+ import { getClaudeUsageTotalsFromInput } from "./usage/claude";
7
+ import { getCodexUsageTotalsFromInput } from "./usage/codex";
71
8
 
72
9
  export function getUsageTotalsFromInput(
73
- input: StatuslineInput | null
10
+ input: StatuslineInput | null,
11
+ type: string | null
74
12
  ): StatuslineUsageTotals | null {
75
13
  if (!input) return null;
76
- const contextWindow = isRecord(input.context_window)
77
- ? (input.context_window as Record<string, unknown>)
78
- : isRecord(input.contextWindow)
79
- ? (input.contextWindow as Record<string, unknown>)
80
- : null;
81
- if (contextWindow) {
82
- const totalInputTokens =
83
- firstNumber(
84
- contextWindow.total_input_tokens,
85
- contextWindow.totalInputTokens
86
- ) ?? null;
87
- const totalOutputTokens =
88
- firstNumber(
89
- contextWindow.total_output_tokens,
90
- contextWindow.totalOutputTokens
91
- ) ?? null;
92
- if (totalInputTokens !== null || totalOutputTokens !== null) {
93
- return {
94
- inputTokens: totalInputTokens,
95
- outputTokens: totalOutputTokens,
96
- totalTokens: (totalInputTokens || 0) + (totalOutputTokens || 0),
97
- };
98
- }
99
- }
100
- if (typeof input.token_usage === "number") {
101
- return {
102
- inputTokens: null,
103
- outputTokens: null,
104
- totalTokens: coerceNumber(input.token_usage),
105
- };
106
- }
107
- if (isRecord(input.token_usage)) {
108
- const tokenUsage = input.token_usage as Record<string, unknown>;
109
- const totalUsage =
110
- (isRecord(tokenUsage.total_token_usage)
111
- ? (tokenUsage.total_token_usage as Record<string, unknown>)
112
- : null) ||
113
- (isRecord(tokenUsage.totalTokenUsage)
114
- ? (tokenUsage.totalTokenUsage as Record<string, unknown>)
115
- : null);
116
- if (totalUsage) {
117
- const parsed = parseUsageTotalsRecord(totalUsage);
118
- if (parsed) return parsed;
119
- }
120
- return parseUsageTotalsRecord(tokenUsage);
14
+ const normalized = normalizeType(type || "");
15
+ if (normalized === "codex") {
16
+ return getCodexUsageTotalsFromInput(input);
121
17
  }
122
- if (isRecord(input.usage)) {
123
- return parseUsageTotalsRecord(input.usage as Record<string, unknown>);
18
+ if (normalized === "claude") {
19
+ return getClaudeUsageTotalsFromInput(input);
124
20
  }
125
- return null;
21
+ return (
22
+ getCodexUsageTotalsFromInput(input) ||
23
+ getClaudeUsageTotalsFromInput(input)
24
+ );
126
25
  }
127
26
 
128
27
  export function normalizeInputUsage(
@@ -131,15 +30,19 @@ export function normalizeInputUsage(
131
30
  if (!inputUsage) return null;
132
31
  const usage: StatuslineUsage = {
133
32
  todayTokens: coerceNumber(inputUsage.todayTokens),
134
- totalTokens: coerceNumber(inputUsage.totalTokens),
33
+ totalTokens: coerceNumber(inputUsage.totalTokens),
135
34
  inputTokens: coerceNumber(inputUsage.inputTokens),
136
35
  outputTokens: coerceNumber(inputUsage.outputTokens),
36
+ cacheReadTokens: coerceNumber(inputUsage.cacheReadTokens),
37
+ cacheWriteTokens: coerceNumber(inputUsage.cacheWriteTokens),
137
38
  };
138
39
  const hasUsage =
139
40
  usage.todayTokens !== null ||
140
41
  usage.totalTokens !== null ||
141
42
  usage.inputTokens !== null ||
142
- usage.outputTokens !== null;
43
+ usage.outputTokens !== null ||
44
+ usage.cacheReadTokens !== null ||
45
+ usage.cacheWriteTokens !== null;
143
46
  return hasUsage ? usage : null;
144
47
  }
145
48
 
@@ -166,8 +69,10 @@ export function resolveUsageFromRecords(
166
69
  return {
167
70
  todayTokens: usage.today,
168
71
  totalTokens: usage.total,
169
- inputTokens: null,
170
- outputTokens: null,
72
+ inputTokens: usage.todayInput ?? null,
73
+ outputTokens: usage.todayOutput ?? null,
74
+ cacheReadTokens: usage.todayCacheRead ?? null,
75
+ cacheWriteTokens: usage.todayCacheWrite ?? null,
171
76
  };
172
77
  } catch {
173
78
  return null;
package/src/types.ts CHANGED
@@ -15,6 +15,7 @@ export interface Profile {
15
15
  env?: Record<string, EnvValue>;
16
16
  removeFiles?: string[];
17
17
  commands?: string[];
18
+ pricing?: ProfilePricing;
18
19
  }
19
20
 
20
21
  export interface CodexStatuslineConfig {
@@ -43,6 +44,24 @@ export interface Config {
43
44
  claudeSessionsPath?: string;
44
45
  codexStatusline?: CodexStatuslineConfig;
45
46
  claudeStatusline?: ClaudeStatuslineConfig;
47
+ pricing?: PricingConfig;
48
+ }
49
+
50
+ export interface TokenPricing {
51
+ input?: number;
52
+ output?: number;
53
+ cacheRead?: number;
54
+ cacheWrite?: number;
55
+ description?: string;
56
+ }
57
+
58
+ export interface PricingConfig {
59
+ models?: Record<string, TokenPricing>;
60
+ }
61
+
62
+ export interface ProfilePricing extends TokenPricing {
63
+ model?: string;
64
+ multiplier?: number;
46
65
  }
47
66
 
48
67
  export interface ListRow {
@@ -54,6 +73,10 @@ export interface ListRow {
54
73
  usageType?: ProfileType | null;
55
74
  todayTokens?: number;
56
75
  totalTokens?: number;
76
+ todayCost?: number;
77
+ totalCost?: number;
78
+ todayBilledTokens?: number;
79
+ totalBilledTokens?: number;
57
80
  }
58
81
 
59
82
  export interface ParsedArgs {