@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 CHANGED
@@ -80,6 +80,46 @@ codenv remove codex primary
80
80
  `codenv list` (or `codenv ls`) prints a table with `PROFILE`, `TYPE`, and `NOTE`. Default profiles are labeled in the `NOTE` column, and the active profile is shown in green.
81
81
  If `profile.name` is set, it is shown in `PROFILE`. Otherwise the profile key is shown (with legacy `type-` prefixes stripped when possible).
82
82
 
83
+ ### Reset usage history
84
+
85
+ ```bash
86
+ codenv usage-reset
87
+ # skip confirmation
88
+ codenv usage-reset --yes
89
+ ```
90
+
91
+ This deletes usage history files (`usage.jsonl`, usage state, `profile-log.jsonl`, `statusline-debug.jsonl`) plus any backup variants in the config directory.
92
+
93
+ ### Usage tracking
94
+
95
+ Usage stats are derived from two sources (statusline input + session logs) and appended to `usage.jsonl`.
96
+
97
+ - Files and paths
98
+ - `usage.jsonl`: JSONL records with `ts`, `type`, `profileKey`/`profileName`, `model`, `sessionId`, and token breakdowns.
99
+ - `usage.jsonl.state.json`: per-session totals + per-session-file metadata (mtime/size) used to compute deltas and avoid double counting.
100
+ - `profile-log.jsonl`: profile usage + session binding log (`use` and `session` events).
101
+ - `statusline-debug.jsonl`: optional debug capture when `CODE_ENV_STATUSLINE_DEBUG` is enabled.
102
+ - Paths can be overridden via `usagePath`, `usageStatePath`, `profileLogPath`, `codexSessionsPath`, `claudeSessionsPath`.
103
+ - Session binding (profile -> session)
104
+ - `codenv init` installs a shell wrapper so `codex`/`claude` run via `codenv launch`.
105
+ - `codenv launch` logs profile usage and then finds the latest unbound session file (prefers matching `cwd`, within a small grace window) and records the binding in `profile-log.jsonl`.
106
+ - Sync uses these bindings to attribute session usage to a profile.
107
+ - Statusline sync (`codenv statusline --sync-usage`)
108
+ - Requires `sessionId`, model, and a profile (`profileKey` or `profileName`) to write usage.
109
+ - Reads stdin JSON for totals and computes a delta against the last stored totals in the state file.
110
+ - If totals decrease (session reset), the current totals are treated as fresh usage; otherwise negative sub-deltas are clamped to zero.
111
+ - If the token breakdown exceeds the reported total, the breakdown sum wins.
112
+ - Appends a delta record to `usage.jsonl` and updates the per-session totals in the state file.
113
+ - Session log sync (`--sync-usage` and `codenv list`)
114
+ - Scans Codex sessions under `CODEX_HOME/sessions` (or `~/.codex/sessions`) and Claude sessions under `CLAUDE_HOME/projects` (or `~/.claude/projects`).
115
+ - Codex: parses `event_msg` token_count records and uses max totals; cached input is recorded as cache read and subtracted from input when possible.
116
+ - Claude: sums `message.usage` input/output/cache tokens across the file.
117
+ - Deltas are computed against prior file metadata and per-session maxima in the state file; files without a resolved binding are skipped.
118
+ - Daily totals
119
+ - "Today" is computed in local time between 00:00 and the next 00:00.
120
+ - Cost calculation
121
+ - Uses pricing from the profile (or `pricing.models`, plus defaults) and requires token splits; if splits are missing, cost is omitted.
122
+
83
123
  ### Add / update a profile
84
124
 
