@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,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_MODEL_PRICING = void 0;
4
+ exports.resolvePricingForProfile = resolvePricingForProfile;
5
+ exports.calculateUsageCost = calculateUsageCost;
6
+ exports.formatUsdAmount = formatUsdAmount;
7
+ const TOKENS_PER_MILLION = 1000000;
8
+ exports.DEFAULT_MODEL_PRICING = {
9
+ "Claude Sonnet 4.5": {
10
+ input: 3.0,
11
+ output: 15.0,
12
+ cacheWrite: 3.75,
13
+ cacheRead: 0.3,
14
+ description: "Balanced performance and speed for daily use.",
15
+ },
16
+ "Sonnet 4.5": {
17
+ input: 3.0,
18
+ output: 15.0,
19
+ cacheWrite: 3.75,
20
+ cacheRead: 0.3,
21
+ description: "Balanced performance and speed for daily use.",
22
+ },
23
+ "Claude Opus 4.5": {
24
+ input: 5.0,
25
+ output: 25.0,
26
+ cacheWrite: 6.25,
27
+ cacheRead: 0.5,
28
+ description: "Most capable model for agents and coding.",
29
+ },
30
+ "Opus 4.5": {
31
+ input: 5.0,
32
+ output: 25.0,
33
+ cacheWrite: 6.25,
34
+ cacheRead: 0.5,
35
+ description: "Most capable model for agents and coding.",
36
+ },
37
+ "Claude Haiku 4.5": {
38
+ input: 1.0,
39
+ output: 5.0,
40
+ cacheWrite: 1.25,
41
+ cacheRead: 0.1,
42
+ description: "Fast responses for lightweight tasks.",
43
+ },
44
+ "Haiku 4.5": {
45
+ input: 1.0,
46
+ output: 5.0,
47
+ cacheWrite: 1.25,
48
+ cacheRead: 0.1,
49
+ description: "Fast responses for lightweight tasks.",
50
+ },
51
+ "claude-opus-4-5-20251101": {
52
+ input: 5.0,
53
+ output: 25.0,
54
+ cacheWrite: 6.25,
55
+ cacheRead: 0.5,
56
+ description: "Most capable model for agents and coding.",
57
+ },
58
+ "claude-sonnet-4-5-20251022": {
59
+ input: 3.0,
60
+ output: 15.0,
61
+ cacheWrite: 3.75,
62
+ cacheRead: 0.3,
63
+ description: "Balanced performance and speed for daily use.",
64
+ },
65
+ "claude-haiku-4-5-20251022": {
66
+ input: 1.0,
67
+ output: 5.0,
68
+ cacheWrite: 1.25,
69
+ cacheRead: 0.1,
70
+ description: "Fast responses for lightweight tasks.",
71
+ },
72
+ "gpt-5.1": {
73
+ input: 1.25,
74
+ output: 10.0,
75
+ cacheRead: 0.125,
76
+ description: "Base model for daily development work.",
77
+ },
78
+ "gpt-5.1-codex": {
79
+ input: 1.25,
80
+ output: 10.0,
81
+ cacheRead: 0.125,
82
+ description: "Code-focused model for programming workflows.",
83
+ },
84
+ "gpt-5.1-codex-max": {
85
+ input: 1.25,
86
+ output: 10.0,
87
+ cacheRead: 0.125,
88
+ description: "Flagship code model for complex projects.",
89
+ },
90
+ "gpt-5.2": {
91
+ input: 1.75,
92
+ output: 14.0,
93
+ cacheRead: 0.175,
94
+ description: "Latest flagship model with improved performance.",
95
+ },
96
+ "gpt-5.2-codex": {
97
+ input: 1.75,
98
+ output: 14.0,
99
+ cacheRead: 0.175,
100
+ description: "Latest flagship code model.",
101
+ },
102
+ };
103
+ function normalizeModelKey(value) {
104
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
105
+ }
106
+ function parsePriceValue(value) {
107
+ if (value === null || value === undefined)
108
+ return null;
109
+ if (typeof value === "number" && Number.isFinite(value))
110
+ return value;
111
+ if (typeof value === "string") {
112
+ const match = value.replace(/,/g, "").match(/-?\d+(?:\.\d+)?/);
113
+ if (match) {
114
+ const parsed = Number(match[0]);
115
+ return Number.isFinite(parsed) ? parsed : null;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ function compactPricing(value) {
121
+ if (!value || typeof value !== "object")
122
+ return null;
123
+ const input = parsePriceValue(value.input);
124
+ const output = parsePriceValue(value.output);
125
+ const cacheRead = parsePriceValue(value.cacheRead);
126
+ const cacheWrite = parsePriceValue(value.cacheWrite);
127
+ const description = typeof value.description === "string" && value.description.trim()
128
+ ? value.description.trim()
129
+ : undefined;
130
+ const pricing = {};
131
+ let hasNumber = false;
132
+ if (input !== null) {
133
+ pricing.input = input;
134
+ hasNumber = true;
135
+ }
136
+ if (output !== null) {
137
+ pricing.output = output;
138
+ hasNumber = true;
139
+ }
140
+ if (cacheRead !== null) {
141
+ pricing.cacheRead = cacheRead;
142
+ hasNumber = true;
143
+ }
144
+ if (cacheWrite !== null) {
145
+ pricing.cacheWrite = cacheWrite;
146
+ hasNumber = true;
147
+ }
148
+ if (description)
149
+ pricing.description = description;
150
+ return hasNumber ? pricing : null;
151
+ }
152
+ function resolveMultiplier(value) {
153
+ const parsed = parsePriceValue(value);
154
+ if (parsed === null)
155
+ return null;
156
+ if (parsed < 0)
157
+ return null;
158
+ return parsed;
159
+ }
160
+ function applyMultiplier(pricing, multiplier) {
161
+ if (!pricing)
162
+ return null;
163
+ if (multiplier === null)
164
+ return pricing;
165
+ const scaled = {};
166
+ if (pricing.input !== undefined)
167
+ scaled.input = pricing.input * multiplier;
168
+ if (pricing.output !== undefined)
169
+ scaled.output = pricing.output * multiplier;
170
+ if (pricing.cacheRead !== undefined)
171
+ scaled.cacheRead = pricing.cacheRead * multiplier;
172
+ if (pricing.cacheWrite !== undefined)
173
+ scaled.cacheWrite = pricing.cacheWrite * multiplier;
174
+ if (pricing.description)
175
+ scaled.description = pricing.description;
176
+ return scaled;
177
+ }
178
+ function buildModelIndex(models) {
179
+ const index = new Map();
180
+ if (!models)
181
+ return index;
182
+ for (const [model, pricing] of Object.entries(models)) {
183
+ const key = normalizeModelKey(model);
184
+ if (!key)
185
+ continue;
186
+ const cleaned = compactPricing(pricing);
187
+ if (!cleaned)
188
+ continue;
189
+ index.set(key, { model, pricing: cleaned });
190
+ }
191
+ return index;
192
+ }
193
+ function resolveModelPricing(config, model) {
194
+ var _a;
195
+ if (!model)
196
+ return null;
197
+ const key = normalizeModelKey(model);
198
+ if (!key)
199
+ return null;
200
+ const configIndex = buildModelIndex((_a = config.pricing) === null || _a === void 0 ? void 0 : _a.models);
201
+ const fromConfig = configIndex.get(key);
202
+ if (fromConfig)
203
+ return fromConfig;
204
+ const defaultsIndex = buildModelIndex(exports.DEFAULT_MODEL_PRICING);
205
+ return defaultsIndex.get(key) || null;
206
+ }
207
+ function mergePricing(base, override) {
208
+ if (!base && !override)
209
+ return null;
210
+ return compactPricing({ ...(base || {}), ...(override || {}) });
211
+ }
212
+ function getProfilePricing(profile) {
213
+ var _a;
214
+ if (!profile || !profile.pricing || typeof profile.pricing !== "object") {
215
+ return { model: null, pricing: null, multiplier: null };
216
+ }
217
+ const raw = profile.pricing;
218
+ const model = typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : null;
219
+ return {
220
+ model,
221
+ pricing: compactPricing(raw),
222
+ multiplier: (_a = raw.multiplier) !== null && _a !== void 0 ? _a : null,
223
+ };
224
+ }
225
+ function resolvePricingForProfile(config, profile, model) {
226
+ var _a, _b;
227
+ const profilePricing = getProfilePricing(profile);
228
+ const baseFromProfileModel = profilePricing.model
229
+ ? resolveModelPricing(config, profilePricing.model)
230
+ : null;
231
+ const mergedProfile = mergePricing(baseFromProfileModel ? baseFromProfileModel.pricing : null, profilePricing.pricing);
232
+ const resolvedPricing = mergedProfile ||
233
+ (baseFromProfileModel ? baseFromProfileModel.pricing : null) ||
234
+ ((_b = (_a = resolveModelPricing(config, model)) === null || _a === void 0 ? void 0 : _a.pricing) !== null && _b !== void 0 ? _b : null);
235
+ if (!resolvedPricing)
236
+ return null;
237
+ const multiplier = resolveMultiplier(profilePricing.multiplier);
238
+ return applyMultiplier(resolvedPricing, multiplier);
239
+ }
240
+ function toFiniteNumber(value) {
241
+ if (value === null || value === undefined)
242
+ return null;
243
+ const num = Number(value);
244
+ if (!Number.isFinite(num))
245
+ return null;
246
+ return num;
247
+ }
248
+ function calculateUsageCost(usage, pricing) {
249
+ var _a, _b, _c, _d, _e, _f;
250
+ if (!usage || !pricing)
251
+ return null;
252
+ const inputTokens = toFiniteNumber(usage.inputTokens);
253
+ const outputTokens = toFiniteNumber(usage.outputTokens);
254
+ const cacheReadTokens = toFiniteNumber(usage.cacheReadTokens);
255
+ const cacheWriteTokens = toFiniteNumber(usage.cacheWriteTokens);
256
+ if (inputTokens === null &&
257
+ outputTokens === null &&
258
+ cacheReadTokens === null &&
259
+ cacheWriteTokens === null) {
260
+ return null;
261
+ }
262
+ const tokens = {
263
+ input: Math.max(0, inputTokens || 0),
264
+ output: Math.max(0, outputTokens || 0),
265
+ cacheRead: Math.max(0, cacheReadTokens || 0),
266
+ cacheWrite: Math.max(0, cacheWriteTokens || 0),
267
+ };
268
+ const knownTotal = toFiniteNumber((_b = (_a = usage.todayTokens) !== null && _a !== void 0 ? _a : usage.totalTokens) !== null && _b !== void 0 ? _b : null);
269
+ const breakdownTotal = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite;
270
+ if (breakdownTotal === 0 && knownTotal !== null && knownTotal > 0) {
271
+ return null;
272
+ }
273
+ if (tokens.input > 0 && pricing.input === undefined)
274
+ return null;
275
+ if (tokens.output > 0 && pricing.output === undefined)
276
+ return null;
277
+ if (tokens.cacheRead > 0 && pricing.cacheRead === undefined)
278
+ return null;
279
+ if (tokens.cacheWrite > 0 && pricing.cacheWrite === undefined)
280
+ return null;
281
+ const total = (tokens.input * ((_c = pricing.input) !== null && _c !== void 0 ? _c : 0) +
282
+ tokens.output * ((_d = pricing.output) !== null && _d !== void 0 ? _d : 0) +
283
+ tokens.cacheRead * ((_e = pricing.cacheRead) !== null && _e !== void 0 ? _e : 0) +
284
+ tokens.cacheWrite * ((_f = pricing.cacheWrite) !== null && _f !== void 0 ? _f : 0)) /
285
+ TOKENS_PER_MILLION;
286
+ return Number.isFinite(total) ? total : null;
287
+ }
288
+ function formatUsdAmount(amount) {
289
+ if (amount === null || !Number.isFinite(amount))
290
+ return "-";
291
+ const normalized = Math.abs(amount) < 1e-12 ? 0 : amount;
292
+ const abs = Math.abs(normalized);
293
+ let decimals = 2;
294
+ if (abs < 1)
295
+ decimals = 4;
296
+ if (abs < 0.1)
297
+ decimals = 5;
298
+ if (abs < 0.01)
299
+ decimals = 6;
300
+ let text = normalized.toFixed(decimals);
301
+ text = text.replace(/\.?0+$/, "");
302
+ return `$${text}`;
303
+ }
@@ -15,6 +15,61 @@
15
15
  "type": "command",
16
16
  "padding": 0
17
17
  },
18
+ "pricing": {
19
+ "models": {
20
+ "Claude Sonnet 4.5": {
21
+ "input": 3.0,
22
+ "output": 15.0,
23
+ "cacheWrite": 3.75,
24
+ "cacheRead": 0.3,
25
+ "description": "Balanced performance and speed for daily use."
26
+ },
27
+ "Claude Opus 4.5": {
28
+ "input": 5.0,
29
+ "output": 25.0,
30
+ "cacheWrite": 6.25,
31
+ "cacheRead": 0.5,
32
+ "description": "Most capable model for agents and coding."
33
+ },
34
+ "Claude Haiku 4.5": {
35
+ "input": 1.0,
36
+ "output": 5.0,
37
+ "cacheWrite": 1.25,
38
+ "cacheRead": 0.1,
39
+ "description": "Fast responses for lightweight tasks."
40
+ },
41
+ "gpt-5.1": {
42
+ "input": 1.25,
43
+ "output": 10.0,
44
+ "cacheRead": 0.125,
45
+ "description": "Base model for daily development work."
46
+ },
47
+ "gpt-5.1-codex": {
48
+ "input": 1.25,
49
+ "output": 10.0,
50
+ "cacheRead": 0.125,
51
+ "description": "Code-focused model for programming workflows."
52
+ },
53
+ "gpt-5.1-codex-max": {
54
+ "input": 1.25,
55
+ "output": 10.0,
56
+ "cacheRead": 0.125,
57
+ "description": "Flagship code model for complex projects."
58
+ },
59
+ "gpt-5.2": {
60
+ "input": 1.75,
61
+ "output": 14.0,
62
+ "cacheRead": 0.175,
63
+ "description": "Latest flagship model with improved performance."
64
+ },
65
+ "gpt-5.2-codex": {
66
+ "input": 1.75,
67
+ "output": 14.0,
68
+ "cacheRead": 0.175,
69
+ "description": "Latest flagship code model."
70
+ }
71
+ }
72
+ },
18
73
  "profiles": {
19
74
  "p_a1b2c3": {
20
75
  "name": "primary",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praeviso/code-env-switch",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Switch between Claude Code and Codex environment variables from a single CLI",
5
5
  "bin": {
6
6
  "codenv": "bin/index.js"
@@ -6,9 +6,12 @@ import { buildListRows } from "../profile/display";
6
6
  import { getResolvedDefaultProfileKeys } from "../config/defaults";
7
7
  import {
8
8
  formatTokenCount,
9
+ readUsageCostIndex,
9
10
  readUsageTotalsIndex,
11
+ resolveUsageCostForProfile,
10
12
  resolveUsageTotalsForProfile,
11
13
  } from "../usage";
14
+ import { formatUsdAmount } from "../usage/pricing";
12
15
 
13
16
  export function printList(config: Config, configPath: string | null): void {
14
17
  const rows = buildListRows(config, getResolvedDefaultProfileKeys);
@@ -18,6 +21,7 @@ export function printList(config: Config, configPath: string | null): void {
18
21
  }
19
22
  try {
20
23
  const usageTotals = readUsageTotalsIndex(config, configPath, true);
24
+ const usageCosts = readUsageCostIndex(config, configPath, false);
21
25
  if (usageTotals) {
22
26
  for (const row of rows) {
23
27
  if (!row.usageType) continue;
@@ -32,6 +36,22 @@ export function printList(config: Config, configPath: string | null): void {
32
36
  row.totalTokens = usage.total;
33
37
  }
34
38
  }
39
+ if (usageCosts) {
40
+ for (const row of rows) {
41
+ if (!row.usageType) continue;
42
+ const cost = resolveUsageCostForProfile(
43
+ usageCosts,
44
+ row.usageType,
45
+ row.key,
46
+ row.name
47
+ );
48
+ if (!cost) continue;
49
+ row.todayCost = cost.today;
50
+ row.totalCost = cost.total;
51
+ row.todayBilledTokens = cost.todayTokens;
52
+ row.totalBilledTokens = cost.totalTokens;
53
+ }
54
+ }
35
55
  } catch {
36
56
  // ignore usage sync errors
37
57
  }
@@ -40,8 +60,20 @@ export function printList(config: Config, configPath: string | null): void {
40
60
  const headerToday = "TODAY";
41
61
  const headerTotal = "TOTAL";
42
62
  const headerNote = "NOTE";
43
- const todayTexts = rows.map((row) => formatTokenCount(row.todayTokens));
44
- const totalTexts = rows.map((row) => formatTokenCount(row.totalTokens));
63
+ const todayTexts = rows.map((row) =>
64
+ formatUsageWithCost(
65
+ row.todayTokens,
66
+ row.todayBilledTokens ?? null,
67
+ row.todayCost ?? null
68
+ )
69
+ );
70
+ const totalTexts = rows.map((row) =>
71
+ formatUsageWithCost(
72
+ row.totalTokens,
73
+ row.totalBilledTokens ?? null,
74
+ row.totalCost ?? null
75
+ )
76
+ );
45
77
  const nameWidth = Math.max(
46
78
  headerName.length,
47
79
  ...rows.map((row) => row.name.length)
@@ -67,7 +99,15 @@ export function printList(config: Config, configPath: string | null): void {
67
99
  todayWidth
68
100
  )} ${total.padStart(totalWidth)} ${note.padEnd(noteWidth)}`;
69
101
 
70
- console.log(formatRow(headerName, headerType, headerToday, headerTotal, headerNote));
102
+ console.log(
103
+ formatRow(
104
+ headerName,
105
+ headerType,
106
+ headerToday,
107
+ headerTotal,
108
+ headerNote
109
+ )
110
+ );
71
111
  console.log(
72
112
  formatRow(
73
113
  "-".repeat(nameWidth),
@@ -81,7 +121,13 @@ export function printList(config: Config, configPath: string | null): void {
81
121
  const row = rows[i];
82
122
  const todayText = todayTexts[i] || "-";
83
123
  const totalText = totalTexts[i] || "-";
84
- const line = formatRow(row.name, row.type, todayText, totalText, row.note);
124
+ const line = formatRow(
125
+ row.name,
126
+ row.type,
127
+ todayText,
128
+ totalText,
129
+ row.note
130
+ );
85
131
  if (row.active) {
86
132
  console.log(`\x1b[32m${line}\x1b[0m`);
87
133
  } else {
@@ -89,3 +135,27 @@ export function printList(config: Config, configPath: string | null): void {
89
135
  }
90
136
  }
91
137
  }
138
+
139
+ function formatUsageWithCost(
140
+ tokens: number | null | undefined,
141
+ billedTokens: number | null,
142
+ cost: number | null
143
+ ): string {
144
+ const tokenText = formatTokenCount(tokens ?? null);
145
+ if (tokenText === "-") return tokenText;
146
+ if (cost === null || !Number.isFinite(cost)) return tokenText;
147
+ if (tokens === null || tokens === undefined || !Number.isFinite(tokens)) {
148
+ return tokenText;
149
+ }
150
+ if (billedTokens === null || !Number.isFinite(billedTokens)) {
151
+ return `${tokenText} (${formatUsdAmount(cost)})`;
152
+ }
153
+ if (billedTokens >= tokens) {
154
+ return `${tokenText} (${formatUsdAmount(cost)})`;
155
+ }
156
+ const billedText = formatTokenCount(billedTokens);
157
+ if (billedText === "-") {
158
+ return `${tokenText} (${formatUsdAmount(cost)})`;
159
+ }
160
+ return `${tokenText} (billed ${billedText}, ${formatUsdAmount(cost)})`;
161
+ }
@@ -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(usage: StatuslineUsage | null): string | null {
23
- if (!usage) return null;
24
- const today =
25
- usage.todayTokens ??
26
- (usage.inputTokens !== null || usage.outputTokens !== null
27
- ? (usage.inputTokens || 0) + (usage.outputTokens || 0)
28
- : usage.totalTokens);
29
- if (today === null) return null;
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
 
@@ -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 { syncUsageFromStatuslineInput } from "../usage";
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 = resolveUsageFromRecords(
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
- const usageSegment = formatUsageSegment(finalUsage);
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);