wezterm-codex-status-line 0.1.1__tar.gz

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.
Files changed (41) hide show
  1. wezterm_codex_status_line-0.1.1/.gitignore +5 -0
  2. wezterm_codex_status_line-0.1.1/LICENSE +21 -0
  3. wezterm_codex_status_line-0.1.1/PKG-INFO +52 -0
  4. wezterm_codex_status_line-0.1.1/README.md +37 -0
  5. wezterm_codex_status_line-0.1.1/pyproject.toml +34 -0
  6. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/__init__.py +1 -0
  7. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/config.lua +456 -0
  8. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/common.lua +28 -0
  9. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/prices.lua +16 -0
  10. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/pricing.lua +87 -0
  11. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/process.lua +260 -0
  12. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/render.lua +511 -0
  13. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/domain/session.lua +449 -0
  14. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/formatting.lua +268 -0
  15. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/git.lua +72 -0
  16. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/layout.lua +53 -0
  17. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/legacy_renderer.lua +342 -0
  18. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/lifecycle.lua +421 -0
  19. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/process.lua +216 -0
  20. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/renderer.lua +342 -0
  21. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/rollout.lua +390 -0
  22. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/session_index.lua +563 -0
  23. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/state.lua +62 -0
  24. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/util.lua +244 -0
  25. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/version.lua +4 -0
  26. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline/wezterm_adapter.lua +211 -0
  27. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline.lua +44 -0
  28. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline_bridge.js +69 -0
  29. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline_bridge.ps1 +285 -0
  30. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline_bridge.py +255 -0
  31. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/codex_statusline_core.lua +8 -0
  32. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/config.schema.json +281 -0
  33. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/default-config.json +180 -0
  34. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/model-pricing.json +21 -0
  35. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/render-cases.json +198 -0
  36. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/sample-state.json +34 -0
  37. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/contract/usage-cases.json +23 -0
  38. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/assets/install.ps1 +822 -0
  39. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/cli.py +677 -0
  40. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/pricing.py +75 -0
  41. wezterm_codex_status_line-0.1.1/src/wezterm_codex_status_line/render.py +338 -0
