@praeviso/code-env-switch 0.1.7 → 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 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
- "command": ["codenv", "statusline", "--type", "codex", "--sync-usage"],
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
- - `command`: string or string[]; command passed to Codex `tui.status_line.command`.
264
- - `showHints`: boolean; whether Codex footer hints are appended when the status line is active.
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
- "command": ["codenv", "statusline", "--type", "codex", "--sync-usage"],
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` 时写入 Codex TUI status line 配置。
262
- - `command`:字符串或字符串数组;写入 Codex 的 `tui.status_line.command`。
263
- - `showHints`:布尔值;状态栏激活时是否拼接 footer 提示。
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`)。
@@ -12,7 +12,16 @@ function printList(config, configPath) {
12
12
  return;
13
13
  }
14
14
  try {
15
- const usageTotals = (0, usage_1.readUsageTotalsIndex)(config, configPath, true);
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) {
@@ -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 unquote(value) {
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 escape = false;
106
- for (let i = 0; i < command.length; i++) {
107
- const ch = command[i];
108
- if (escape) {
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 === "\"" && !inSingle) {
122
- inDouble = !inDouble;
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
- if (current)
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 parseNumberValue(raw) {
103
+ function parseTomlStringArray(raw) {
147
104
  const trimmed = raw.trim();
148
- if (!trimmed)
105
+ if (!trimmed.startsWith("[") || !hasUnquotedClosingBracket(trimmed)) {
149
106
  return null;
150
- const parsed = Number(trimmed);
151
- if (!Number.isFinite(parsed))
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 Math.floor(parsed);
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 parseStatusLineSection(text) {
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 commandToArray(value) {
208
- if (!value)
169
+ function parseTuiSection(text) {
170
+ const section = parseSectionByHeader(text, /^\s*\[tui\]\s*$/m);
171
+ if (!section)
209
172
  return null;
210
- if (Array.isArray(value)) {
211
- return value.map((entry) => String(entry));
212
- }
213
- const trimmed = value.trim();
214
- if (!trimmed)
215
- return null;
216
- return tokenizeCommand(trimmed);
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 commandMatches(existing, desired) {
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
- const existingTokens = commandToArray(existing);
222
- const desiredTokens = commandToArray(desired);
223
- if (existingTokens && desiredTokens) {
224
- if (existingTokens.length !== desiredTokens.length)
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 resolveDesiredCommand(config) {
249
- var _a;
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 resolveDesiredStatusLineConfig(config) {
266
- var _a, _b, _c, _d, _e, _f;
267
- const showHints = (_b = (_a = config.codexStatusline) === null || _a === void 0 ? void 0 : _a.showHints) !== null && _b !== void 0 ? _b : DEFAULT_SHOW_HINTS;
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 formatCommandValue(command) {
281
- if (Array.isArray(command)) {
282
- const parts = command.map((part) => JSON.stringify(part)).join(", ");
283
- return `[${parts}]`;
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 JSON.stringify(command);
225
+ return kept;
286
226
  }
287
- function buildStatusLineSection(desired) {
288
- const commandText = formatCommandValue(desired.command);
289
- return (`[tui.status_line]\n` +
290
- `command = ${commandText}\n` +
291
- `show_hints = ${desired.showHints ? "true" : "false"}\n` +
292
- `update_interval_ms = ${desired.updateIntervalMs}\n` +
293
- `timeout_ms = ${desired.timeoutMs}\n`);
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 section = parseStatusLineSection(raw);
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
- if (section && !force) {
334
- console.log(`codenv: existing Codex status_line config in ${configPath}:`);
335
- console.log(section.sectionText);
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(raw, section, buildStatusLineSection(desired));
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;
@@ -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
- const profileKey = (0, utils_1.firstNonEmpty)(args.profileKey, envProfile.key, inputProfile ? inputProfile.key : null);
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,9 +46,9 @@ 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);
51
+ const shouldSyncUsageFromSessions = args.syncUsage && !stdinUsageTotals;
43
52
  const model = (0, utils_1.firstNonEmpty)(args.model, process.env.CODE_ENV_MODEL, (0, input_1.getModelFromInput)(stdinInput));
44
53
  const modelProvider = (0, utils_1.firstNonEmpty)(process.env.CODE_ENV_MODEL_PROVIDER, (0, input_1.getModelProviderFromInput)(stdinInput));
45
54
  (0, debug_1.appendStatuslineDebug)(configPath, {
@@ -86,7 +95,7 @@ function buildStatuslineResult(args, config, configPath) {
86
95
  usage.inputTokens !== null ||
87
96
  usage.outputTokens !== null;
88
97
  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);
98
+ const recordsUsage = (0, usage_2.resolveUsageFromRecords)(config, configPath, type, profileKey, profileName, shouldSyncUsageFromSessions);
90
99
  let finalUsage = hasExplicitUsage ? usage : null;
91
100
  if (!finalUsage && args.syncUsage && recordsUsage) {
92
101
  finalUsage = recordsUsage;
@@ -131,12 +140,12 @@ function buildStatuslineResult(args, config, configPath) {
131
140
  }
132
141
  else {
133
142
  const sessionCostFromRecords = sessionId
134
- ? (0, usage_1.readUsageSessionCost)(config, configPath, type, sessionId, args.syncUsage)
143
+ ? (0, usage_1.readUsageSessionCost)(config, configPath, type, sessionId, shouldSyncUsageFromSessions)
135
144
  : null;
136
145
  sessionCost =
137
146
  sessionCostFromRecords !== null && sessionCostFromRecords !== void 0 ? sessionCostFromRecords : (sessionUsage ? (0, pricing_1.calculateUsageCost)(sessionUsage, pricing) : null);
138
147
  }
139
- const costIndex = (0, usage_1.readUsageCostIndex)(config, configPath, args.syncUsage);
148
+ const costIndex = (0, usage_1.readUsageCostIndex)(config, configPath, shouldSyncUsageFromSessions);
140
149
  const costTotals = costIndex
141
150
  ? (0, usage_1.resolveUsageCostForProfile)(costIndex, type, profileKey, profileName)
142
151
  : null;