85
125
  ```bash
@@ -217,6 +257,17 @@ If nothing is found, `codenv add` writes to `~/.config/code-env/config.json`.
217
257
  "type": "command",
218
258
  "padding": 0
219
259
  },
260
+ "pricing": {
261
+ "models": {
262
+ "Claude Sonnet 4.5": {
263
+ "input": 3.0,
264
+ "output": 15.0,
265
+ "cacheWrite": 3.75,
266
+ "cacheRead": 0.3,
267
+ "description": "Balanced performance and speed for daily use."
268
+ }
269
+ }
270
+ },
220
271
  "profiles": {
221
272
  "p_a1b2c3": {
222
273
  "name": "primary",
@@ -247,10 +298,16 @@ Notes:
247
298
  - `type`: string; statusLine type (default: `command`).
248
299
  - `padding`: number; statusLine padding (default: 0).
249
300
  - `settingsPath`: optional; override `~/.claude/settings.json` (also supports `CODE_ENV_CLAUDE_SETTINGS_PATH`).
301
+ - `pricing`: optional; model pricing (USD per 1M tokens) used to convert token usage to dollar amounts in the status line.
302
+ - `models`: map of model name to pricing. Keys are matched case/format-insensitively.
303
+ - `input`/`output`/`cacheRead`/`cacheWrite`: token rates.
304
+ - Cost display uses the profile pricing model if set; otherwise the status line model label. No breakdown => no cost.
250
305
  - `name`: human-facing profile name shown in `codenv list` and used by `codenv use <name>`.
251
306
  - `type`: optional; `codex` or `claude` (alias `cc`) for `codenv use <type> <name>` matching.
252
307
  - `note`: shown in `codenv list`.
253
308
  - `removeFiles`: optional; `codenv use` emits `rm -f` for each path. Codex profiles also remove `~/.codex/auth.json`.
309
+ - `pricing` (profile): optional; per-profile pricing override. Supports `model` plus `input`/`output`/`cacheRead`/`cacheWrite`.
310
+ - `multiplier`: optional; scale pricing (number).
254
311
  - `ANTHROPIC_AUTH_TOKEN`: when `ANTHROPIC_API_KEY` is set, `codenv use` also exports `ANTHROPIC_AUTH_TOKEN` with the same value.
255
312
  - `commands`: optional; emitted as-is in the switch script.
256
313
 
package/README_zh.md CHANGED
@@ -80,6 +80,46 @@ codenv remove codex primary
80
80
  `codenv list`(或 `codenv ls`)会输出 `PROFILE` / `TYPE` / `NOTE` 的表格。默认项会标注在 `NOTE` 列,当前激活的配置会用绿色显示。
81
81
  如果设置了 `profile.name`,`PROFILE` 列会显示该名称;否则显示 profile 的 key(会尽量去掉旧的 `type-` 前缀)。
82
82
 
83
+ ### 清理用量历史
84
+
85
+ ```bash
86
+ codenv usage-reset
87
+ # 跳过确认
88
+ codenv usage-reset --yes
89
+ ```
90
+
91
+ 该命令会删除用量历史文件(`usage.jsonl`、用量 state、`profile-log.jsonl`、`statusline-debug.jsonl`)以及配置目录中的相关备份文件。
92
+
93
+ ### 用量统计逻辑
94
+
95
+ 用量来自两条路径(状态栏输入同步 + 会话日志解析),最终追加到 `usage.jsonl`。
96
+
97
+ - 文件与路径
98
+ - `usage.jsonl`:JSONL 记录,包含 `ts`/`type`/`profileKey`/`profileName`/`model`/`sessionId` 和 token 拆分字段。
99
+ - `usage.jsonl.state.json`:保存每个 session 的累计 totals + session 文件的 mtime/size,用于计算增量并避免重复统计。
100
+ - `profile-log.jsonl`:profile 使用与 session 绑定日志(`use`/`session`)。
101
+ - `statusline-debug.jsonl`:当 `CODE_ENV_STATUSLINE_DEBUG` 开启时写入的调试信息。
102
+ - 可通过 `usagePath`/`usageStatePath`/`profileLogPath`/`codexSessionsPath`/`claudeSessionsPath` 覆盖默认路径。
103
+ - 会话绑定(profile -> session)
104
+ - `codenv init` 安装 shell 包装函数,使 `codex`/`claude` 实际走 `codenv launch`。
105
+ - `codenv launch` 记录 profile 使用,并在启动后短时间内(默认约 5 秒、每秒轮询)找到最新未绑定的 session 文件(优先 `cwd` 匹配),写入 `profile-log.jsonl`。
106
+ - 后续同步会用该绑定将 session 归因到对应 profile。
107
+ - 状态栏同步(`codenv statusline --sync-usage`)
108
+ - 需要 `sessionId`、model,以及 profile(`profileKey` 或 `profileName`)才会写入用量。
109
+ - 从 stdin JSON 读取 totals,与 state 中的上次 totals 做差得到增量。
110
+ - 如果 totals 回退(session reset),直接把当前 totals 当作新增;否则对负数子项做 0 处理。
111
+ - 当拆分合计大于 total 时,以拆分合计为准。
112
+ - 把增量写入 `usage.jsonl`,并更新 state 中的 session totals。
113
+ - 会话日志同步(`--sync-usage` 与 `codenv list`)
114
+ - 扫描 Codex 的 `CODEX_HOME/sessions`(或 `~/.codex/sessions`)与 Claude 的 `CLAUDE_HOME/projects`(或 `~/.claude/projects`)。
115
+ - Codex:读取 `event_msg` 的 token_count 记录,取累计最大值;cached input 计为 cache read,并在可判断时从 input 中扣除。
116
+ - Claude:汇总 `message.usage` 中的 input/output/cache tokens。
117
+ - 与 state 中的文件元数据和 session 最大值做差,生成增量并写入 `usage.jsonl`;无法解析到绑定的文件会被跳过。
118
+ - 今日统计
119
+ - “今日”按本地时区 00:00 到次日 00:00 计算。
120
+ - 费用换算
121
+ - 使用 profile 定价或 `pricing.models`(含默认值)换算,依赖 input/output/cache 拆分;缺少拆分则不显示金额。
122
+
83
123
  ### 添加 / 更新 profile
84
124
 
85
125
  ```bash