@@ -0,0 +1,5 @@
1
+ /src/wezterm_codex_status_line/assets
2
+ /LICENSE
3
+ /dist
4
+ /*.egg-info
5
+ /.venv
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lambda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.5
2
+ Name: wezterm-codex-status-line
3
+ Version: 0.1.1
4
+ Summary: Install and configure the WezTerm Codex status line
5
+ Project-URL: Documentation, https://un4gt.github.io/wezterm-codex-status-line/
6
+ Project-URL: Repository, https://github.com/un4gt/wezterm-codex-status-line
7
+ Author: un4gt
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: jsonschema<5,>=4.23
12
+ Requires-Dist: questionary<3,>=2.1
13
+ Requires-Dist: typer<1,>=0.16
14
+ Description-Content-Type: text/markdown
15
+
16
+ # WezTerm Codex Status Line
17
+
18
+ 为 WezTerm 中的 Codex CLI 添加底部状态栏,显示模型、推理强度、Git 分支和 Token 用量。
19
+
20
+ ## 安装
21
+
22
+ ```sh
23
+ uvx wezterm-codex-status-line install --no-title-bridge
24
+ ```
25
+
26
+ 安装后,在 WezTerm 配置的 `return config` 之前加入:
27
+
28
+ ```lua
29
+ require("codex_statusline").setup()
30
+ ```
31
+
32
+ 重新加载 WezTerm 配置,然后启动 Codex。平台要求见[安装指南](https://un4gt.github.io/wezterm-codex-status-line/docs/getting-started/installation/);macOS 暂未完成实机验证,Linux 暂未完成 Wayland/X11 桌面验证。
33
+
34
+ ## 配置
35
+
36
+ 在[交互预览](https://un4gt.github.io/wezterm-codex-status-line/preview/)中调整显示并下载 JSON,然后导入:
37
+
38
+ ```sh
39
+ uvx wezterm-codex-status-line configure --from ./codex_statusline_config.json
40
+ ```
41
+
42
+ 重新加载 WezTerm 配置以应用修改。完整步骤见[配置指南](https://un4gt.github.io/wezterm-codex-status-line/docs/guides/configuration/)。
43
+
44
+ ## 命令
45
+
46
+ `install` 安装,`configure` 配置,`preview` 预览,`doctor` 检查,`update` 更新,`uninstall` 卸载。
47
+
48
+ 参数见[命令行参考](https://un4gt.github.io/wezterm-codex-status-line/docs/guides/cli-reference/)。卸载前先移除 WezTerm 配置中的模块引用并重新加载配置。
49
+
50
+ ## 许可证
51
+
52
+ MIT
@@ -0,0 +1,37 @@
1
+ # WezTerm Codex Status Line
2
+
3
+ 为 WezTerm 中的 Codex CLI 添加底部状态栏,显示模型、推理强度、Git 分支和 Token 用量。
4
+
5
+ ## 安装
6
+
7
+ ```sh
8
+ uvx wezterm-codex-status-line install --no-title-bridge
9
+ ```
10
+
11
+ 安装后,在 WezTerm 配置的 `return config` 之前加入:
12
+
13
+ ```lua
14
+ require("codex_statusline").setup()
15
+ ```
16
+
17
+ 重新加载 WezTerm 配置,然后启动 Codex。平台要求见[安装指南](https://un4gt.github.io/wezterm-codex-status-line/docs/getting-started/installation/);macOS 暂未完成实机验证,Linux 暂未完成 Wayland/X11 桌面验证。
18
+
19
+ ## 配置
20
+
21
+ 在[交互预览](https://un4gt.github.io/wezterm-codex-status-line/preview/)中调整显示并下载 JSON,然后导入:
22
+
23
+ ```sh
24
+ uvx wezterm-codex-status-line configure --from ./codex_statusline_config.json
25
+ ```
26
+
27
+ 重新加载 WezTerm 配置以应用修改。完整步骤见[配置指南](https://un4gt.github.io/wezterm-codex-status-line/docs/guides/configuration/)。
28
+
29
+ ## 命令
30
+
31
+ `install` 安装,`configure` 配置,`preview` 预览,`doctor` 检查,`update` 更新,`uninstall` 卸载。
32
+
33
+ 参数见[命令行参考](https://un4gt.github.io/wezterm-codex-status-line/docs/guides/cli-reference/)。卸载前先移除 WezTerm 配置中的模块引用并重新加载配置。
34
+
35
+ ## 许可证
36
+
37
+ MIT
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "wezterm-codex-status-line"
7
+ version = "0.1.1"
8
+ description = "Install and configure the WezTerm Codex status line"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "un4gt"}]
14
+ dependencies = [
15
+ "jsonschema>=4.23,<5",
16
+ "questionary>=2.1,<3",
17
+ "typer>=0.16,<1"
18
+ ]
19
+
20
+ [project.urls]
21
+ Documentation = "https://un4gt.github.io/wezterm-codex-status-line/"
22
+ Repository = "https://github.com/un4gt/wezterm-codex-status-line"
23
+
24
+ [project.scripts]
25
+ wezterm-codex-status-line = "wezterm_codex_status_line.cli:main"
26
+
27
+ [tool.hatch.build]
28
+ artifacts = ["/src/wezterm_codex_status_line/assets/**", "/LICENSE"]
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/wezterm_codex_status_line"]
32
+
33
+ [tool.hatch.build.targets.sdist]
34
+ include = ["src", "README.md", "LICENSE", "pyproject.toml"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -0,0 +1,456 @@
1
+ local core = require("codex_statusline_core")
2
+ local util = require("codex_statusline.util")
3
+ local M = {}
4
+
5
+ function M.new(wezterm, adapter)
6
+ local trim = util.trim
7
+ local path_join = util.path_join
8
+ local read_file = util.read_file
9
+ local shallow_merge = util.shallow_merge
10
+ local resolved_module_path = util.resolved_module_path
11
+ local safe_json_parse = adapter.safe_json_parse
12
+ local CONFIG_CACHE = {
13
+ at = 0,
14
+ loaded = false,
15
+ values = nil,
16
+ }
17
+
18
+ local function strip_toml_comment(line)
19
+ local in_single = false
20
+ local in_double = false
21
+
22
+ local i = 1
23
+ while i <= #line do
24
+ local ch = line:sub(i, i)
25
+
26
+ if ch == "'" and not in_double then
27
+ in_single = not in_single
28
+ elseif ch == '"' and not in_single then
29
+ local backslashes = 0
30
+ local j = i - 1
31
+ while j >= 1 and line:sub(j, j) == "\\" do
32
+ backslashes = backslashes + 1
33
+ j = j - 1
34
+ end
35
+ if backslashes % 2 == 0 then
36
+ in_double = not in_double
37
+ end
38
+ elseif ch == "#" and not in_single and not in_double then
39
+ return line:sub(1, i - 1)
40
+ end
41
+
42
+ i = i + 1
43
+ end
44
+
45
+ return line
46
+ end
47
+
48
+ local function parse_toml_scalar(value)
49
+ local v = trim(value)
50
+ if not v then
51
+ return nil
52
+ end
53
+
54
+ if v:sub(1, 1) == '"' and v:sub(-1) == '"' and #v >= 2 then
55
+ local inner = v:sub(2, -2)
56
+ inner = inner:gsub("\\\\", "\\"):gsub('\\"', '"')
57
+ return inner
58
+ end
59
+
60
+ if v:sub(1, 1) == "'" and v:sub(-1) == "'" and #v >= 2 then
61
+ return v:sub(2, -2)
62
+ end
63
+
64
+ return v
65
+ end
66
+
67
+ local function default_codex_config_paths()
68
+ local home = wezterm.home_dir or os.getenv("USERPROFILE") or os.getenv("HOME")
69
+ if not home then
70
+ return nil
71
+ end
72
+ local codex_dir = path_join({ home, ".codex" })
73
+ return {
74
+ path_join({ codex_dir, "config.toml" }),
75
+ }
76
+ end
77
+
78
+ local function default_codex_home()
79
+ local explicit = trim(os.getenv("CODEX_HOME"))
80
+ if explicit then
81
+ return explicit
82
+ end
83
+ local home = wezterm.home_dir or os.getenv("USERPROFILE") or os.getenv("HOME")
84
+ if not home then
85
+ return nil
86
+ end
87
+ return path_join({ home, ".codex" })
88
+ end
89
+
90
+ local function codex_home_for_opts(opts)
91
+ return trim(opts and opts.codex_home) or default_codex_home()
92
+ end
93
+
94
+ local function bridge_root_for_opts(opts)
95
+ local configured = opts and opts.sessions and trim(opts.sessions.bridge_dir) or nil
96
+ if configured then
97
+ return configured
98
+ end
99
+ local codex_home = codex_home_for_opts(opts)
100
+ if not codex_home then
101
+ return nil
102
+ end
103
+ return path_join({ codex_home, "wezterm-statusline" })
104
+ end
105
+
106
+ local function load_codex_config(opts)
107
+ if not opts.codex_config.enabled then
108
+ return nil
109
+ end
110
+
111
+ local now = os.time()
112
+ if CONFIG_CACHE.loaded and (now - CONFIG_CACHE.at) < opts.codex_config.cache_ttl_seconds then
113
+ return CONFIG_CACHE.values
114
+ end
115
+
116
+ local paths = {}
117
+ if trim(opts.codex_config.path) then
118
+ table.insert(paths, opts.codex_config.path)
119
+ else
120
+ local defaults = default_codex_config_paths()
121
+ if defaults then
122
+ for _, p in ipairs(defaults) do
123
+ table.insert(paths, p)
124
+ end
125
+ end
126
+ end
127
+ if #paths == 0 then
128
+ CONFIG_CACHE.values = nil
129
+ CONFIG_CACHE.at = now
130
+ CONFIG_CACHE.loaded = true
131
+ return nil
132
+ end
133
+
134
+ local text = nil
135
+ for _, p in ipairs(paths) do
136
+ text = read_file(p)
137
+ if text then
138
+ break
139
+ end
140
+ end
141
+ if not text then
142
+ CONFIG_CACHE.values = nil
143
+ CONFIG_CACHE.at = now
144
+ CONFIG_CACHE.loaded = true
145
+ return nil
146
+ end
147
+
148
+ local values = {}
149
+ for line in text:gmatch("[^\r\n]+") do
150
+ local raw = trim(line)
151
+ if raw and raw:sub(1, 1) ~= "#" then
152
+ if raw:sub(1, 1) == "[" then
153
+ break
154
+ end
155
+ local eq = raw:find("=", 1, true)
156
+ if eq then
157
+ local key = trim(raw:sub(1, eq - 1))
158
+ local rhs = strip_toml_comment(raw:sub(eq + 1))
159
+ local val = parse_toml_scalar(rhs)
160
+ if key and val then
161
+ values[key] = val
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ CONFIG_CACHE.values = {
168
+ model = trim(values.model),
169
+ thinking = trim(values.model_reasoning_effort or values.reasoning_effort),
170
+ provider = trim(values.model_provider or values.provider),
171
+ service_tier = trim(values.service_tier),
172
+ }
173
+ CONFIG_CACHE.at = now
174
+ CONFIG_CACHE.loaded = true
175
+
176
+ return CONFIG_CACHE.values
177
+ end
178
+
179
+ local DEFAULTS = {
180
+ debug = false,
181
+ label = "CODEX",
182
+ log = {
183
+ enabled = true,
184
+ marker = "CODEX_STATUSLINE_LOADED",
185
+ },
186
+ compat = {
187
+ -- Older WezTerm builds used `update-right-status` instead of `update-status`.
188
+ -- Enable if you don't see `update-status` events firing.
189
+ update_right_status = false,
190
+ },
191
+ codex_config = {
192
+ enabled = true,
193
+ path = nil,
194
+ cache_ttl_seconds = 5,
195
+ },
196
+ title_bridge = {
197
+ enabled = true,
198
+ app_name = "codex",
199
+ },
200
+ git = {
201
+ enabled = true,
202
+ cache_ttl_seconds = 5,
203
+ },
204
+ codex_home = nil, -- defaults to $CODEX_HOME or ~/.codex
205
+ user_vars = {
206
+ model = { "codex_model", "CODEX_MODEL" },
207
+ thinking = { "codex_thinking", "CODEX_THINKING", "codex_reasoning", "CODEX_REASONING" },
208
+ provider = { "codex_provider", "CODEX_PROVIDER", "codex_model_provider", "CODEX_MODEL_PROVIDER" },
209
+ active = { "codex_active", "CODEX_ACTIVE" },
210
+ },
211
+ process_match = {
212
+ enabled = true,
213
+ names = { "codex", "codex.exe" },
214
+ argv_markers = { "/@openai/codex/bin/codex.js" },
215
+ terminal_names = {
216
+ "wezterm",
217
+ "wezterm.exe",
218
+ "wezterm-gui",
219
+ "wezterm-gui.exe",
220
+ "wezterm-mux-server-impl",
221
+ "wezterm-mux-server-impl.exe",
222
+ },
223
+ -- Keep the status visible briefly only when process inspection is unavailable.
224
+ grace_seconds = 5,
225
+ tree_cache_ttl_seconds = 1,
226
+ },
227
+ sessions = {
228
+ enabled = true,
229
+ binding_mode = "auto", -- "auto" | "hook" | "heuristic"
230
+ bridge_dir = nil, -- defaults to $CODEX_HOME/wezterm-statusline
231
+ allow_fallback_latest = false,
232
+ resume_fallback_enabled = true,
233
+ resume_fallback_max_age_seconds = 30,
234
+ resume_fallback_clock_skew_seconds = 5,
235
+ resume_fallback_scan_ttl_seconds = 2,
236
+ cache_ttl_seconds = 5,
237
+ full_scan_ttl_seconds = 300,
238
+ tail_ttl_seconds = 1,
239
+ open_fail_clear_seconds = 30,
240
+ initial_seek_bytes = 131072,
241
+ activity_seek_bytes = 65536,
242
+ activity_max_seek_bytes = 8388608,
243
+ max_meta_lines = 40,
244
+ max_tail_lines = 200,
245
+ },
246
+ bottom_pane = {
247
+ enabled = true,
248
+ rows = 1,
249
+ layout_debounce_ms = 1000,
250
+ close_grace_seconds = 2,
251
+ prevent_focus = true,
252
+ },
253
+ render = {
254
+ powerline = true,
255
+ plain_fallback = true,
256
+ model_display = "name", -- "name" | "icon_name" | "icon"
257
+ segment_order = {
258
+ "label",
259
+ "model",
260
+ "reasoning",
261
+ "activity",
262
+ "provider",
263
+ "personality",
264
+ "service_tier",
265
+ "cwd",
266
+ "project",
267
+ "git",
268
+ "permissions",
269
+ "approval",
270
+ "context",
271
+ "context_used",
272
+ "context_window",
273
+ "used_tokens",
274
+ "cache_rate",
275
+ "cost",
276
+ "input_tokens",
277
+ "cached_tokens",
278
+ "output_tokens",
279
+ "reasoning_tokens",
280
+ "thread_id",
281
+ "task_progress",
282
+ "codex_version",
283
+ "icon",
284
+ },
285
+ disabled_segments = {
286
+ "personality",
287
+ "service_tier",
288
+ "project",
289
+ "permissions",
290
+ "approval",
291
+ "context_used",
292
+ "context_window",
293
+ "input_tokens",
294
+ "cached_tokens",
295
+ "output_tokens",
296
+ "reasoning_tokens",
297
+ "thread_id",
298
+ "task_progress",
299
+ "codex_version",
300
+ },
301
+ },
302
+ activity = {
303
+ labels = {
304
+ plan = "PLAN",
305
+ review = "REVIEW",
306
+ goal_active = "GOAL",
307
+ goal_paused = "GOAL PAUSED",
308
+ goal_blocked = "GOAL BLOCKED",
309
+ goal_usage_limited = "GOAL LIMITED",
310
+ goal_budget_limited = "GOAL BUDGET",
311
+ goal_complete = "GOAL DONE",
312
+ },
313
+ },
314
+ icon = {
315
+ text = "",
316
+ },
317
+ pricing = { models = {} },
318
+ theme = {
319
+ bg = "#11151a",
320
+ fg = "#d5dbe3",
321
+ dim = "#82909f",
322
+ segments = {
323
+ label = { bg = "#1d4ed8", fg = "#ffffff" },
324
+ model = { bg = "#11151a", fg = "#d5dbe3" },
325
+ reasoning = { bg = "#11151a", fg = "#fbbf24" },
326
+ activity = { bg = "#6d28d9", fg = "#ffffff" },
327
+ provider = { bg = "#11151a", fg = "#aab2bf" },
328
+ personality = { bg = "#11151a", fg = "#c4b5fd" },
329
+ service_tier = { bg = "#11151a", fg = "#f0abfc" },
330
+ cwd = { bg = "#11151a", fg = "#d5dbe3" },
331
+ project = { bg = "#11151a", fg = "#93c5fd" },
332
+ git = { bg = "#11151a", fg = "#4ade80" },
333
+ permissions = { bg = "#11151a", fg = "#fca5a5" },
334
+ approval = { bg = "#11151a", fg = "#fdba74" },
335
+ context = { bg = "#11151a", fg = "#7dd3fc" },
336
+ context_used = { bg = "#11151a", fg = "#67e8f9" },
337
+ context_window = { bg = "#11151a", fg = "#a5b4fc" },
338
+ used_tokens = { bg = "#11151a", fg = "#d5dbe3" },
339
+ cache_rate = { bg = "#11151a", fg = "#67e8f9" },
340
+ cost = { bg = "#11151a", fg = "#fbbf24" },
341
+ input_tokens = { bg = "#11151a", fg = "#86efac" },
342
+ cached_tokens = { bg = "#11151a", fg = "#94a3b8" },
343
+ output_tokens = { bg = "#11151a", fg = "#fde68a" },
344
+ reasoning_tokens = { bg = "#11151a", fg = "#f0abfc" },
345
+ thread_id = { bg = "#11151a", fg = "#94a3b8" },
346
+ task_progress = { bg = "#11151a", fg = "#5eead4" },
347
+ codex_version = { bg = "#11151a", fg = "#aab2bf" },
348
+ icon = { bg = "#11151a", fg = "#58d6c5" },
349
+ -- Backward-compatible theme keys for existing setup() overrides.
350
+ thinking = { bg = "#11151a", fg = "#fbbf24" },
351
+ tokens = { bg = "#11151a", fg = "#d5dbe3" },
352
+ },
353
+ glyphs = {
354
+ sep = "",
355
+ branch = "",
356
+ folder = "",
357
+ },
358
+ },
359
+ }
360
+
361
+ local function managed_config_path()
362
+ local module_path = resolved_module_path()
363
+ if not module_path then
364
+ return nil
365
+ end
366
+ local directory = module_path:match("^(.*)[/\\][^/\\]+$")
367
+ if not directory then
368
+ return nil
369
+ end
370
+ return path_join({ directory, "codex_statusline_config.json" })
371
+ end
372
+
373
+ local function load_managed_config()
374
+ if type(io) ~= "table" or type(io.open) ~= "function" then
375
+ return nil
376
+ end
377
+ local path = managed_config_path()
378
+ if not path then
379
+ return nil
380
+ end
381
+ local handle = io.open(path, "r")
382
+ if not handle then
383
+ return nil
384
+ end
385
+ local content = handle:read("*a")
386
+ handle:close()
387
+ local value = safe_json_parse(content)
388
+ if type(value) ~= "table" or tonumber(value.schema) ~= 1 or type(value.options) ~= "table" then
389
+ wezterm.log_error("codex_statusline: ignoring invalid managed config " .. tostring(path))
390
+ return nil
391
+ end
392
+ return value.options
393
+ end
394
+
395
+ local function resolve(user_opts)
396
+ local opts = shallow_merge(DEFAULTS, load_managed_config())
397
+ opts = shallow_merge(opts, user_opts)
398
+
399
+ for _, name in ipairs({
400
+ "log",
401
+ "compat",
402
+ "codex_config",
403
+ "title_bridge",
404
+ "git",
405
+ "user_vars",
406
+ "process_match",
407
+ "sessions",
408
+ "bottom_pane",
409
+ "activity",
410
+ "icon",
411
+ "pricing",
412
+ "render",
413
+ "theme",
414
+ }) do
415
+ if type(opts[name]) ~= "table" then
416
+ error("invalid option table: " .. name)
417
+ end
418
+ end
419
+ if type(opts.theme.segments) ~= "table" or type(opts.theme.glyphs) ~= "table" then
420
+ error("invalid theme configuration")
421
+ end
422
+ local model_display = opts.render.model_display
423
+ if model_display ~= "name" and model_display ~= "icon_name" and model_display ~= "icon" then
424
+ error("render.model_display must be name, icon_name, or icon")
425
+ end
426
+ local pricing = require("codex_statusline.domain.pricing")
427
+ if type(opts.pricing.models) ~= "table" then error("pricing.models must be a table") end
428
+ local price_count = 0
429
+ for model, price in pairs(opts.pricing.models) do
430
+ price_count = price_count + 1
431
+ if not pricing.valid_model(model) or not pricing.valid_price(price) then
432
+ error("invalid pricing.models entry: " .. tostring(model))
433
+ end
434
+ end
435
+ if price_count > 64 then error("pricing.models supports at most 64 entries") end
436
+
437
+ local delay = opts.bottom_pane.layout_debounce_ms
438
+ if type(delay) ~= "number" or delay % 1 ~= 0 or delay < 250 or delay > 5000 then
439
+ error("bottom_pane.layout_debounce_ms must be an integer between 250 and 5000")
440
+ end
441
+ if opts.bottom_pane.rows ~= 1 and opts.bottom_pane.rows ~= 2 then
442
+ error("bottom_pane.rows must be 1 or 2")
443
+ end
444
+ return opts
445
+ end
446
+
447
+ return {
448
+ resolve = resolve,
449
+ load_codex_config = load_codex_config,
450
+ codex_home_for_opts = codex_home_for_opts,
451
+ bridge_root_for_opts = bridge_root_for_opts,
452
+ }
453
+
454
+ end
455
+
456
+ return M
@@ -0,0 +1,28 @@
1
+ local M = {}
2
+
3
+ local function trim(value)
4
+ if value == nil then
5
+ return nil
6
+ end
7
+ local text = tostring(value):gsub("^%s+", ""):gsub("%s+$", "")
8
+ if text == "" then
9
+ return nil
10
+ end
11
+ return text
12
+ end
13
+
14
+ local function to_number(value)
15
+ if type(value) == "number" then
16
+ return value
17
+ end
18
+ if type(value) == "string" then
19
+ local normalized = value:gsub(",", "")
20
+ return tonumber(normalized)
21
+ end
22
+ return nil
23
+ end
24
+
25
+ return {
26
+ trim = trim,
27
+ to_number = to_number,
28
+ }
@@ -0,0 +1,16 @@
1
+ -- Generated from contract/model-pricing.json by scripts/sync-model-pricing.mjs.
2
+ -- https://developers.openai.com/api/docs/pricing (verified 2026-09-14; USD / 1M tokens, standard short context)
3
+ return {
4
+ ["gpt-6-astra"] = { input = 10, cached_input = 1, output = 50 },
5
+ ["gpt-5.6-sol"] = { input = 4, cached_input = 0.4, output = 20 },
6
+ ["gpt-5.6-terra"] = { input = 2, cached_input = 0.2, output = 12 },
7
+ ["gpt-5.6-luna"] = { input = 0.2, cached_input = 0.02, output = 1.2 },
8
+ ["gpt-5.5"] = { input = 5, cached_input = 0.5, output = 30 },
9
+ ["gpt-5.4"] = { input = 2.5, cached_input = 0.25, output = 15 },
10
+ ["gpt-5.4-mini"] = { input = 0.75, cached_input = 0.075, output = 4.5 },
11
+ ["gpt-5.4-nano"] = { input = 0.2, cached_input = 0.02, output = 1.25 },
12
+ ["gpt-5.3-codex"] = { input = 1.75, cached_input = 0.175, output = 14 },
13
+ ["gpt-5.2"] = { input = 1.75, cached_input = 0.175, output = 14 },
14
+ ["gpt-5.1"] = { input = 1.25, cached_input = 0.125, output = 10 },
15
+ ["gpt-5"] = { input = 1.25, cached_input = 0.125, output = 10 },
16
+ }