@praeviso/code-env-switch 0.1.8 → 0.1.9
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 +5 -9
- package/README_zh.md +5 -9
- package/bin/commands/list.js +10 -1
- package/bin/statusline/codex.js +160 -195
- package/bin/statusline/index.js +10 -2
- package/bin/usage/index.js +123 -21
- package/code-env.example.json +1 -4
- package/docs/usage.md +3 -0
- package/docs/usage_zh.md +2 -0
- package/package.json +4 -4
- package/src/commands/list.ts +11 -1
- package/src/statusline/codex.ts +198 -202
- package/src/statusline/index.ts +17 -2
- package/src/types.ts +1 -4
- package/src/usage/index.ts +135 -21
package/README.md
CHANGED
|
@@ -219,10 +219,7 @@ If nothing is found, `codenv add` writes to `~/.config/code-env/config.json`.
|
|
|
219
219
|
"claude": "default"
|
|
220
220
|
},
|
|
221
221
|
"codexStatusline": {
|
|
222
|
-
"
|
|
223
|
-
"showHints": false,
|
|
224
|
-
"updateIntervalMs": 300,
|
|
225
|
-
"timeoutMs": 1000
|
|
222
|
+
"items": ["model-with-reasoning", "context-remaining", "current-dir", "git-branch"]
|
|
226
223
|
},
|
|
227
224
|
"claudeStatusline": {
|
|
228
225
|
"command": "codenv statusline --type claude --sync-usage",
|
|
@@ -259,12 +256,11 @@ If nothing is found, `codenv add` writes to `~/.config/code-env/config.json`.
|
|
|
259
256
|
Notes:
|
|
260
257
|
- `unset`: global keys to clear. Type-specific defaults are applied only for the active type and won't clear the other type.
|
|
261
258
|
- `defaultProfiles`: optional; map of `codex`/`claude` to profile name or key used by `codenv auto`.
|
|
262
|
-
- `codexStatusline`: optional; config to inject Codex TUI status line settings when launching `codex`.
|
|
263
|
-
- `
|
|
264
|
-
- `
|
|
265
|
-
- `updateIntervalMs`: number; update interval in ms for the status line command.
|
|
266
|
-
- `timeoutMs`: number; timeout in ms for the status line command.
|
|
259
|
+
- `codexStatusline`: optional; config to inject official Codex TUI status line settings when launching `codex`.
|
|
260
|
+
- `items`: string[]; ordered item IDs written to `tui.status_line` in `~/.codex/config.toml`.
|
|
261
|
+
- Supported item IDs include: `model-name`, `model-with-reasoning`, `current-dir`, `project-root`, `git-branch`, `context-remaining`, `context-used`, `five-hour-limit`, `weekly-limit`, `codex-version`, `context-window-size`, `used-tokens`, `total-input-tokens`, `total-output-tokens`, `session-id`.
|
|
267
262
|
- `configPath`: optional; override `~/.codex/config.toml` (also supports `CODE_ENV_CODEX_CONFIG_PATH`).
|
|
263
|
+
- If `items` is unset, `codenv` leaves Codex status-line config unchanged (Codex defaults apply).
|
|
268
264
|
- `claudeStatusline`: optional; config to inject Claude Code statusLine settings when launching `claude`.
|
|
269
265
|
- `command`: string (or string[]; arrays are joined into a single command string).
|
|
270
266
|
- `type`: string; statusLine type (default: `command`).
|
package/README_zh.md
CHANGED
|
@@ -218,10 +218,7 @@ codenv use codex primary | source
|
|
|
218
218
|
"claude": "default"
|
|
219
219
|
},
|
|
220
220
|
"codexStatusline": {
|
|
221
|
-
"
|
|
222
|
-
"showHints": false,
|
|
223
|
-
"updateIntervalMs": 300,
|
|
224
|
-
"timeoutMs": 1000
|
|
221
|
+
"items": ["model-with-reasoning", "context-remaining", "current-dir", "git-branch"]
|
|
225
222
|
},
|
|
226
223
|
"claudeStatusline": {
|
|
227
224
|
"command": "codenv statusline --type claude --sync-usage",
|
|
@@ -258,12 +255,11 @@ codenv use codex primary | source
|
|
|
258
255
|
说明:
|
|
259
256
|
- `unset`:全局需要清理的环境变量。按 type 的默认清理键只会对当前 type 生效,不会影响其他 type。
|
|
260
257
|
- `defaultProfiles`:可选;`codex`/`claude` 对应的默认 profile 名称或 key,供 `codenv auto` 使用。
|
|
261
|
-
- `codexStatusline`:可选;在启动 `codex`
|
|
262
|
-
- `
|
|
263
|
-
-
|
|
264
|
-
- `updateIntervalMs`:数字;状态栏命令的刷新间隔(毫秒)。
|
|
265
|
-
- `timeoutMs`:数字;状态栏命令超时(毫秒)。
|
|
258
|
+
- `codexStatusline`:可选;在启动 `codex` 时写入官方 Codex TUI 状态栏配置。
|
|
259
|
+
- `items`:字符串数组;按顺序写入 `~/.codex/config.toml` 的 `tui.status_line`。
|
|
260
|
+
- 支持的 item ID 包括:`model-name`、`model-with-reasoning`、`current-dir`、`project-root`、`git-branch`、`context-remaining`、`context-used`、`five-hour-limit`、`weekly-limit`、`codex-version`、`context-window-size`、`used-tokens`、`total-input-tokens`、`total-output-tokens`、`session-id`。
|
|
266
261
|
- `configPath`:可选;覆盖 `~/.codex/config.toml`(也可用 `CODE_ENV_CODEX_CONFIG_PATH`)。
|
|
262
|
+
- 若未设置 `items`,`codenv` 不会改写 Codex 状态栏配置(使用 Codex 默认值)。
|
|
267
263
|
- `claudeStatusline`:可选;在启动 `claude` 时写入 Claude Code statusLine 配置。
|
|
268
264
|
- `command`:字符串(或字符串数组;数组会被拼接成单个命令字符串)。
|
|
269
265
|
- `type`:字符串;statusLine 类型(默认 `command`)。
|
package/bin/commands/list.js
CHANGED
|
@@ -12,7 +12,16 @@ function printList(config, configPath) {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
15
|
+
const usagePath = (0, usage_1.getUsagePath)(config, configPath);
|
|
16
|
+
if (usagePath) {
|
|
17
|
+
(0, usage_1.syncUsageFromSessions)(config, configPath, usagePath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// ignore usage sync errors
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const usageTotals = (0, usage_1.readUsageTotalsIndex)(config, configPath, false);
|
|
16
25
|
const usageCosts = (0, usage_1.readUsageCostIndex)(config, configPath, false);
|
|
17
26
|
if (usageTotals) {
|
|
18
27
|
for (const row of rows) {
|
package/bin/statusline/codex.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureCodexStatuslineConfig = ensureCodexStatuslineConfig;
|
|
4
4
|
/**
|
|
5
|
-
* Codex CLI status line integration
|
|
5
|
+
* Codex CLI status line integration (official schema)
|
|
6
6
|
*/
|
|
7
7
|
const fs = require("fs");
|
|
8
8
|
const os = require("os");
|
|
@@ -10,16 +10,6 @@ const path = require("path");
|
|
|
10
10
|
const utils_1 = require("../shell/utils");
|
|
11
11
|
const ui_1 = require("../ui");
|
|
12
12
|
const DEFAULT_CODEX_CONFIG_PATH = path.join(os.homedir(), ".codex", "config.toml");
|
|
13
|
-
const DEFAULT_STATUSLINE_COMMAND = [
|
|
14
|
-
"codenv",
|
|
15
|
-
"statusline",
|
|
16
|
-
"--type",
|
|
17
|
-
"codex",
|
|
18
|
-
"--sync-usage",
|
|
19
|
-
];
|
|
20
|
-
const DEFAULT_SHOW_HINTS = false;
|
|
21
|
-
const DEFAULT_UPDATE_INTERVAL_MS = 300;
|
|
22
|
-
const DEFAULT_TIMEOUT_MS = 1000;
|
|
23
13
|
function parseBooleanEnv(value) {
|
|
24
14
|
if (value === undefined)
|
|
25
15
|
return null;
|
|
@@ -44,6 +34,24 @@ function resolveCodexConfigPath(config) {
|
|
|
44
34
|
}
|
|
45
35
|
return DEFAULT_CODEX_CONFIG_PATH;
|
|
46
36
|
}
|
|
37
|
+
function resolveDesiredStatusLineItems(config) {
|
|
38
|
+
var _a;
|
|
39
|
+
const raw = (_a = config.codexStatusline) === null || _a === void 0 ? void 0 : _a.items;
|
|
40
|
+
if (!Array.isArray(raw))
|
|
41
|
+
return null;
|
|
42
|
+
return raw
|
|
43
|
+
.map((entry) => String(entry).trim())
|
|
44
|
+
.filter((entry) => entry);
|
|
45
|
+
}
|
|
46
|
+
function resolveDesiredStatusLineConfig(config) {
|
|
47
|
+
const statusLineItems = resolveDesiredStatusLineItems(config);
|
|
48
|
+
if (statusLineItems === null)
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
statusLineItems,
|
|
52
|
+
configPath: resolveCodexConfigPath(config),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
47
55
|
function readConfig(filePath) {
|
|
48
56
|
if (!fs.existsSync(filePath))
|
|
49
57
|
return "";
|
|
@@ -73,88 +81,75 @@ function stripInlineComment(value) {
|
|
|
73
81
|
}
|
|
74
82
|
return value.trim();
|
|
75
83
|
}
|
|
76
|
-
function
|
|
77
|
-
const trimmed = value.trim();
|
|
78
|
-
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
79
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
80
|
-
return trimmed.slice(1, -1);
|
|
81
|
-
}
|
|
82
|
-
return trimmed;
|
|
83
|
-
}
|
|
84
|
-
function parseCommandValue(raw) {
|
|
85
|
-
const trimmed = raw.trim();
|
|
86
|
-
if (!trimmed)
|
|
87
|
-
return null;
|
|
88
|
-
if (trimmed.startsWith("[")) {
|
|
89
|
-
const items = [];
|
|
90
|
-
const regex = /"((?:\\.|[^"\\])*)"/g;
|
|
91
|
-
let match = null;
|
|
92
|
-
while ((match = regex.exec(trimmed))) {
|
|
93
|
-
const item = match[1].replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
94
|
-
items.push(item);
|
|
95
|
-
}
|
|
96
|
-
return items.length > 0 ? items : null;
|
|
97
|
-
}
|
|
98
|
-
return unquote(trimmed);
|
|
99
|
-
}
|
|
100
|
-
function tokenizeCommand(command) {
|
|
101
|
-
const tokens = [];
|
|
102
|
-
let current = "";
|
|
84
|
+
function hasUnquotedClosingBracket(value) {
|
|
103
85
|
let inSingle = false;
|
|
104
86
|
let inDouble = false;
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
current += ch;
|
|
110
|
-
escape = false;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (ch === "\\" && !inSingle) {
|
|
114
|
-
escape = true;
|
|
87
|
+
for (let i = 0; i < value.length; i++) {
|
|
88
|
+
const ch = value[i];
|
|
89
|
+
if (ch === "\"" && !inSingle && value[i - 1] !== "\\") {
|
|
90
|
+
inDouble = !inDouble;
|
|
115
91
|
continue;
|
|
116
92
|
}
|
|
117
|
-
if (ch === "'" && !inDouble) {
|
|
93
|
+
if (ch === "'" && !inDouble && value[i - 1] !== "\\") {
|
|
118
94
|
inSingle = !inSingle;
|
|
119
95
|
continue;
|
|
120
96
|
}
|
|
121
|
-
if (ch === "
|
|
122
|
-
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (!inSingle && !inDouble && /\s/.test(ch)) {
|
|
126
|
-
if (current) {
|
|
127
|
-
tokens.push(current);
|
|
128
|
-
current = "";
|
|
129
|
-
}
|
|
130
|
-
continue;
|
|
97
|
+
if (!inSingle && !inDouble && ch === "]") {
|
|
98
|
+
return true;
|
|
131
99
|
}
|
|
132
|
-
current += ch;
|
|
133
100
|
}
|
|
134
|
-
|
|
135
|
-
tokens.push(current);
|
|
136
|
-
return tokens;
|
|
137
|
-
}
|
|
138
|
-
function parseBooleanValue(raw) {
|
|
139
|
-
const normalized = raw.trim().toLowerCase();
|
|
140
|
-
if (normalized === "true")
|
|
141
|
-
return true;
|
|
142
|
-
if (normalized === "false")
|
|
143
|
-
return false;
|
|
144
|
-
return null;
|
|
101
|
+
return false;
|
|
145
102
|
}
|
|
146
|
-
function
|
|
103
|
+
function parseTomlStringArray(raw) {
|
|
147
104
|
const trimmed = raw.trim();
|
|
148
|
-
if (!trimmed)
|
|
105
|
+
if (!trimmed.startsWith("[") || !hasUnquotedClosingBracket(trimmed)) {
|
|
149
106
|
return null;
|
|
150
|
-
|
|
151
|
-
if (
|
|
107
|
+
}
|
|
108
|
+
if (/^\[\s*\]$/.test(trimmed))
|
|
109
|
+
return [];
|
|
110
|
+
const items = [];
|
|
111
|
+
const regex = /"((?:\\.|[^"\\])*)"|'([^']*)'/g;
|
|
112
|
+
let match = null;
|
|
113
|
+
while ((match = regex.exec(trimmed))) {
|
|
114
|
+
if (match[0].startsWith("\"")) {
|
|
115
|
+
items.push(match[1].replace(/\\"/g, "\"").replace(/\\\\/g, "\\"));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
items.push(match[2] || "");
|
|
119
|
+
}
|
|
120
|
+
if (items.length === 0)
|
|
152
121
|
return null;
|
|
153
|
-
return
|
|
122
|
+
return items;
|
|
123
|
+
}
|
|
124
|
+
function parseStatusLineItems(sectionText) {
|
|
125
|
+
const lines = sectionText.split(/\r?\n/).slice(1);
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const trimmed = lines[i].trim();
|
|
128
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
129
|
+
continue;
|
|
130
|
+
const matchLine = /^status_line\s*=\s*(.*)$/.exec(trimmed);
|
|
131
|
+
if (!matchLine)
|
|
132
|
+
continue;
|
|
133
|
+
const value = stripInlineComment(matchLine[1]);
|
|
134
|
+
if (!value.startsWith("["))
|
|
135
|
+
return null;
|
|
136
|
+
const parts = [value];
|
|
137
|
+
if (!hasUnquotedClosingBracket(value)) {
|
|
138
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
139
|
+
const next = stripInlineComment(lines[j]);
|
|
140
|
+
if (!next)
|
|
141
|
+
continue;
|
|
142
|
+
parts.push(next);
|
|
143
|
+
if (hasUnquotedClosingBracket(next))
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return parseTomlStringArray(parts.join(" "));
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
154
150
|
}
|
|
155
|
-
function
|
|
151
|
+
function parseSectionByHeader(text, headerRegex) {
|
|
156
152
|
var _a;
|
|
157
|
-
const headerRegex = /^\s*\[tui\.status_line\]\s*$/m;
|
|
158
153
|
const match = headerRegex.exec(text);
|
|
159
154
|
if (!match || match.index === undefined)
|
|
160
155
|
return null;
|
|
@@ -165,132 +160,83 @@ function parseStatusLineSection(text) {
|
|
|
165
160
|
const end = nextHeaderMatch
|
|
166
161
|
? afterHeader + ((_a = nextHeaderMatch.index) !== null && _a !== void 0 ? _a : rest.length)
|
|
167
162
|
: text.length;
|
|
168
|
-
const sectionText = text.slice(start, end).trimEnd();
|
|
169
|
-
const lines = sectionText.split(/\r?\n/).slice(1);
|
|
170
|
-
const config = {
|
|
171
|
-
command: null,
|
|
172
|
-
showHints: null,
|
|
173
|
-
updateIntervalMs: null,
|
|
174
|
-
timeoutMs: null,
|
|
175
|
-
};
|
|
176
|
-
for (const line of lines) {
|
|
177
|
-
const trimmed = line.trim();
|
|
178
|
-
if (!trimmed)
|
|
179
|
-
continue;
|
|
180
|
-
if (trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
181
|
-
continue;
|
|
182
|
-
const matchLine = /^([A-Za-z0-9_]+)\s*=\s*(.+)$/.exec(trimmed);
|
|
183
|
-
if (!matchLine)
|
|
184
|
-
continue;
|
|
185
|
-
const key = matchLine[1];
|
|
186
|
-
const rawValue = stripInlineComment(matchLine[2]);
|
|
187
|
-
if (key === "command") {
|
|
188
|
-
config.command = parseCommandValue(rawValue);
|
|
189
|
-
}
|
|
190
|
-
else if (key === "show_hints") {
|
|
191
|
-
config.showHints = parseBooleanValue(rawValue);
|
|
192
|
-
}
|
|
193
|
-
else if (key === "update_interval_ms") {
|
|
194
|
-
config.updateIntervalMs = parseNumberValue(rawValue);
|
|
195
|
-
}
|
|
196
|
-
else if (key === "timeout_ms") {
|
|
197
|
-
config.timeoutMs = parseNumberValue(rawValue);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
163
|
return {
|
|
201
164
|
start,
|
|
202
165
|
end,
|
|
203
|
-
sectionText,
|
|
204
|
-
config,
|
|
166
|
+
sectionText: text.slice(start, end).trimEnd(),
|
|
205
167
|
};
|
|
206
168
|
}
|
|
207
|
-
function
|
|
208
|
-
|
|
169
|
+
function parseTuiSection(text) {
|
|
170
|
+
const section = parseSectionByHeader(text, /^\s*\[tui\]\s*$/m);
|
|
171
|
+
if (!section)
|
|
209
172
|
return null;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
173
|
+
return {
|
|
174
|
+
start: section.start,
|
|
175
|
+
end: section.end,
|
|
176
|
+
sectionText: section.sectionText,
|
|
177
|
+
config: {
|
|
178
|
+
statusLineItems: parseStatusLineItems(section.sectionText),
|
|
179
|
+
},
|
|
180
|
+
};
|
|
217
181
|
}
|
|
218
|
-
function
|
|
182
|
+
function parseLegacyStatusLineSection(text) {
|
|
183
|
+
return parseSectionByHeader(text, /^\s*\[tui\.status_line\]\s*$/m);
|
|
184
|
+
}
|
|
185
|
+
function statusLineItemsMatch(existing, desired) {
|
|
219
186
|
if (!existing)
|
|
220
187
|
return false;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
188
|
+
if (existing.length !== desired.length)
|
|
189
|
+
return false;
|
|
190
|
+
for (let i = 0; i < desired.length; i++) {
|
|
191
|
+
if (existing[i] !== desired[i])
|
|
225
192
|
return false;
|
|
226
|
-
for (let i = 0; i < desiredTokens.length; i++) {
|
|
227
|
-
if (existingTokens[i] !== desiredTokens[i])
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
return true;
|
|
231
193
|
}
|
|
232
|
-
if (typeof existing === "string" && typeof desired === "string") {
|
|
233
|
-
return existing.trim() === desired.trim();
|
|
234
|
-
}
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
function configMatches(config, desired) {
|
|
238
|
-
if (!commandMatches(config.command, desired.command))
|
|
239
|
-
return false;
|
|
240
|
-
if (config.showHints !== desired.showHints)
|
|
241
|
-
return false;
|
|
242
|
-
if (config.updateIntervalMs !== desired.updateIntervalMs)
|
|
243
|
-
return false;
|
|
244
|
-
if (config.timeoutMs !== desired.timeoutMs)
|
|
245
|
-
return false;
|
|
246
194
|
return true;
|
|
247
195
|
}
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
const raw = (_a = config.codexStatusline) === null || _a === void 0 ? void 0 : _a.command;
|
|
251
|
-
if (typeof raw === "string") {
|
|
252
|
-
const trimmed = raw.trim();
|
|
253
|
-
if (trimmed)
|
|
254
|
-
return trimmed;
|
|
255
|
-
}
|
|
256
|
-
else if (Array.isArray(raw)) {
|
|
257
|
-
const cleaned = raw
|
|
258
|
-
.map((entry) => String(entry).trim())
|
|
259
|
-
.filter((entry) => entry);
|
|
260
|
-
if (cleaned.length > 0)
|
|
261
|
-
return cleaned;
|
|
262
|
-
}
|
|
263
|
-
return DEFAULT_STATUSLINE_COMMAND;
|
|
196
|
+
function configMatches(config, desired) {
|
|
197
|
+
return statusLineItemsMatch(config.statusLineItems, desired.statusLineItems);
|
|
264
198
|
}
|
|
265
|
-
function
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const updateIntervalMs = (_d = (_c = config.codexStatusline) === null || _c === void 0 ? void 0 : _c.updateIntervalMs) !== null && _d !== void 0 ? _d : DEFAULT_UPDATE_INTERVAL_MS;
|
|
269
|
-
const timeoutMs = (_f = (_e = config.codexStatusline) === null || _e === void 0 ? void 0 : _e.timeoutMs) !== null && _f !== void 0 ? _f : DEFAULT_TIMEOUT_MS;
|
|
270
|
-
const command = resolveDesiredCommand(config);
|
|
271
|
-
const configPath = resolveCodexConfigPath(config);
|
|
272
|
-
return {
|
|
273
|
-
command,
|
|
274
|
-
showHints,
|
|
275
|
-
updateIntervalMs,
|
|
276
|
-
timeoutMs,
|
|
277
|
-
configPath,
|
|
278
|
-
};
|
|
199
|
+
function formatStatusLineItems(items) {
|
|
200
|
+
const parts = items.map((item) => JSON.stringify(item)).join(", ");
|
|
201
|
+
return `[${parts}]`;
|
|
279
202
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
203
|
+
function removeStatusLineSettingLines(lines) {
|
|
204
|
+
const kept = [];
|
|
205
|
+
let inMultilineArray = false;
|
|
206
|
+
for (const line of lines) {
|
|
207
|
+
const trimmed = line.trim();
|
|
208
|
+
if (inMultilineArray) {
|
|
209
|
+
const value = stripInlineComment(trimmed);
|
|
210
|
+
if (hasUnquotedClosingBracket(value)) {
|
|
211
|
+
inMultilineArray = false;
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const matchLine = /^status_line\s*=\s*(.*)$/.exec(trimmed);
|
|
216
|
+
if (!matchLine) {
|
|
217
|
+
kept.push(line);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const value = stripInlineComment(matchLine[1]);
|
|
221
|
+
if (value.startsWith("[") && !hasUnquotedClosingBracket(value)) {
|
|
222
|
+
inMultilineArray = true;
|
|
223
|
+
}
|
|
284
224
|
}
|
|
285
|
-
return
|
|
225
|
+
return kept;
|
|
286
226
|
}
|
|
287
|
-
function
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
`
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
227
|
+
function buildTuiSection(section, desired) {
|
|
228
|
+
const statusLine = `status_line = ${formatStatusLineItems(desired.statusLineItems)}`;
|
|
229
|
+
if (!section) {
|
|
230
|
+
return `[tui]\n${statusLine}\n`;
|
|
231
|
+
}
|
|
232
|
+
const lines = section.sectionText.split(/\r?\n/);
|
|
233
|
+
const header = lines[0].trim() === "[tui]" ? lines[0] : "[tui]";
|
|
234
|
+
const body = removeStatusLineSettingLines(lines.slice(1));
|
|
235
|
+
while (body.length > 0 && body[body.length - 1].trim() === "") {
|
|
236
|
+
body.pop();
|
|
237
|
+
}
|
|
238
|
+
body.push(statusLine);
|
|
239
|
+
return `${header}\n${body.join("\n")}\n`;
|
|
294
240
|
}
|
|
295
241
|
function upsertSection(text, section, newSection) {
|
|
296
242
|
if (!section) {
|
|
@@ -309,6 +255,15 @@ function upsertSection(text, section, newSection) {
|
|
|
309
255
|
suffix = `\n${suffix}`;
|
|
310
256
|
return `${prefix}${newSection}${suffix}`;
|
|
311
257
|
}
|
|
258
|
+
function removeSection(text, section) {
|
|
259
|
+
let prefix = text.slice(0, section.start);
|
|
260
|
+
let suffix = text.slice(section.end);
|
|
261
|
+
if (prefix && !prefix.endsWith("\n"))
|
|
262
|
+
prefix += "\n";
|
|
263
|
+
if (suffix && !suffix.startsWith("\n"))
|
|
264
|
+
suffix = `\n${suffix}`;
|
|
265
|
+
return `${prefix}${suffix}`;
|
|
266
|
+
}
|
|
312
267
|
function writeConfig(filePath, text) {
|
|
313
268
|
try {
|
|
314
269
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -324,22 +279,32 @@ async function ensureCodexStatuslineConfig(config, enabled) {
|
|
|
324
279
|
if (!enabled || disabled)
|
|
325
280
|
return false;
|
|
326
281
|
const desired = resolveDesiredStatusLineConfig(config);
|
|
282
|
+
if (!desired)
|
|
283
|
+
return false;
|
|
327
284
|
const configPath = desired.configPath;
|
|
328
285
|
const raw = readConfig(configPath);
|
|
329
|
-
const
|
|
286
|
+
const legacySection = parseLegacyStatusLineSection(raw);
|
|
287
|
+
const base = legacySection ? removeSection(raw, legacySection) : raw;
|
|
288
|
+
const section = parseTuiSection(base);
|
|
330
289
|
if (section && configMatches(section.config, desired))
|
|
331
290
|
return false;
|
|
332
291
|
const force = parseBooleanEnv(process.env.CODE_ENV_CODEX_STATUSLINE_FORCE) === true;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
console.log(
|
|
292
|
+
const hasExistingStatusLine = Boolean((section && Array.isArray(section.config.statusLineItems)) || legacySection);
|
|
293
|
+
if (hasExistingStatusLine && !force) {
|
|
294
|
+
console.log(`codenv: existing Codex tui.status_line config in ${configPath}:`);
|
|
295
|
+
if (section && Array.isArray(section.config.statusLineItems)) {
|
|
296
|
+
console.log(section.sectionText);
|
|
297
|
+
}
|
|
298
|
+
else if (legacySection) {
|
|
299
|
+
console.log(legacySection.sectionText);
|
|
300
|
+
}
|
|
336
301
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
337
|
-
console.warn("codenv: no TTY available to confirm status_line overwrite.");
|
|
302
|
+
console.warn("codenv: no TTY available to confirm tui.status_line overwrite.");
|
|
338
303
|
return false;
|
|
339
304
|
}
|
|
340
305
|
const rl = (0, ui_1.createReadline)();
|
|
341
306
|
try {
|
|
342
|
-
const confirm = await (0, ui_1.askConfirm)(rl, "Overwrite Codex status_line config? (y/N): ");
|
|
307
|
+
const confirm = await (0, ui_1.askConfirm)(rl, "Overwrite Codex tui.status_line config? (y/N): ");
|
|
343
308
|
if (!confirm)
|
|
344
309
|
return false;
|
|
345
310
|
}
|
|
@@ -347,9 +312,9 @@ async function ensureCodexStatuslineConfig(config, enabled) {
|
|
|
347
312
|
rl.close();
|
|
348
313
|
}
|
|
349
314
|
}
|
|
350
|
-
const updated = upsertSection(
|
|
315
|
+
const updated = upsertSection(base, section, buildTuiSection(section, desired));
|
|
351
316
|
if (!writeConfig(configPath, updated)) {
|
|
352
|
-
console.error("codenv: failed to write Codex config; status_line not updated.");
|
|
317
|
+
console.error("codenv: failed to write Codex config; tui.status_line not updated.");
|
|
353
318
|
return false;
|
|
354
319
|
}
|
|
355
320
|
return true;
|
package/bin/statusline/index.js
CHANGED
|
@@ -19,8 +19,17 @@ function buildStatuslineResult(args, config, configPath) {
|
|
|
19
19
|
}
|
|
20
20
|
let type = (0, input_1.normalizeTypeValue)(typeCandidate);
|
|
21
21
|
const envProfile = (0, input_1.resolveEnvProfile)(type);
|
|
22
|
-
|
|
22
|
+
let profileKey = (0, utils_1.firstNonEmpty)(args.profileKey, envProfile.key, inputProfile ? inputProfile.key : null);
|
|
23
23
|
let profileName = (0, utils_1.firstNonEmpty)(args.profileName, envProfile.name, inputProfile ? inputProfile.name : null);
|
|
24
|
+
const terminalTag = process.env.CODE_ENV_TERMINAL_TAG || null;
|
|
25
|
+
if (!profileKey && !profileName) {
|
|
26
|
+
const fallback = (0, usage_1.resolveProfileFromLog)(config, configPath, (0, type_1.normalizeType)(type || ""), terminalTag);
|
|
27
|
+
if (fallback) {
|
|
28
|
+
profileKey = fallback.profileKey;
|
|
29
|
+
profileName = fallback.profileName;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const sessionId = (0, input_1.getSessionId)(stdinInput);
|
|
24
33
|
if (profileKey && !profileName && config.profiles && config.profiles[profileKey]) {
|
|
25
34
|
const profile = config.profiles[profileKey];
|
|
26
35
|
profileName = (0, type_1.getProfileDisplayName)(profileKey, profile, type || undefined);
|
|
@@ -37,7 +46,6 @@ function buildStatuslineResult(args, config, configPath) {
|
|
|
37
46
|
type = inferred;
|
|
38
47
|
}
|
|
39
48
|
const cwd = (0, utils_1.firstNonEmpty)(args.cwd, process.env.CODE_ENV_CWD, (0, input_1.getWorkspaceDir)(stdinInput), stdinInput ? stdinInput.cwd : null, process.cwd());
|
|
40
|
-
const sessionId = (0, input_1.getSessionId)(stdinInput);
|
|
41
49
|
const usageType = (0, type_1.normalizeType)(type || "");
|
|
42
50
|
const stdinUsageTotals = (0, usage_2.getUsageTotalsFromInput)(stdinInput, usageType);
|
|
43
51
|
const shouldSyncUsageFromSessions = args.syncUsage && !stdinUsageTotals;
|