@@ -216,6 +256,17 @@ codenv use codex primary | source
216
256
  "type": "command",
217
257
  "padding": 0
218
258
  },
259
+ "pricing": {
260
+ "models": {
261
+ "Claude Sonnet 4.5": {
262
+ "input": 3.0,
263
+ "output": 15.0,
264
+ "cacheWrite": 3.75,
265
+ "cacheRead": 0.3,
266
+ "description": "平衡性能与速度,适合日常使用"
267
+ }
268
+ }
269
+ },
219
270
  "profiles": {
220
271
  "p_a1b2c3": {
221
272
  "name": "primary",
@@ -246,10 +297,16 @@ codenv use codex primary | source
246
297
  - `type`:字符串;statusLine 类型(默认 `command`)。
247
298
  - `padding`:数字;statusLine padding(默认 0)。
248
299
  - `settingsPath`:可选;覆盖 `~/.claude/settings.json`(也可用 `CODE_ENV_CLAUDE_SETTINGS_PATH`)。
300
+ - `pricing`:可选;模型价格(美元 / 1M tokens),用于在状态栏将 token 用量换算为美元额度。
301
+ - `models`:模型名称到价格的映射(匹配时忽略大小写与格式差异)。
302
+ - `input`/`output`/`cacheRead`/`cacheWrite`:输入/输出/缓存价格。
303
+ - 优先使用 profile 中指定的 `model`,否则使用状态栏输入的模型名称;无拆分则不显示金额。
249
304
  - `name`:用于展示的 profile 名称,`codenv list` 与 `codenv use <name>` 会使用它。
250
305
  - `type`:可选,`codex` 或 `claude`(别名 `cc`),便于用 `codenv use <type> <name>` 匹配。
251
306
  - `note`:显示在 `codenv list` 输出中。
252
307
  - `removeFiles`:可选;`codenv use` 会输出对应 `rm -f`。Codex profile 还会删除 `~/.codex/auth.json`。
308
+ - `pricing`(profile 内):可选;为单个 profile 覆盖价格。支持 `model` 以及 `input`/`output`/`cacheRead`/`cacheWrite`。
309
+ - `multiplier`:可选;倍率(数字)。
253
310
  - `ANTHROPIC_AUTH_TOKEN`:当设置了 `ANTHROPIC_API_KEY` 时,`codenv use` 会自动以同样的值导出 `ANTHROPIC_AUTH_TOKEN`。
254
311
  - `commands`:可选;原样输出到切换脚本中。
255
312
 
package/bin/cli/args.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseArgs = parseArgs;
4
4
  exports.parseInitArgs = parseInitArgs;
5
5
  exports.parseAddArgs = parseAddArgs;
6
+ exports.parseUsageResetArgs = parseUsageResetArgs;
6
7
  exports.parseStatuslineArgs = parseStatuslineArgs;
7
8
  const type_1 = require("../profile/type");
8
9
  function parseArgs(argv) {
@@ -150,6 +151,18 @@ function parseAddArgs(args) {
150
151
  }
151
152
  return result;
152
153
  }
154
+ function parseUsageResetArgs(args) {
155
+ const result = { yes: false };
156
+ for (let i = 0; i < args.length; i++) {
157
+ const arg = args[i];
158
+ if (arg === "-y" || arg === "--yes") {
159
+ result.yes = true;
160
+ continue;
161
+ }
162
+ throw new Error(`Unknown usage-reset argument: ${arg}`);
163
+ }
164
+ return result;
165
+ }
153
166
  function parseNumberFlag(value, flag) {
154
167
  if (value === null || value === undefined || value === "") {
155
168
  throw new Error(`Missing value for ${flag}.`);
package/bin/cli/help.js CHANGED
@@ -29,6 +29,7 @@ Usage:
29
29
  codenv launch <codex|claude> [--] [args...]
30
30
  codenv init
31
31
  codenv statusline [options]
32
+ codenv usage-reset [--yes]
32
33
 
33
34
  Options:
34
35
  -c, --config <path> Path to config JSON
@@ -59,6 +60,9 @@ Statusline options:
59
60
  --usage-output <n> Set output token usage
60
61
  --sync-usage Sync usage from sessions before reading
61
62
 
63
+ Usage reset options:
64
+ -y, --yes Skip confirmation prompt
65
+
62
66
  Examples:
63
67
  codenv init
64
68
  codenv use codex primary
@@ -69,6 +73,7 @@ Examples:
69
73
  codenv remove --all
70
74
  codenv launch codex -- --help
71
75
  codenv statusline --format json
76
+ codenv usage-reset --yes
72
77
  CODE_ENV_CONFIG=~/.config/code-env/config.json codenv use claude default
73
78
  codenv add --type codex primary OPENAI_BASE_URL=https://api.example.com/v1 OPENAI_API_KEY=YOUR_API_KEY
74
79
  codenv add
package/bin/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printHelp = exports.parseStatuslineArgs = exports.parseAddArgs = exports.parseInitArgs = exports.parseArgs = void 0;
3
+ exports.printHelp = exports.parseStatuslineArgs = exports.parseUsageResetArgs = exports.parseAddArgs = exports.parseInitArgs = exports.parseArgs = void 0;
4
4
  /**
5
5
  * CLI module exports
6
6
  */
@@ -8,6 +8,7 @@ var args_1 = require("./args");
8
8
  Object.defineProperty(exports, "parseArgs", { enumerable: true, get: function () { return args_1.parseArgs; } });
9
9
  Object.defineProperty(exports, "parseInitArgs", { enumerable: true, get: function () { return args_1.parseInitArgs; } });
10
10
  Object.defineProperty(exports, "parseAddArgs", { enumerable: true, get: function () { return args_1.parseAddArgs; } });
11
+ Object.defineProperty(exports, "parseUsageResetArgs", { enumerable: true, get: function () { return args_1.parseUsageResetArgs; } });
11
12
  Object.defineProperty(exports, "parseStatuslineArgs", { enumerable: true, get: function () { return args_1.parseStatuslineArgs; } });
12
13
  var help_1 = require("./help");
13
14
  Object.defineProperty(exports, "printHelp", { enumerable: true, get: function () { return help_1.printHelp; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printStatusline = exports.runLaunch = exports.printUnset = exports.printShow = exports.addConfig = exports.printList = exports.printUse = exports.buildUseLines = void 0;
3
+ exports.runUsageReset = exports.printStatusline = exports.runLaunch = exports.printUnset = exports.printShow = exports.addConfig = exports.printList = exports.printUse = exports.buildUseLines = void 0;
4
4
  /**
5
5
  * Commands module exports
6
6
  */
@@ -19,3 +19,5 @@ var launch_1 = require("./launch");
19
19
  Object.defineProperty(exports, "runLaunch", { enumerable: true, get: function () { return launch_1.runLaunch; } });
20
20
  var statusline_1 = require("./statusline");
21
21
  Object.defineProperty(exports, "printStatusline", { enumerable: true, get: function () { return statusline_1.printStatusline; } });
22
+ var usage_1 = require("./usage");
23
+ Object.defineProperty(exports, "runUsageReset", { enumerable: true, get: function () { return usage_1.runUsageReset; } });
@@ -4,6 +4,7 @@ exports.printList = printList;
4
4
  const display_1 = require("../profile/display");
5
5
  const defaults_1 = require("../config/defaults");
6
6
  const usage_1 = require("../usage");
7
+ const pricing_1 = require("../usage/pricing");
7
8
  function printList(config, configPath) {
8
9
  const rows = (0, display_1.buildListRows)(config, defaults_1.getResolvedDefaultProfileKeys);
9
10
  if (rows.length === 0) {
@@ -12,6 +13,7 @@ function printList(config, configPath) {
12
13
  }
13
14
  try {
14
15
  const usageTotals = (0, usage_1.readUsageTotalsIndex)(config, configPath, true);
16
+ const usageCosts = (0, usage_1.readUsageCostIndex)(config, configPath, false);
15
17
  if (usageTotals) {
16
18
  for (const row of rows) {
17
19
  if (!row.usageType)
@@ -23,6 +25,19 @@ function printList(config, configPath) {
23
25
  row.totalTokens = usage.total;
24
26
  }
25
27
  }
28
+ if (usageCosts) {
29
+ for (const row of rows) {
30
+ if (!row.usageType)
31
+ continue;
32
+ const cost = (0, usage_1.resolveUsageCostForProfile)(usageCosts, row.usageType, row.key, row.name);
33
+ if (!cost)
34
+ continue;
35
+ row.todayCost = cost.today;
36
+ row.totalCost = cost.total;
37
+ row.todayBilledTokens = cost.todayTokens;
38
+ row.totalBilledTokens = cost.totalTokens;
39
+ }
40
+ }
26
41
  }
27
42
  catch {
28
43
  // ignore usage sync errors
@@ -32,8 +47,14 @@ function printList(config, configPath) {
32
47
  const headerToday = "TODAY";
33
48
  const headerTotal = "TOTAL";
34
49
  const headerNote = "NOTE";
35
- const todayTexts = rows.map((row) => (0, usage_1.formatTokenCount)(row.todayTokens));
36
- const totalTexts = rows.map((row) => (0, usage_1.formatTokenCount)(row.totalTokens));
50
+ const todayTexts = rows.map((row) => {
51
+ var _a, _b;
52
+ return formatUsageWithCost(row.todayTokens, (_a = row.todayBilledTokens) !== null && _a !== void 0 ? _a : null, (_b = row.todayCost) !== null && _b !== void 0 ? _b : null);
53
+ });
54
+ const totalTexts = rows.map((row) => {
55
+ var _a, _b;
56
+ return formatUsageWithCost(row.totalTokens, (_a = row.totalBilledTokens) !== null && _a !== void 0 ? _a : null, (_b = row.totalCost) !== null && _b !== void 0 ? _b : null);
57
+ });
37
58
  const nameWidth = Math.max(headerName.length, ...rows.map((row) => row.name.length));
38
59
  const typeWidth = Math.max(headerType.length, ...rows.map((row) => row.type.length));
39
60
  const todayWidth = Math.max(headerToday.length, ...todayTexts.map((v) => v.length));
@@ -55,3 +76,24 @@ function printList(config, configPath) {
55
76
  }
56
77
  }
57
78
  }
79
+ function formatUsageWithCost(tokens, billedTokens, cost) {
80
+ const tokenText = (0, usage_1.formatTokenCount)(tokens !== null && tokens !== void 0 ? tokens : null);
81
+ if (tokenText === "-")
82
+ return tokenText;
83
+ if (cost === null || !Number.isFinite(cost))
84
+ return tokenText;
85
+ if (tokens === null || tokens === undefined || !Number.isFinite(tokens)) {
86
+ return tokenText;
87
+ }
88
+ if (billedTokens === null || !Number.isFinite(billedTokens)) {
89
+ return `${tokenText} (${(0, pricing_1.formatUsdAmount)(cost)})`;
90
+ }
91
+ if (billedTokens >= tokens) {
92
+ return `${tokenText} (${(0, pricing_1.formatUsdAmount)(cost)})`;
93
+ }
94
+ const billedText = (0, usage_1.formatTokenCount)(billedTokens);
95
+ if (billedText === "-") {
96
+ return `${tokenText} (${(0, pricing_1.formatUsdAmount)(cost)})`;
97
+ }
98
+ return `${tokenText} (billed ${billedText}, ${(0, pricing_1.formatUsdAmount)(cost)})`;
99
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runUsageReset = runUsageReset;
4
+ const usage_1 = require("../usage");
5
+ const ui_1 = require("../ui");
6
+ async function runUsageReset(config, configPath, args) {
7
+ if (!args.yes) {
8
+ const rl = (0, ui_1.createReadline)();
9
+ try {
10
+ const confirmed = await (0, ui_1.askConfirm)(rl, "Clear all usage history files? This cannot be undone. (y/N): ");
11
+ if (!confirmed)
12
+ return;
13
+ }
14
+ finally {
15
+ rl.close();
16
+ }
17
+ }
18
+ const result = (0, usage_1.clearUsageHistory)(config, configPath);
19
+ const removed = result.removed.sort();
20
+ const missing = result.missing.sort();
21
+ const failed = result.failed.sort((a, b) => a.path.localeCompare(b.path));
22
+ if (removed.length === 0 && failed.length === 0) {
23
+ console.log("No usage files found.");
24
+ return;
25
+ }
26
+ if (removed.length > 0) {
27
+ console.log(`Removed ${removed.length} file(s):`);
28
+ for (const filePath of removed) {
29
+ console.log(`- ${filePath}`);
30
+ }
31
+ }
32
+ if (missing.length > 0) {
33
+ console.log(`Skipped ${missing.length} missing file(s).`);
34
+ }
35
+ if (failed.length > 0) {
36
+ for (const failure of failed) {
37
+ console.error(`Failed to remove ${failure.path}: ${failure.error}`);
38
+ }
39
+ process.exitCode = 1;
40
+ }
41
+ }
package/bin/index.js CHANGED
@@ -111,6 +111,13 @@ async function main() {
111
111
  (0, commands_1.printStatusline)(config, configPath, statuslineArgs);
112
112
  return;
113
113
  }
114
+ if (cmd === "usage-reset" || cmd === "reset-usage") {
115
+ const resetArgs = (0, cli_1.parseUsageResetArgs)(args.slice(1));
116
+ const configPath = process.env.CODE_ENV_CONFIG_PATH || (0, config_1.findConfigPath)(parsed.configPath);
117
+ const config = (0, config_1.readConfigIfExists)(configPath);
118
+ await (0, commands_1.runUsageReset)(config, configPath, resetArgs);
119
+ return;
120
+ }
114
121
  const configPath = (0, config_1.findConfigPath)(parsed.configPath);
115
122
  const config = (0, config_1.readConfig)(configPath);
116
123
  if (cmd === "default") {
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStatuslineDebugPath = getStatuslineDebugPath;
3
4
  exports.appendStatuslineDebug = appendStatuslineDebug;
4
5
  const fs = require("fs");
5
6
  const path = require("path");
@@ -8,6 +8,7 @@ exports.formatContextSegment = formatContextSegment;
8
8
  exports.formatContextUsedSegment = formatContextUsedSegment;
9
9
  exports.formatModeSegment = formatModeSegment;
10
10
  const usage_1 = require("../usage");
11
+ const pricing_1 = require("../usage/pricing");
11
12
  const style_1 = require("./style");
12
13
  const path = require("path");
13
14
  function getCwdSegment(cwd) {
@@ -17,16 +18,12 @@ function getCwdSegment(cwd) {
17
18
  const segment = `${style_1.ICON_CWD} ${base}`;
18
19
  return (0, style_1.dim)(segment);
19
20
  }
20
- function formatUsageSegment(usage) {
21
- var _a;
22
- if (!usage)
21
+ function formatUsageSegment(todayCost, sessionCost) {
22
+ if (todayCost === null && sessionCost === null)
23
23
  return null;
24
- const today = (_a = usage.todayTokens) !== null && _a !== void 0 ? _a : (usage.inputTokens !== null || usage.outputTokens !== null
25
- ? (usage.inputTokens || 0) + (usage.outputTokens || 0)
26
- : usage.totalTokens);
27
- if (today === null)
28
- return null;
29
- const text = `Today ${(0, usage_1.formatTokenCount)(today)}`;
24
+ const todayText = `T ${(0, pricing_1.formatUsdAmount)(todayCost)}`;
25
+ const sessionText = `S ${(0, pricing_1.formatUsdAmount)(sessionCost)}`;
26
+ const text = `${todayText} / ${sessionText}`;
30
27
  return (0, style_1.colorize)(`${style_1.ICON_USAGE} ${text}`, "33");
31
28
  }
32
29
  function formatModelSegment(model, provider) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildStatuslineResult = buildStatuslineResult;
4
4
  const type_1 = require("../profile/type");
5
5
  const usage_1 = require("../usage");
6
+ const pricing_1 = require("../usage/pricing");
6
7
  const debug_1 = require("./debug");
7
8
  const format_1 = require("./format");
8
9
  const git_1 = require("./git");
@@ -37,8 +38,10 @@ function buildStatuslineResult(args, config, configPath) {
37
38
  }
38
39
  const cwd = (0, utils_1.firstNonEmpty)(args.cwd, process.env.CODE_ENV_CWD, (0, input_1.getWorkspaceDir)(stdinInput), stdinInput ? stdinInput.cwd : null, process.cwd());
39
40
  const sessionId = (0, input_1.getSessionId)(stdinInput);
40
- const stdinUsageTotals = (0, usage_2.getUsageTotalsFromInput)(stdinInput);
41
41
  const usageType = (0, type_1.normalizeType)(type || "");
42
+ const stdinUsageTotals = (0, usage_2.getUsageTotalsFromInput)(stdinInput, usageType);
43
+ const model = (0, utils_1.firstNonEmpty)(args.model, process.env.CODE_ENV_MODEL, (0, input_1.getModelFromInput)(stdinInput));
44
+ const modelProvider = (0, utils_1.firstNonEmpty)(process.env.CODE_ENV_MODEL_PROVIDER, (0, input_1.getModelProviderFromInput)(stdinInput));
42
45
  (0, debug_1.appendStatuslineDebug)(configPath, {
43
46
  timestamp: new Date().toISOString(),
44
47
  typeCandidate,
@@ -68,27 +71,31 @@ function buildStatuslineResult(args, config, configPath) {
68
71
  input: stdinInput,
69
72
  });
70
73
  if (args.syncUsage && sessionId && stdinUsageTotals) {
71
- (0, usage_1.syncUsageFromStatuslineInput)(config, configPath, usageType, profileKey, profileName, sessionId, stdinUsageTotals, cwd);
74
+ (0, usage_1.syncUsageFromStatuslineInput)(config, configPath, usageType, profileKey, profileName, sessionId, stdinUsageTotals, cwd, model);
72
75
  }
73
- const model = (0, utils_1.firstNonEmpty)(args.model, process.env.CODE_ENV_MODEL, (0, input_1.getModelFromInput)(stdinInput));
74
- const modelProvider = (0, utils_1.firstNonEmpty)(process.env.CODE_ENV_MODEL_PROVIDER, (0, input_1.getModelProviderFromInput)(stdinInput));
75
76
  const usage = {
76
77
  todayTokens: (0, utils_1.firstNumber)(args.usageToday, process.env.CODE_ENV_USAGE_TODAY),
77
78
  totalTokens: (0, utils_1.firstNumber)(args.usageTotal, process.env.CODE_ENV_USAGE_TOTAL),
78
79
  inputTokens: (0, utils_1.firstNumber)(args.usageInput, process.env.CODE_ENV_USAGE_INPUT),
79
80
  outputTokens: (0, utils_1.firstNumber)(args.usageOutput, process.env.CODE_ENV_USAGE_OUTPUT),
81
+ cacheReadTokens: null,
82
+ cacheWriteTokens: null,
80
83
  };
81
84
  const hasExplicitUsage = usage.todayTokens !== null ||
82
85
  usage.totalTokens !== null ||
83
86
  usage.inputTokens !== null ||
84
87
  usage.outputTokens !== null;
85
- const stdinUsage = (0, usage_2.normalizeInputUsage)((0, input_1.getInputUsage)(stdinInput));
88
+ const stdinUsage = (0, usage_2.normalizeInputUsage)((0, input_1.getInputUsage)(stdinInput, usageType));
89
+ const recordsUsage = (0, usage_2.resolveUsageFromRecords)(config, configPath, type, profileKey, profileName, args.syncUsage);
86
90
  let finalUsage = hasExplicitUsage ? usage : null;
91
+ if (!finalUsage && args.syncUsage && recordsUsage) {
92
+ finalUsage = recordsUsage;
93
+ }
87
94
  if (!finalUsage) {
88
95
  finalUsage = stdinUsage;
89
96
  }
90
- if (!finalUsage) {
91
- finalUsage = (0, usage_2.resolveUsageFromRecords)(config, configPath, type, profileKey, profileName, args.syncUsage);
97
+ if (!finalUsage && recordsUsage) {
98
+ finalUsage = recordsUsage;
92
99
  }
93
100
  let gitStatus = (0, git_1.getGitStatus)(cwd);
94
101
  if (!gitStatus) {
@@ -103,7 +110,38 @@ function buildStatuslineResult(args, config, configPath) {
103
110
  const gitSegment = (0, git_1.formatGitSegment)(gitStatus);
104
111
  const profileSegment = (0, format_1.formatProfileSegment)(type, profileKey, profileName);
105
112
  const modelSegment = (0, format_1.formatModelSegment)(model, modelProvider);
106
- const usageSegment = (0, format_1.formatUsageSegment)(finalUsage);
113
+ let profile = profileKey && config.profiles ? config.profiles[profileKey] : null;
114
+ if (!profile && profileName && config.profiles) {
115
+ const matches = Object.entries(config.profiles).find(([key, entry]) => {
116
+ const displayName = (0, type_1.getProfileDisplayName)(key, entry);
117
+ return (displayName === profileName ||
118
+ entry.name === profileName ||
119
+ key === profileName);
120
+ });
121
+ if (matches)
122
+ profile = matches[1];
123
+ }
124
+ const sessionUsage = hasExplicitUsage ? usage : stdinUsage;
125
+ const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile || null, model);
126
+ let sessionCost = null;
127
+ if (hasExplicitUsage) {
128
+ sessionCost = sessionUsage
129
+ ? (0, pricing_1.calculateUsageCost)(sessionUsage, pricing)
130
+ : null;
131
+ }
132
+ else {
133
+ const sessionCostFromRecords = sessionId
134
+ ? (0, usage_1.readUsageSessionCost)(config, configPath, type, sessionId, args.syncUsage)
135
+ : null;
136
+ sessionCost =
137
+ sessionCostFromRecords !== null && sessionCostFromRecords !== void 0 ? sessionCostFromRecords : (sessionUsage ? (0, pricing_1.calculateUsageCost)(sessionUsage, pricing) : null);
138
+ }
139
+ const costIndex = (0, usage_1.readUsageCostIndex)(config, configPath, args.syncUsage);
140
+ const costTotals = costIndex
141
+ ? (0, usage_1.resolveUsageCostForProfile)(costIndex, type, profileKey, profileName)
142
+ : null;
143
+ const todayCost = costTotals ? costTotals.today : null;
144
+ const usageSegment = (0, format_1.formatUsageSegment)(todayCost, sessionCost);
107
145
  const contextLeft = (0, input_1.getContextLeftPercent)(stdinInput, type);
108
146
  const contextSegment = (0, format_1.formatContextSegment)(contextLeft);
109
147
  const contextUsedTokens = (0, input_1.getContextUsedTokens)(stdinInput);
@@ -17,6 +17,8 @@ const fs = require("fs");
17
17
  const constants_1 = require("../constants");
18
18
  const type_1 = require("../profile/type");
19
19
  const utils_1 = require("./utils");
20
+ const claude_1 = require("./usage/claude");
21
+ const codex_1 = require("./usage/codex");
20
22
  function readStdinJson() {
21
23
  if (process.stdin.isTTY)
22
24
  return null;
@@ -92,101 +94,17 @@ function getInputProfile(input) {
92
94
  return null;
93
95
  return input.profile;
94
96
  }
95
- function getInputUsage(input) {
96
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
97
+ function getInputUsage(input, type) {
97
98
  if (!input)
98
99
  return null;
99
- if ((0, utils_1.isRecord)(input.usage)) {
100
- return input.usage;
100
+ const normalized = normalizeTypeValue(type);
101
+ if (normalized === "codex") {
102
+ return (0, codex_1.getCodexInputUsage)(input);
101
103
  }
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
- }
104
+ if (normalized === "claude") {
105
+ return (0, claude_1.getClaudeInputUsage)(input);
145
106
  }
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
- };
107
+ return (0, codex_1.getCodexInputUsage)(input) || (0, claude_1.getClaudeInputUsage)(input);
190
108
  }
191
109
  function getSessionId(input) {
192
110
  if (!input)