@praeviso/code-env-switch 0.1.4 → 0.1.6
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 +57 -0
- package/README_zh.md +57 -0
- package/bin/cli/args.js +13 -0
- package/bin/cli/help.js +5 -0
- package/bin/cli/index.js +2 -1
- package/bin/commands/index.js +3 -1
- package/bin/commands/list.js +44 -2
- package/bin/commands/usage.js +41 -0
- package/bin/index.js +7 -0
- package/bin/statusline/debug.js +1 -0
- package/bin/statusline/format.js +6 -9
- package/bin/statusline/index.js +46 -8
- package/bin/statusline/input.js +9 -91
- package/bin/statusline/usage/claude.js +181 -0
- package/bin/statusline/usage/codex.js +177 -0
- package/bin/statusline/usage.js +20 -76
- package/bin/usage/index.js +647 -50
- package/bin/usage/pricing.js +303 -0
- package/code-env.example.json +55 -0
- package/package.json +1 -1
- package/src/cli/args.ts +14 -0
- package/src/cli/help.ts +5 -0
- package/src/cli/index.ts +7 -1
- package/src/commands/index.ts +1 -0
- package/src/commands/list.ts +74 -4
- package/src/commands/usage.ts +53 -0
- package/src/index.ts +11 -0
- package/src/statusline/debug.ts +1 -1
- package/src/statusline/format.ts +9 -10
- package/src/statusline/index.ts +74 -24
- package/src/statusline/input.ts +13 -154
- package/src/statusline/types.ts +6 -0
- package/src/statusline/usage/claude.ts +299 -0
- package/src/statusline/usage/codex.ts +258 -0
- package/src/statusline/usage.ts +24 -119
- package/src/types.ts +27 -0
- package/src/usage/index.ts +779 -44
- package/src/usage/pricing.ts +323 -0
- package/PLAN.md +0 -33
package/src/statusline/format.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatTokenCount } from "../usage";
|
|
2
|
+
import { formatUsdAmount } from "../usage/pricing";
|
|
2
3
|
import {
|
|
3
4
|
ICON_CONTEXT,
|
|
4
5
|
ICON_CWD,
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
colorize,
|
|
10
11
|
dim,
|
|
11
12
|
} from "./style";
|
|
12
|
-
import type { StatuslineUsage } from "./types";
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
|
|
15
15
|
export function getCwdSegment(cwd: string): string | null {
|
|
@@ -19,15 +19,14 @@ export function getCwdSegment(cwd: string): string | null {
|
|
|
19
19
|
return dim(segment);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function formatUsageSegment(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const text = `Today ${formatTokenCount(today)}`;
|
|
22
|
+
export function formatUsageSegment(
|
|
23
|
+
todayCost: number | null,
|
|
24
|
+
sessionCost: number | null
|
|
25
|
+
): string | null {
|
|
26
|
+
if (todayCost === null && sessionCost === null) return null;
|
|
27
|
+
const todayText = `T ${formatUsdAmount(todayCost)}`;
|
|
28
|
+
const sessionText = `S ${formatUsdAmount(sessionCost)}`;
|
|
29
|
+
const text = `${todayText} / ${sessionText}`;
|
|
31
30
|
return colorize(`${ICON_USAGE} ${text}`, "33");
|
|
32
31
|
}
|
|
33
32
|
|
package/src/statusline/index.ts
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { Config, StatuslineArgs } from "../types";
|
|
5
5
|
import { normalizeType, inferProfileType, getProfileDisplayName } from "../profile/type";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
readUsageCostIndex,
|
|
8
|
+
readUsageSessionCost,
|
|
9
|
+
resolveUsageCostForProfile,
|
|
10
|
+
syncUsageFromStatuslineInput,
|
|
11
|
+
} from "../usage";
|
|
12
|
+
import { calculateUsageCost, resolvePricingForProfile } from "../usage/pricing";
|
|
7
13
|
import { appendStatuslineDebug } from "./debug";
|
|
8
14
|
import {
|
|
9
15
|
formatContextSegment,
|
|
@@ -91,8 +97,17 @@ export function buildStatuslineResult(
|
|
|
91
97
|
)!;
|
|
92
98
|
|
|
93
99
|
const sessionId = getSessionId(stdinInput);
|
|
94
|
-
const stdinUsageTotals = getUsageTotalsFromInput(stdinInput);
|
|
95
100
|
const usageType = normalizeType(type || "");
|
|
101
|
+
const stdinUsageTotals = getUsageTotalsFromInput(stdinInput, usageType);
|
|
102
|
+
const model = firstNonEmpty(
|
|
103
|
+
args.model,
|
|
104
|
+
process.env.CODE_ENV_MODEL,
|
|
105
|
+
getModelFromInput(stdinInput)
|
|
106
|
+
);
|
|
107
|
+
const modelProvider = firstNonEmpty(
|
|
108
|
+
process.env.CODE_ENV_MODEL_PROVIDER,
|
|
109
|
+
getModelProviderFromInput(stdinInput)
|
|
110
|
+
);
|
|
96
111
|
appendStatuslineDebug(configPath, {
|
|
97
112
|
timestamp: new Date().toISOString(),
|
|
98
113
|
typeCandidate,
|
|
@@ -130,20 +145,11 @@ export function buildStatuslineResult(
|
|
|
130
145
|
profileName,
|
|
131
146
|
sessionId,
|
|
132
147
|
stdinUsageTotals,
|
|
133
|
-
cwd
|
|
148
|
+
cwd,
|
|
149
|
+
model
|
|
134
150
|
);
|
|
135
151
|
}
|
|
136
152
|
|
|
137
|
-
const model = firstNonEmpty(
|
|
138
|
-
args.model,
|
|
139
|
-
process.env.CODE_ENV_MODEL,
|
|
140
|
-
getModelFromInput(stdinInput)
|
|
141
|
-
);
|
|
142
|
-
const modelProvider = firstNonEmpty(
|
|
143
|
-
process.env.CODE_ENV_MODEL_PROVIDER,
|
|
144
|
-
getModelProviderFromInput(stdinInput)
|
|
145
|
-
);
|
|
146
|
-
|
|
147
153
|
const usage: StatuslineUsage = {
|
|
148
154
|
todayTokens: firstNumber(
|
|
149
155
|
args.usageToday,
|
|
@@ -161,6 +167,8 @@ export function buildStatuslineResult(
|
|
|
161
167
|
args.usageOutput,
|
|
162
168
|
process.env.CODE_ENV_USAGE_OUTPUT
|
|
163
169
|
),
|
|
170
|
+
cacheReadTokens: null,
|
|
171
|
+
cacheWriteTokens: null,
|
|
164
172
|
};
|
|
165
173
|
|
|
166
174
|
const hasExplicitUsage =
|
|
@@ -169,21 +177,25 @@ export function buildStatuslineResult(
|
|
|
169
177
|
usage.inputTokens !== null ||
|
|
170
178
|
usage.outputTokens !== null;
|
|
171
179
|
|
|
172
|
-
const stdinUsage = normalizeInputUsage(getInputUsage(stdinInput));
|
|
180
|
+
const stdinUsage = normalizeInputUsage(getInputUsage(stdinInput, usageType));
|
|
181
|
+
const recordsUsage = resolveUsageFromRecords(
|
|
182
|
+
config,
|
|
183
|
+
configPath,
|
|
184
|
+
type,
|
|
185
|
+
profileKey,
|
|
186
|
+
profileName,
|
|
187
|
+
args.syncUsage
|
|
188
|
+
);
|
|
173
189
|
|
|
174
190
|
let finalUsage: StatuslineUsage | null = hasExplicitUsage ? usage : null;
|
|
191
|
+
if (!finalUsage && args.syncUsage && recordsUsage) {
|
|
192
|
+
finalUsage = recordsUsage;
|
|
193
|
+
}
|
|
175
194
|
if (!finalUsage) {
|
|
176
195
|
finalUsage = stdinUsage;
|
|
177
196
|
}
|
|
178
|
-
if (!finalUsage) {
|
|
179
|
-
finalUsage =
|
|
180
|
-
config,
|
|
181
|
-
configPath,
|
|
182
|
-
type,
|
|
183
|
-
profileKey,
|
|
184
|
-
profileName,
|
|
185
|
-
args.syncUsage
|
|
186
|
-
);
|
|
197
|
+
if (!finalUsage && recordsUsage) {
|
|
198
|
+
finalUsage = recordsUsage;
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
let gitStatus = getGitStatus(cwd);
|
|
@@ -198,7 +210,45 @@ export function buildStatuslineResult(
|
|
|
198
210
|
const gitSegment = formatGitSegment(gitStatus);
|
|
199
211
|
const profileSegment = formatProfileSegment(type, profileKey, profileName);
|
|
200
212
|
const modelSegment = formatModelSegment(model, modelProvider);
|
|
201
|
-
|
|
213
|
+
let profile = profileKey && config.profiles ? config.profiles[profileKey] : null;
|
|
214
|
+
if (!profile && profileName && config.profiles) {
|
|
215
|
+
const matches = Object.entries(config.profiles).find(([key, entry]) => {
|
|
216
|
+
const displayName = getProfileDisplayName(key, entry);
|
|
217
|
+
return (
|
|
218
|
+
displayName === profileName ||
|
|
219
|
+
entry.name === profileName ||
|
|
220
|
+
key === profileName
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
if (matches) profile = matches[1];
|
|
224
|
+
}
|
|
225
|
+
const sessionUsage = hasExplicitUsage ? usage : stdinUsage;
|
|
226
|
+
const pricing = resolvePricingForProfile(config, profile || null, model);
|
|
227
|
+
let sessionCost: number | null = null;
|
|
228
|
+
if (hasExplicitUsage) {
|
|
229
|
+
sessionCost = sessionUsage
|
|
230
|
+
? calculateUsageCost(sessionUsage, pricing)
|
|
231
|
+
: null;
|
|
232
|
+
} else {
|
|
233
|
+
const sessionCostFromRecords = sessionId
|
|
234
|
+
? readUsageSessionCost(
|
|
235
|
+
config,
|
|
236
|
+
configPath,
|
|
237
|
+
type,
|
|
238
|
+
sessionId,
|
|
239
|
+
args.syncUsage
|
|
240
|
+
)
|
|
241
|
+
: null;
|
|
242
|
+
sessionCost =
|
|
243
|
+
sessionCostFromRecords ??
|
|
244
|
+
(sessionUsage ? calculateUsageCost(sessionUsage, pricing) : null);
|
|
245
|
+
}
|
|
246
|
+
const costIndex = readUsageCostIndex(config, configPath, args.syncUsage);
|
|
247
|
+
const costTotals = costIndex
|
|
248
|
+
? resolveUsageCostForProfile(costIndex, type, profileKey, profileName)
|
|
249
|
+
: null;
|
|
250
|
+
const todayCost = costTotals ? costTotals.today : null;
|
|
251
|
+
const usageSegment = formatUsageSegment(todayCost, sessionCost);
|
|
202
252
|
const contextLeft = getContextLeftPercent(stdinInput, type);
|
|
203
253
|
const contextSegment = formatContextSegment(contextLeft);
|
|
204
254
|
const contextUsedTokens = getContextUsedTokens(stdinInput);
|
package/src/statusline/input.ts
CHANGED
|
@@ -7,7 +7,9 @@ import type {
|
|
|
7
7
|
StatuslineInputProfile,
|
|
8
8
|
StatuslineInputUsage,
|
|
9
9
|
} from "./types";
|
|
10
|
-
import { coerceNumber, firstNonEmpty,
|
|
10
|
+
import { coerceNumber, firstNonEmpty, isRecord } from "./utils";
|
|
11
|
+
import { getClaudeInputUsage } from "./usage/claude";
|
|
12
|
+
import { getCodexInputUsage } from "./usage/codex";
|
|
11
13
|
|
|
12
14
|
export function readStdinJson(): StatuslineInput | null {
|
|
13
15
|
if (process.stdin.isTTY) return null;
|
|
@@ -83,162 +85,19 @@ export function getInputProfile(
|
|
|
83
85
|
return input.profile as StatuslineInputProfile;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
export function getInputUsage(
|
|
88
|
+
export function getInputUsage(
|
|
89
|
+
input: StatuslineInput | null,
|
|
90
|
+
type: string | null
|
|
91
|
+
): StatuslineInputUsage | null {
|
|
87
92
|
if (!input) return null;
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const normalized = normalizeTypeValue(type);
|
|
94
|
+
if (normalized === "codex") {
|
|
95
|
+
return getCodexInputUsage(input);
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (typeof tokenUsage === "number") {
|
|
94
|
-
return {
|
|
95
|
-
todayTokens: null,
|
|
96
|
-
totalTokens: coerceNumber(tokenUsage),
|
|
97
|
-
inputTokens: null,
|
|
98
|
-
outputTokens: null,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (isRecord(tokenUsage)) {
|
|
102
|
-
const record = tokenUsage as Record<string, unknown>;
|
|
103
|
-
const todayTokens =
|
|
104
|
-
firstNumber(
|
|
105
|
-
record.todayTokens,
|
|
106
|
-
record.today,
|
|
107
|
-
record.today_tokens,
|
|
108
|
-
record.daily,
|
|
109
|
-
record.daily_tokens
|
|
110
|
-
) ?? null;
|
|
111
|
-
const totalTokens =
|
|
112
|
-
firstNumber(
|
|
113
|
-
record.totalTokens,
|
|
114
|
-
record.total,
|
|
115
|
-
record.total_tokens
|
|
116
|
-
) ?? null;
|
|
117
|
-
const inputTokens =
|
|
118
|
-
firstNumber(
|
|
119
|
-
record.inputTokens,
|
|
120
|
-
record.input,
|
|
121
|
-
record.input_tokens
|
|
122
|
-
) ?? null;
|
|
123
|
-
const outputTokens =
|
|
124
|
-
firstNumber(
|
|
125
|
-
record.outputTokens,
|
|
126
|
-
record.output,
|
|
127
|
-
record.output_tokens
|
|
128
|
-
) ?? null;
|
|
129
|
-
const cacheRead =
|
|
130
|
-
firstNumber(
|
|
131
|
-
record.cache_read_input_tokens,
|
|
132
|
-
record.cacheReadInputTokens,
|
|
133
|
-
record.cache_read,
|
|
134
|
-
record.cacheRead
|
|
135
|
-
) ?? null;
|
|
136
|
-
const cacheWrite =
|
|
137
|
-
firstNumber(
|
|
138
|
-
record.cache_creation_input_tokens,
|
|
139
|
-
record.cacheCreationInputTokens,
|
|
140
|
-
record.cache_write_input_tokens,
|
|
141
|
-
record.cacheWriteInputTokens,
|
|
142
|
-
record.cache_write,
|
|
143
|
-
record.cacheWrite
|
|
144
|
-
) ?? null;
|
|
145
|
-
if (
|
|
146
|
-
todayTokens === null &&
|
|
147
|
-
totalTokens === null &&
|
|
148
|
-
inputTokens === null &&
|
|
149
|
-
outputTokens === null &&
|
|
150
|
-
cacheRead === null &&
|
|
151
|
-
cacheWrite === null
|
|
152
|
-
) {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
|
|
156
|
-
const computedTotal = hasCacheTokens
|
|
157
|
-
? (inputTokens || 0) +
|
|
158
|
-
(outputTokens || 0) +
|
|
159
|
-
(cacheRead || 0) +
|
|
160
|
-
(cacheWrite || 0)
|
|
161
|
-
: null;
|
|
162
|
-
const resolvedTodayTokens = hasCacheTokens
|
|
163
|
-
? todayTokens ?? totalTokens ?? computedTotal
|
|
164
|
-
: todayTokens;
|
|
165
|
-
return {
|
|
166
|
-
todayTokens: resolvedTodayTokens,
|
|
167
|
-
totalTokens: totalTokens ?? null,
|
|
168
|
-
inputTokens,
|
|
169
|
-
outputTokens,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
97
|
+
if (normalized === "claude") {
|
|
98
|
+
return getClaudeInputUsage(input);
|
|
172
99
|
}
|
|
173
|
-
|
|
174
|
-
? (input.context_window as Record<string, unknown>)
|
|
175
|
-
: isRecord(input.contextWindow)
|
|
176
|
-
? (input.contextWindow as Record<string, unknown>)
|
|
177
|
-
: null;
|
|
178
|
-
if (!contextWindow) return null;
|
|
179
|
-
const totalInputTokens =
|
|
180
|
-
firstNumber(
|
|
181
|
-
contextWindow.total_input_tokens,
|
|
182
|
-
contextWindow.totalInputTokens
|
|
183
|
-
) ?? null;
|
|
184
|
-
const totalOutputTokens =
|
|
185
|
-
firstNumber(
|
|
186
|
-
contextWindow.total_output_tokens,
|
|
187
|
-
contextWindow.totalOutputTokens
|
|
188
|
-
) ?? null;
|
|
189
|
-
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
190
|
-
return {
|
|
191
|
-
todayTokens: null,
|
|
192
|
-
totalTokens: null,
|
|
193
|
-
inputTokens: totalInputTokens,
|
|
194
|
-
outputTokens: totalOutputTokens,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const currentUsage = isRecord(contextWindow.current_usage)
|
|
198
|
-
? (contextWindow.current_usage as Record<string, unknown>)
|
|
199
|
-
: isRecord(contextWindow.currentUsage)
|
|
200
|
-
? (contextWindow.currentUsage as Record<string, unknown>)
|
|
201
|
-
: null;
|
|
202
|
-
if (!currentUsage) return null;
|
|
203
|
-
const inputTokens =
|
|
204
|
-
firstNumber(
|
|
205
|
-
currentUsage.input_tokens,
|
|
206
|
-
currentUsage.inputTokens
|
|
207
|
-
) ?? null;
|
|
208
|
-
const outputTokens =
|
|
209
|
-
firstNumber(
|
|
210
|
-
currentUsage.output_tokens,
|
|
211
|
-
currentUsage.outputTokens
|
|
212
|
-
) ?? null;
|
|
213
|
-
const cacheRead =
|
|
214
|
-
firstNumber(
|
|
215
|
-
currentUsage.cache_read_input_tokens,
|
|
216
|
-
currentUsage.cacheReadInputTokens
|
|
217
|
-
) ?? null;
|
|
218
|
-
const cacheWrite =
|
|
219
|
-
firstNumber(
|
|
220
|
-
currentUsage.cache_creation_input_tokens,
|
|
221
|
-
currentUsage.cacheCreationInputTokens
|
|
222
|
-
) ?? null;
|
|
223
|
-
if (
|
|
224
|
-
inputTokens === null &&
|
|
225
|
-
outputTokens === null &&
|
|
226
|
-
cacheRead === null &&
|
|
227
|
-
cacheWrite === null
|
|
228
|
-
) {
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
const totalTokens =
|
|
232
|
-
(inputTokens || 0) +
|
|
233
|
-
(outputTokens || 0) +
|
|
234
|
-
(cacheRead || 0) +
|
|
235
|
-
(cacheWrite || 0);
|
|
236
|
-
return {
|
|
237
|
-
todayTokens: totalTokens,
|
|
238
|
-
totalTokens: null,
|
|
239
|
-
inputTokens,
|
|
240
|
-
outputTokens,
|
|
241
|
-
};
|
|
100
|
+
return getCodexInputUsage(input) || getClaudeInputUsage(input);
|
|
242
101
|
}
|
|
243
102
|
|
|
244
103
|
export function getSessionId(input: StatuslineInput | null): string | null {
|
package/src/statusline/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface StatuslineInputUsage {
|
|
|
9
9
|
totalTokens?: number;
|
|
10
10
|
inputTokens?: number;
|
|
11
11
|
outputTokens?: number;
|
|
12
|
+
cacheReadTokens?: number;
|
|
13
|
+
cacheWriteTokens?: number;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface StatuslineInputContextWindowUsage {
|
|
@@ -72,11 +74,15 @@ export interface StatuslineUsage {
|
|
|
72
74
|
totalTokens: number | null;
|
|
73
75
|
inputTokens: number | null;
|
|
74
76
|
outputTokens: number | null;
|
|
77
|
+
cacheReadTokens: number | null;
|
|
78
|
+
cacheWriteTokens: number | null;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export interface StatuslineUsageTotals {
|
|
78
82
|
inputTokens: number | null;
|
|
79
83
|
outputTokens: number | null;
|
|
84
|
+
cacheReadTokens: number | null;
|
|
85
|
+
cacheWriteTokens: number | null;
|
|
80
86
|
totalTokens: number | null;
|
|
81
87
|
}
|
|
82
88
|
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
StatuslineInput,
|
|
3
|
+
StatuslineInputUsage,
|
|
4
|
+
StatuslineUsageTotals,
|
|
5
|
+
} from "../types";
|
|
6
|
+
import { firstNumber, isRecord } from "../utils";
|
|
7
|
+
|
|
8
|
+
function resolveContextWindow(
|
|
9
|
+
input: StatuslineInput
|
|
10
|
+
): Record<string, unknown> | null {
|
|
11
|
+
if (isRecord(input.context_window)) {
|
|
12
|
+
return input.context_window as Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
if (isRecord(input.contextWindow)) {
|
|
15
|
+
return input.contextWindow as Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveCurrentUsage(
|
|
21
|
+
contextWindow: Record<string, unknown>
|
|
22
|
+
): Record<string, unknown> | null {
|
|
23
|
+
if (isRecord(contextWindow.current_usage)) {
|
|
24
|
+
return contextWindow.current_usage as Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
if (isRecord(contextWindow.currentUsage)) {
|
|
27
|
+
return contextWindow.currentUsage as Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseClaudeUsageTotalsRecord(
|
|
33
|
+
record: Record<string, unknown>
|
|
34
|
+
): StatuslineUsageTotals | null {
|
|
35
|
+
const inputTokens =
|
|
36
|
+
firstNumber(
|
|
37
|
+
record.inputTokens,
|
|
38
|
+
record.input,
|
|
39
|
+
record.input_tokens
|
|
40
|
+
) ?? null;
|
|
41
|
+
const outputTokens =
|
|
42
|
+
firstNumber(
|
|
43
|
+
record.outputTokens,
|
|
44
|
+
record.output,
|
|
45
|
+
record.output_tokens
|
|
46
|
+
) ?? null;
|
|
47
|
+
const cacheRead =
|
|
48
|
+
firstNumber(
|
|
49
|
+
record.cache_read_input_tokens,
|
|
50
|
+
record.cacheReadInputTokens,
|
|
51
|
+
record.cache_read,
|
|
52
|
+
record.cacheRead
|
|
53
|
+
) ?? null;
|
|
54
|
+
const cacheWrite =
|
|
55
|
+
firstNumber(
|
|
56
|
+
record.cache_creation_input_tokens,
|
|
57
|
+
record.cacheCreationInputTokens,
|
|
58
|
+
record.cache_write_input_tokens,
|
|
59
|
+
record.cacheWriteInputTokens,
|
|
60
|
+
record.cache_write,
|
|
61
|
+
record.cacheWrite
|
|
62
|
+
) ?? null;
|
|
63
|
+
const totalTokens =
|
|
64
|
+
firstNumber(
|
|
65
|
+
record.totalTokens,
|
|
66
|
+
record.total,
|
|
67
|
+
record.total_tokens
|
|
68
|
+
) ?? null;
|
|
69
|
+
let computedTotal: number | null = null;
|
|
70
|
+
if (
|
|
71
|
+
inputTokens !== null ||
|
|
72
|
+
outputTokens !== null ||
|
|
73
|
+
cacheRead !== null ||
|
|
74
|
+
cacheWrite !== null
|
|
75
|
+
) {
|
|
76
|
+
computedTotal =
|
|
77
|
+
(inputTokens || 0) +
|
|
78
|
+
(outputTokens || 0) +
|
|
79
|
+
(cacheRead || 0) +
|
|
80
|
+
(cacheWrite || 0);
|
|
81
|
+
}
|
|
82
|
+
const resolvedTotal = totalTokens ?? computedTotal;
|
|
83
|
+
if (
|
|
84
|
+
inputTokens === null &&
|
|
85
|
+
outputTokens === null &&
|
|
86
|
+
cacheRead === null &&
|
|
87
|
+
cacheWrite === null &&
|
|
88
|
+
resolvedTotal === null
|
|
89
|
+
) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
inputTokens,
|
|
94
|
+
outputTokens,
|
|
95
|
+
cacheReadTokens: cacheRead,
|
|
96
|
+
cacheWriteTokens: cacheWrite,
|
|
97
|
+
totalTokens: resolvedTotal,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseClaudeInputUsageRecord(
|
|
102
|
+
record: Record<string, unknown>
|
|
103
|
+
): StatuslineInputUsage | null {
|
|
104
|
+
const todayTokens =
|
|
105
|
+
firstNumber(
|
|
106
|
+
record.todayTokens,
|
|
107
|
+
record.today,
|
|
108
|
+
record.today_tokens,
|
|
109
|
+
record.daily,
|
|
110
|
+
record.daily_tokens
|
|
111
|
+
) ?? null;
|
|
112
|
+
const totalTokens =
|
|
113
|
+
firstNumber(
|
|
114
|
+
record.totalTokens,
|
|
115
|
+
record.total,
|
|
116
|
+
record.total_tokens
|
|
117
|
+
) ?? null;
|
|
118
|
+
const inputTokens =
|
|
119
|
+
firstNumber(
|
|
120
|
+
record.inputTokens,
|
|
121
|
+
record.input,
|
|
122
|
+
record.input_tokens
|
|
123
|
+
) ?? null;
|
|
124
|
+
const outputTokens =
|
|
125
|
+
firstNumber(
|
|
126
|
+
record.outputTokens,
|
|
127
|
+
record.output,
|
|
128
|
+
record.output_tokens
|
|
129
|
+
) ?? null;
|
|
130
|
+
const cacheRead =
|
|
131
|
+
firstNumber(
|
|
132
|
+
record.cache_read_input_tokens,
|
|
133
|
+
record.cacheReadInputTokens,
|
|
134
|
+
record.cache_read,
|
|
135
|
+
record.cacheRead
|
|
136
|
+
) ?? null;
|
|
137
|
+
const cacheWrite =
|
|
138
|
+
firstNumber(
|
|
139
|
+
record.cache_creation_input_tokens,
|
|
140
|
+
record.cacheCreationInputTokens,
|
|
141
|
+
record.cache_write_input_tokens,
|
|
142
|
+
record.cacheWriteInputTokens,
|
|
143
|
+
record.cache_write,
|
|
144
|
+
record.cacheWrite
|
|
145
|
+
) ?? null;
|
|
146
|
+
if (
|
|
147
|
+
todayTokens === null &&
|
|
148
|
+
totalTokens === null &&
|
|
149
|
+
inputTokens === null &&
|
|
150
|
+
outputTokens === null &&
|
|
151
|
+
cacheRead === null &&
|
|
152
|
+
cacheWrite === null
|
|
153
|
+
) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const hasCacheTokens = cacheRead !== null || cacheWrite !== null;
|
|
157
|
+
const computedTotal = hasCacheTokens
|
|
158
|
+
? (inputTokens || 0) +
|
|
159
|
+
(outputTokens || 0) +
|
|
160
|
+
(cacheRead || 0) +
|
|
161
|
+
(cacheWrite || 0)
|
|
162
|
+
: null;
|
|
163
|
+
const resolvedTodayTokens = hasCacheTokens
|
|
164
|
+
? todayTokens ?? totalTokens ?? computedTotal
|
|
165
|
+
: todayTokens;
|
|
166
|
+
return {
|
|
167
|
+
todayTokens: resolvedTodayTokens,
|
|
168
|
+
totalTokens: totalTokens ?? null,
|
|
169
|
+
inputTokens,
|
|
170
|
+
outputTokens,
|
|
171
|
+
cacheReadTokens: cacheRead,
|
|
172
|
+
cacheWriteTokens: cacheWrite,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseTotalsFromContextWindow(
|
|
177
|
+
contextWindow: Record<string, unknown>
|
|
178
|
+
): StatuslineUsageTotals | null {
|
|
179
|
+
const inputTokens =
|
|
180
|
+
firstNumber(
|
|
181
|
+
contextWindow.total_input_tokens,
|
|
182
|
+
contextWindow.totalInputTokens
|
|
183
|
+
) ?? null;
|
|
184
|
+
const outputTokens =
|
|
185
|
+
firstNumber(
|
|
186
|
+
contextWindow.total_output_tokens,
|
|
187
|
+
contextWindow.totalOutputTokens
|
|
188
|
+
) ?? null;
|
|
189
|
+
if (inputTokens === null && outputTokens === null) return null;
|
|
190
|
+
const currentUsage = resolveCurrentUsage(contextWindow);
|
|
191
|
+
const cacheRead =
|
|
192
|
+
currentUsage
|
|
193
|
+
? firstNumber(
|
|
194
|
+
currentUsage.cache_read_input_tokens,
|
|
195
|
+
currentUsage.cacheReadInputTokens
|
|
196
|
+
) ?? null
|
|
197
|
+
: null;
|
|
198
|
+
const cacheWrite =
|
|
199
|
+
currentUsage
|
|
200
|
+
? firstNumber(
|
|
201
|
+
currentUsage.cache_creation_input_tokens,
|
|
202
|
+
currentUsage.cacheCreationInputTokens
|
|
203
|
+
) ?? null
|
|
204
|
+
: null;
|
|
205
|
+
const totalTokens =
|
|
206
|
+
(inputTokens || 0) +
|
|
207
|
+
(outputTokens || 0) +
|
|
208
|
+
(cacheRead || 0) +
|
|
209
|
+
(cacheWrite || 0);
|
|
210
|
+
return {
|
|
211
|
+
inputTokens,
|
|
212
|
+
outputTokens,
|
|
213
|
+
cacheReadTokens: cacheRead,
|
|
214
|
+
cacheWriteTokens: cacheWrite,
|
|
215
|
+
totalTokens,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getClaudeUsageTotalsFromInput(
|
|
220
|
+
input: StatuslineInput | null
|
|
221
|
+
): StatuslineUsageTotals | null {
|
|
222
|
+
if (!input) return null;
|
|
223
|
+
const contextWindow = resolveContextWindow(input);
|
|
224
|
+
if (contextWindow) {
|
|
225
|
+
const totals = parseTotalsFromContextWindow(contextWindow);
|
|
226
|
+
if (totals) return totals;
|
|
227
|
+
}
|
|
228
|
+
if (isRecord(input.usage)) {
|
|
229
|
+
return parseClaudeUsageTotalsRecord(input.usage as Record<string, unknown>);
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function getClaudeInputUsage(
|
|
235
|
+
input: StatuslineInput | null
|
|
236
|
+
): StatuslineInputUsage | null {
|
|
237
|
+
if (!input) return null;
|
|
238
|
+
if (isRecord(input.usage)) {
|
|
239
|
+
const parsed = parseClaudeInputUsageRecord(input.usage as Record<string, unknown>);
|
|
240
|
+
if (parsed) return parsed;
|
|
241
|
+
return input.usage as StatuslineInputUsage;
|
|
242
|
+
}
|
|
243
|
+
const contextWindow = resolveContextWindow(input);
|
|
244
|
+
if (!contextWindow) return null;
|
|
245
|
+
const totals = parseTotalsFromContextWindow(contextWindow);
|
|
246
|
+
if (totals) {
|
|
247
|
+
return {
|
|
248
|
+
todayTokens: null,
|
|
249
|
+
totalTokens: null,
|
|
250
|
+
inputTokens: totals.inputTokens,
|
|
251
|
+
outputTokens: totals.outputTokens,
|
|
252
|
+
cacheReadTokens: totals.cacheReadTokens,
|
|
253
|
+
cacheWriteTokens: totals.cacheWriteTokens,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const currentUsage = resolveCurrentUsage(contextWindow);
|
|
257
|
+
if (!currentUsage) return null;
|
|
258
|
+
const inputTokens =
|
|
259
|
+
firstNumber(
|
|
260
|
+
currentUsage.input_tokens,
|
|
261
|
+
currentUsage.inputTokens
|
|
262
|
+
) ?? null;
|
|
263
|
+
const outputTokens =
|
|
264
|
+
firstNumber(
|
|
265
|
+
currentUsage.output_tokens,
|
|
266
|
+
currentUsage.outputTokens
|
|
267
|
+
) ?? null;
|
|
268
|
+
const cacheRead =
|
|
269
|
+
firstNumber(
|
|
270
|
+
currentUsage.cache_read_input_tokens,
|
|
271
|
+
currentUsage.cacheReadInputTokens
|
|
272
|
+
) ?? null;
|
|
273
|
+
const cacheWrite =
|
|
274
|
+
firstNumber(
|
|
275
|
+
currentUsage.cache_creation_input_tokens,
|
|
276
|
+
currentUsage.cacheCreationInputTokens
|
|
277
|
+
) ?? null;
|
|
278
|
+
if (
|
|
279
|
+
inputTokens === null &&
|
|
280
|
+
outputTokens === null &&
|
|
281
|
+
cacheRead === null &&
|
|
282
|
+
cacheWrite === null
|
|
283
|
+
) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const totalTokens =
|
|
287
|
+
(inputTokens || 0) +
|
|
288
|
+
(outputTokens || 0) +
|
|
289
|
+
(cacheRead || 0) +
|
|
290
|
+
(cacheWrite || 0);
|
|
291
|
+
return {
|
|
292
|
+
todayTokens: totalTokens,
|
|
293
|
+
totalTokens: null,
|
|
294
|
+
inputTokens,
|
|
295
|
+
outputTokens,
|
|
296
|
+
cacheReadTokens: cacheRead,
|
|
297
|
+
cacheWriteTokens: cacheWrite,
|
|
298
|
+
};
|
|
299
|
+
}
|