@praeviso/code-env-switch 0.1.2 → 0.1.4
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/bin/statusline/debug.js +42 -0
- package/bin/statusline/format.js +60 -0
- package/bin/statusline/git.js +96 -0
- package/bin/statusline/index.js +73 -400
- package/bin/statusline/input.js +249 -0
- package/bin/statusline/style.js +22 -0
- package/bin/statusline/types.js +2 -0
- package/bin/statusline/usage.js +123 -0
- package/bin/statusline/utils.js +35 -0
- package/bin/usage/index.js +133 -10
- package/package.json +1 -1
- package/src/statusline/debug.ts +40 -0
- package/src/statusline/format.ts +68 -0
- package/src/statusline/git.ts +82 -0
- package/src/statusline/index.ts +93 -470
- package/src/statusline/input.ts +300 -0
- package/src/statusline/style.ts +19 -0
- package/src/statusline/types.ts +105 -0
- package/src/statusline/usage.ts +175 -0
- package/src/statusline/utils.ts +27 -0
- package/src/usage/index.ts +156 -10
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readStdinJson = readStdinJson;
|
|
4
|
+
exports.normalizeTypeValue = normalizeTypeValue;
|
|
5
|
+
exports.detectTypeFromEnv = detectTypeFromEnv;
|
|
6
|
+
exports.resolveEnvProfile = resolveEnvProfile;
|
|
7
|
+
exports.getModelFromInput = getModelFromInput;
|
|
8
|
+
exports.getModelProviderFromInput = getModelProviderFromInput;
|
|
9
|
+
exports.getInputProfile = getInputProfile;
|
|
10
|
+
exports.getInputUsage = getInputUsage;
|
|
11
|
+
exports.getSessionId = getSessionId;
|
|
12
|
+
exports.getContextUsedTokens = getContextUsedTokens;
|
|
13
|
+
exports.getContextLeftPercent = getContextLeftPercent;
|
|
14
|
+
exports.getWorkspaceDir = getWorkspaceDir;
|
|
15
|
+
exports.getGitStatusFromInput = getGitStatusFromInput;
|
|
16
|
+
const fs = require("fs");
|
|
17
|
+
const constants_1 = require("../constants");
|
|
18
|
+
const type_1 = require("../profile/type");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
20
|
+
function readStdinJson() {
|
|
21
|
+
if (process.stdin.isTTY)
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
const raw = fs.readFileSync(0, "utf8");
|
|
25
|
+
const trimmed = raw.trim();
|
|
26
|
+
if (!trimmed)
|
|
27
|
+
return null;
|
|
28
|
+
const parsed = JSON.parse(trimmed);
|
|
29
|
+
if (!(0, utils_1.isRecord)(parsed))
|
|
30
|
+
return null;
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function normalizeTypeValue(value) {
|
|
38
|
+
if (!value)
|
|
39
|
+
return null;
|
|
40
|
+
const normalized = (0, type_1.normalizeType)(value);
|
|
41
|
+
if (normalized)
|
|
42
|
+
return normalized;
|
|
43
|
+
const trimmed = String(value).trim();
|
|
44
|
+
return trimmed ? trimmed : null;
|
|
45
|
+
}
|
|
46
|
+
function detectTypeFromEnv() {
|
|
47
|
+
const matches = constants_1.DEFAULT_PROFILE_TYPES.filter((type) => {
|
|
48
|
+
const suffix = type.toUpperCase();
|
|
49
|
+
return (process.env[`CODE_ENV_PROFILE_KEY_${suffix}`] ||
|
|
50
|
+
process.env[`CODE_ENV_PROFILE_NAME_${suffix}`]);
|
|
51
|
+
});
|
|
52
|
+
if (matches.length === 1)
|
|
53
|
+
return matches[0];
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function resolveEnvProfile(type) {
|
|
57
|
+
const genericKey = process.env.CODE_ENV_PROFILE_KEY || null;
|
|
58
|
+
const genericName = process.env.CODE_ENV_PROFILE_NAME || null;
|
|
59
|
+
if (!type) {
|
|
60
|
+
return { key: genericKey, name: genericName };
|
|
61
|
+
}
|
|
62
|
+
const suffix = type.toUpperCase();
|
|
63
|
+
const key = process.env[`CODE_ENV_PROFILE_KEY_${suffix}`] || genericKey;
|
|
64
|
+
const name = process.env[`CODE_ENV_PROFILE_NAME_${suffix}`] || genericName;
|
|
65
|
+
return { key: key || null, name: name || null };
|
|
66
|
+
}
|
|
67
|
+
function getModelFromInput(input) {
|
|
68
|
+
if (!input)
|
|
69
|
+
return null;
|
|
70
|
+
const raw = input.model;
|
|
71
|
+
if (!raw)
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof raw === "string")
|
|
74
|
+
return raw;
|
|
75
|
+
if ((0, utils_1.isRecord)(raw)) {
|
|
76
|
+
const displayName = raw.displayName || raw.display_name;
|
|
77
|
+
if (displayName)
|
|
78
|
+
return String(displayName);
|
|
79
|
+
if (raw.id)
|
|
80
|
+
return String(raw.id);
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function getModelProviderFromInput(input) {
|
|
85
|
+
if (!input || !input.model_provider)
|
|
86
|
+
return null;
|
|
87
|
+
const provider = String(input.model_provider).trim();
|
|
88
|
+
return provider ? provider : null;
|
|
89
|
+
}
|
|
90
|
+
function getInputProfile(input) {
|
|
91
|
+
if (!input || !(0, utils_1.isRecord)(input.profile))
|
|
92
|
+
return null;
|
|
93
|
+
return input.profile;
|
|
94
|
+
}
|
|
95
|
+
function getInputUsage(input) {
|
|
96
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
97
|
+
if (!input)
|
|
98
|
+
return null;
|
|
99
|
+
if ((0, utils_1.isRecord)(input.usage)) {
|
|
100
|
+
return input.usage;
|
|
101
|
+
}
|
|
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
|
+
}
|
|
145
|
+
}
|
|
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
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function getSessionId(input) {
|
|
192
|
+
if (!input)
|
|
193
|
+
return null;
|
|
194
|
+
return (0, utils_1.firstNonEmpty)(input.session_id, input.sessionId);
|
|
195
|
+
}
|
|
196
|
+
function getContextUsedTokens(input) {
|
|
197
|
+
if (!input)
|
|
198
|
+
return null;
|
|
199
|
+
return (0, utils_1.coerceNumber)(input.context_window_used_tokens);
|
|
200
|
+
}
|
|
201
|
+
function getContextLeftPercent(input, type) {
|
|
202
|
+
if (!input)
|
|
203
|
+
return null;
|
|
204
|
+
const raw = (0, utils_1.coerceNumber)(input.context_window_percent);
|
|
205
|
+
if (raw === null || raw < 0)
|
|
206
|
+
return null;
|
|
207
|
+
const percent = raw <= 1 ? raw * 100 : raw;
|
|
208
|
+
if (percent > 100)
|
|
209
|
+
return null;
|
|
210
|
+
const usedTokens = getContextUsedTokens(input);
|
|
211
|
+
const normalizedType = normalizeTypeValue(type);
|
|
212
|
+
const preferRemaining = normalizedType === "codex" ||
|
|
213
|
+
normalizedType === "claude" ||
|
|
214
|
+
usedTokens === null ||
|
|
215
|
+
(usedTokens <= 0 && percent >= 99);
|
|
216
|
+
const left = preferRemaining ? percent : 100 - percent;
|
|
217
|
+
return Math.max(0, Math.min(100, left));
|
|
218
|
+
}
|
|
219
|
+
function getWorkspaceDir(input) {
|
|
220
|
+
if (!input || !(0, utils_1.isRecord)(input.workspace))
|
|
221
|
+
return null;
|
|
222
|
+
const currentDir = input.workspace.current_dir;
|
|
223
|
+
if (currentDir) {
|
|
224
|
+
const trimmed = String(currentDir).trim();
|
|
225
|
+
if (trimmed)
|
|
226
|
+
return trimmed;
|
|
227
|
+
}
|
|
228
|
+
const projectDir = input.workspace.project_dir;
|
|
229
|
+
if (!projectDir)
|
|
230
|
+
return null;
|
|
231
|
+
const trimmed = String(projectDir).trim();
|
|
232
|
+
return trimmed ? trimmed : null;
|
|
233
|
+
}
|
|
234
|
+
function getGitStatusFromInput(input) {
|
|
235
|
+
if (!input || !input.git_branch)
|
|
236
|
+
return null;
|
|
237
|
+
const branch = String(input.git_branch).trim();
|
|
238
|
+
if (!branch)
|
|
239
|
+
return null;
|
|
240
|
+
return {
|
|
241
|
+
branch,
|
|
242
|
+
ahead: 0,
|
|
243
|
+
behind: 0,
|
|
244
|
+
staged: 0,
|
|
245
|
+
unstaged: 0,
|
|
246
|
+
untracked: 0,
|
|
247
|
+
conflicted: 0,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ICON_CWD = exports.ICON_REVIEW = exports.ICON_CONTEXT = exports.ICON_USAGE = exports.ICON_MODEL = exports.ICON_PROFILE = exports.ICON_GIT = void 0;
|
|
4
|
+
exports.colorize = colorize;
|
|
5
|
+
exports.dim = dim;
|
|
6
|
+
const COLOR_ENABLED = !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
7
|
+
const ANSI_RESET = "\x1b[0m";
|
|
8
|
+
exports.ICON_GIT = "⎇";
|
|
9
|
+
exports.ICON_PROFILE = "👤";
|
|
10
|
+
exports.ICON_MODEL = "⚙";
|
|
11
|
+
exports.ICON_USAGE = "⚡";
|
|
12
|
+
exports.ICON_CONTEXT = "🧠";
|
|
13
|
+
exports.ICON_REVIEW = "📝";
|
|
14
|
+
exports.ICON_CWD = "📁";
|
|
15
|
+
function colorize(text, colorCode) {
|
|
16
|
+
if (!COLOR_ENABLED)
|
|
17
|
+
return text;
|
|
18
|
+
return `\x1b[${colorCode}m${text}${ANSI_RESET}`;
|
|
19
|
+
}
|
|
20
|
+
function dim(text) {
|
|
21
|
+
return colorize(text, "2");
|
|
22
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseUsageTotalsRecord = parseUsageTotalsRecord;
|
|
4
|
+
exports.getUsageTotalsFromInput = getUsageTotalsFromInput;
|
|
5
|
+
exports.normalizeInputUsage = normalizeInputUsage;
|
|
6
|
+
exports.resolveUsageFromRecords = resolveUsageFromRecords;
|
|
7
|
+
const type_1 = require("../profile/type");
|
|
8
|
+
const usage_1 = require("../usage");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
function parseUsageTotalsRecord(record) {
|
|
11
|
+
var _a, _b, _c, _d, _e;
|
|
12
|
+
const inputTokens = (_a = (0, utils_1.firstNumber)(record.inputTokens, record.input, record.input_tokens)) !== null && _a !== void 0 ? _a : null;
|
|
13
|
+
const outputTokens = (_b = (0, utils_1.firstNumber)(record.outputTokens, record.output, record.output_tokens)) !== null && _b !== void 0 ? _b : null;
|
|
14
|
+
const totalTokens = (_c = (0, utils_1.firstNumber)(record.totalTokens, record.total, record.total_tokens)) !== null && _c !== void 0 ? _c : null;
|
|
15
|
+
const cacheRead = (_d = (0, utils_1.firstNumber)(record.cache_read_input_tokens, record.cacheReadInputTokens, record.cache_read, record.cacheRead)) !== null && _d !== void 0 ? _d : null;
|
|
16
|
+
const cacheWrite = (_e = (0, utils_1.firstNumber)(record.cache_creation_input_tokens, record.cacheCreationInputTokens, record.cache_write_input_tokens, record.cacheWriteInputTokens, record.cache_write, record.cacheWrite)) !== null && _e !== void 0 ? _e : null;
|
|
17
|
+
let computedTotal = null;
|
|
18
|
+
if (inputTokens !== null ||
|
|
19
|
+
outputTokens !== null ||
|
|
20
|
+
cacheRead !== null ||
|
|
21
|
+
cacheWrite !== null) {
|
|
22
|
+
computedTotal =
|
|
23
|
+
(inputTokens || 0) +
|
|
24
|
+
(outputTokens || 0) +
|
|
25
|
+
(cacheRead || 0) +
|
|
26
|
+
(cacheWrite || 0);
|
|
27
|
+
}
|
|
28
|
+
const resolvedTotal = totalTokens !== null && totalTokens !== void 0 ? totalTokens : computedTotal;
|
|
29
|
+
if (inputTokens === null &&
|
|
30
|
+
outputTokens === null &&
|
|
31
|
+
resolvedTotal === null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
inputTokens,
|
|
36
|
+
outputTokens,
|
|
37
|
+
totalTokens: resolvedTotal,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function getUsageTotalsFromInput(input) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
if (!input)
|
|
43
|
+
return null;
|
|
44
|
+
const contextWindow = (0, utils_1.isRecord)(input.context_window)
|
|
45
|
+
? input.context_window
|
|
46
|
+
: (0, utils_1.isRecord)(input.contextWindow)
|
|
47
|
+
? input.contextWindow
|
|
48
|
+
: null;
|
|
49
|
+
if (contextWindow) {
|
|
50
|
+
const totalInputTokens = (_a = (0, utils_1.firstNumber)(contextWindow.total_input_tokens, contextWindow.totalInputTokens)) !== null && _a !== void 0 ? _a : null;
|
|
51
|
+
const totalOutputTokens = (_b = (0, utils_1.firstNumber)(contextWindow.total_output_tokens, contextWindow.totalOutputTokens)) !== null && _b !== void 0 ? _b : null;
|
|
52
|
+
if (totalInputTokens !== null || totalOutputTokens !== null) {
|
|
53
|
+
return {
|
|
54
|
+
inputTokens: totalInputTokens,
|
|
55
|
+
outputTokens: totalOutputTokens,
|
|
56
|
+
totalTokens: (totalInputTokens || 0) + (totalOutputTokens || 0),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (typeof input.token_usage === "number") {
|
|
61
|
+
return {
|
|
62
|
+
inputTokens: null,
|
|
63
|
+
outputTokens: null,
|
|
64
|
+
totalTokens: (0, utils_1.coerceNumber)(input.token_usage),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if ((0, utils_1.isRecord)(input.token_usage)) {
|
|
68
|
+
const tokenUsage = input.token_usage;
|
|
69
|
+
const totalUsage = ((0, utils_1.isRecord)(tokenUsage.total_token_usage)
|
|
70
|
+
? tokenUsage.total_token_usage
|
|
71
|
+
: null) ||
|
|
72
|
+
((0, utils_1.isRecord)(tokenUsage.totalTokenUsage)
|
|
73
|
+
? tokenUsage.totalTokenUsage
|
|
74
|
+
: null);
|
|
75
|
+
if (totalUsage) {
|
|
76
|
+
const parsed = parseUsageTotalsRecord(totalUsage);
|
|
77
|
+
if (parsed)
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
return parseUsageTotalsRecord(tokenUsage);
|
|
81
|
+
}
|
|
82
|
+
if ((0, utils_1.isRecord)(input.usage)) {
|
|
83
|
+
return parseUsageTotalsRecord(input.usage);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
function normalizeInputUsage(inputUsage) {
|
|
88
|
+
if (!inputUsage)
|
|
89
|
+
return null;
|
|
90
|
+
const usage = {
|
|
91
|
+
todayTokens: (0, utils_1.coerceNumber)(inputUsage.todayTokens),
|
|
92
|
+
totalTokens: (0, utils_1.coerceNumber)(inputUsage.totalTokens),
|
|
93
|
+
inputTokens: (0, utils_1.coerceNumber)(inputUsage.inputTokens),
|
|
94
|
+
outputTokens: (0, utils_1.coerceNumber)(inputUsage.outputTokens),
|
|
95
|
+
};
|
|
96
|
+
const hasUsage = usage.todayTokens !== null ||
|
|
97
|
+
usage.totalTokens !== null ||
|
|
98
|
+
usage.inputTokens !== null ||
|
|
99
|
+
usage.outputTokens !== null;
|
|
100
|
+
return hasUsage ? usage : null;
|
|
101
|
+
}
|
|
102
|
+
function resolveUsageFromRecords(config, configPath, type, profileKey, profileName, syncUsage) {
|
|
103
|
+
try {
|
|
104
|
+
const normalized = (0, type_1.normalizeType)(type || "");
|
|
105
|
+
if (!normalized || (!profileKey && !profileName))
|
|
106
|
+
return null;
|
|
107
|
+
const totals = (0, usage_1.readUsageTotalsIndex)(config, configPath, syncUsage);
|
|
108
|
+
if (!totals)
|
|
109
|
+
return null;
|
|
110
|
+
const usage = (0, usage_1.resolveUsageTotalsForProfile)(totals, normalized, profileKey, profileName);
|
|
111
|
+
if (!usage)
|
|
112
|
+
return null;
|
|
113
|
+
return {
|
|
114
|
+
todayTokens: usage.today,
|
|
115
|
+
totalTokens: usage.total,
|
|
116
|
+
inputTokens: null,
|
|
117
|
+
outputTokens: null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isRecord = isRecord;
|
|
4
|
+
exports.firstNonEmpty = firstNonEmpty;
|
|
5
|
+
exports.coerceNumber = coerceNumber;
|
|
6
|
+
exports.firstNumber = firstNumber;
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function firstNonEmpty(...values) {
|
|
11
|
+
for (const value of values) {
|
|
12
|
+
if (value === null || value === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
const text = String(value).trim();
|
|
15
|
+
if (text)
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function coerceNumber(value) {
|
|
21
|
+
if (value === null || value === undefined || value === "")
|
|
22
|
+
return null;
|
|
23
|
+
const num = Number(value);
|
|
24
|
+
if (!Number.isFinite(num))
|
|
25
|
+
return null;
|
|
26
|
+
return num;
|
|
27
|
+
}
|
|
28
|
+
function firstNumber(...values) {
|
|
29
|
+
for (const value of values) {
|
|
30
|
+
const num = coerceNumber(value);
|
|
31
|
+
if (num !== null)
|
|
32
|
+
return num;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
package/bin/usage/index.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.formatTokenCount = formatTokenCount;
|
|
|
9
9
|
exports.buildUsageTotals = buildUsageTotals;
|
|
10
10
|
exports.readUsageTotalsIndex = readUsageTotalsIndex;
|
|
11
11
|
exports.resolveUsageTotalsForProfile = resolveUsageTotalsForProfile;
|
|
12
|
+
exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
|
|
12
13
|
exports.logProfileUse = logProfileUse;
|
|
13
14
|
exports.logSessionBinding = logSessionBinding;
|
|
14
15
|
exports.readSessionBindingIndex = readSessionBindingIndex;
|
|
@@ -120,6 +121,18 @@ function normalizeUsageType(type) {
|
|
|
120
121
|
const trimmed = String(type).trim();
|
|
121
122
|
return trimmed ? trimmed : null;
|
|
122
123
|
}
|
|
124
|
+
function buildSessionKey(type, sessionId) {
|
|
125
|
+
const normalized = normalizeUsageType(type || "");
|
|
126
|
+
return normalized ? `${normalized}::${sessionId}` : sessionId;
|
|
127
|
+
}
|
|
128
|
+
function toFiniteNumber(value) {
|
|
129
|
+
if (value === null || value === undefined)
|
|
130
|
+
return null;
|
|
131
|
+
const num = Number(value);
|
|
132
|
+
if (!Number.isFinite(num))
|
|
133
|
+
return null;
|
|
134
|
+
return num;
|
|
135
|
+
}
|
|
123
136
|
function buildUsageLookupKey(type, profileId) {
|
|
124
137
|
if (!profileId)
|
|
125
138
|
return null;
|
|
@@ -147,6 +160,75 @@ function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
|
|
|
147
160
|
(nameLookup && totals.byName.get(nameLookup)) ||
|
|
148
161
|
null);
|
|
149
162
|
}
|
|
163
|
+
function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd) {
|
|
164
|
+
var _a, _b, _c;
|
|
165
|
+
if (!sessionId)
|
|
166
|
+
return;
|
|
167
|
+
if (!totals)
|
|
168
|
+
return;
|
|
169
|
+
if (!profileKey && !profileName)
|
|
170
|
+
return;
|
|
171
|
+
const normalizedType = (0, type_1.normalizeType)(type || "");
|
|
172
|
+
if (!normalizedType)
|
|
173
|
+
return;
|
|
174
|
+
const usagePath = getUsagePath(config, configPath);
|
|
175
|
+
if (!usagePath)
|
|
176
|
+
return;
|
|
177
|
+
const inputTokens = (_a = toFiniteNumber(totals.inputTokens)) !== null && _a !== void 0 ? _a : 0;
|
|
178
|
+
const outputTokens = (_b = toFiniteNumber(totals.outputTokens)) !== null && _b !== void 0 ? _b : 0;
|
|
179
|
+
const totalTokens = (_c = toFiniteNumber(totals.totalTokens)) !== null && _c !== void 0 ? _c : inputTokens + outputTokens;
|
|
180
|
+
if (!Number.isFinite(totalTokens))
|
|
181
|
+
return;
|
|
182
|
+
const statePath = getUsageStatePath(usagePath, config);
|
|
183
|
+
const lockPath = `${statePath}.lock`;
|
|
184
|
+
const lockFd = acquireLock(lockPath);
|
|
185
|
+
if (lockFd === null)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const state = readUsageState(statePath);
|
|
189
|
+
const sessions = state.sessions || {};
|
|
190
|
+
const key = buildSessionKey(normalizedType, sessionId);
|
|
191
|
+
const prev = sessions[key];
|
|
192
|
+
const prevInput = prev ? prev.inputTokens : 0;
|
|
193
|
+
const prevOutput = prev ? prev.outputTokens : 0;
|
|
194
|
+
const prevTotal = prev ? prev.totalTokens : 0;
|
|
195
|
+
let deltaInput = inputTokens - prevInput;
|
|
196
|
+
let deltaOutput = outputTokens - prevOutput;
|
|
197
|
+
let deltaTotal = totalTokens - prevTotal;
|
|
198
|
+
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
199
|
+
deltaInput = inputTokens;
|
|
200
|
+
deltaOutput = outputTokens;
|
|
201
|
+
deltaTotal = totalTokens;
|
|
202
|
+
}
|
|
203
|
+
if (deltaTotal > 0) {
|
|
204
|
+
const record = {
|
|
205
|
+
ts: new Date().toISOString(),
|
|
206
|
+
type: normalizedType,
|
|
207
|
+
profileKey: profileKey || null,
|
|
208
|
+
profileName: profileName || null,
|
|
209
|
+
inputTokens: deltaInput,
|
|
210
|
+
outputTokens: deltaOutput,
|
|
211
|
+
totalTokens: deltaTotal,
|
|
212
|
+
};
|
|
213
|
+
appendUsageRecord(usagePath, record);
|
|
214
|
+
}
|
|
215
|
+
const now = new Date().toISOString();
|
|
216
|
+
sessions[key] = {
|
|
217
|
+
type: normalizedType,
|
|
218
|
+
inputTokens,
|
|
219
|
+
outputTokens,
|
|
220
|
+
totalTokens,
|
|
221
|
+
startTs: prev ? prev.startTs : now,
|
|
222
|
+
endTs: now,
|
|
223
|
+
cwd: cwd || (prev ? prev.cwd : null),
|
|
224
|
+
};
|
|
225
|
+
state.sessions = sessions;
|
|
226
|
+
writeUsageState(statePath, state);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
releaseLock(lockPath, lockFd);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
150
232
|
function logProfileUse(config, configPath, profileKey, requestedType, terminalTag, cwd) {
|
|
151
233
|
const profile = config.profiles && config.profiles[profileKey];
|
|
152
234
|
if (!profile)
|
|
@@ -285,19 +367,20 @@ function resolveProfileForSession(config, logEntries, type, sessionFile, session
|
|
|
285
367
|
}
|
|
286
368
|
function readUsageState(statePath) {
|
|
287
369
|
if (!statePath || !fs.existsSync(statePath)) {
|
|
288
|
-
return { version: 1, files: {} };
|
|
370
|
+
return { version: 1, files: {}, sessions: {} };
|
|
289
371
|
}
|
|
290
372
|
try {
|
|
291
373
|
const raw = fs.readFileSync(statePath, "utf8");
|
|
292
374
|
const parsed = JSON.parse(raw);
|
|
293
375
|
if (!parsed || typeof parsed !== "object") {
|
|
294
|
-
return { version: 1, files: {} };
|
|
376
|
+
return { version: 1, files: {}, sessions: {} };
|
|
295
377
|
}
|
|
296
378
|
const files = parsed.files && typeof parsed.files === "object" ? parsed.files : {};
|
|
297
|
-
|
|
379
|
+
const sessions = parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
|
|
380
|
+
return { version: 1, files, sessions };
|
|
298
381
|
}
|
|
299
382
|
catch {
|
|
300
|
-
return { version: 1, files: {} };
|
|
383
|
+
return { version: 1, files: {}, sessions: {} };
|
|
301
384
|
}
|
|
302
385
|
}
|
|
303
386
|
function writeUsageState(statePath, state) {
|
|
@@ -632,6 +715,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
632
715
|
const logEntries = readProfileLogEntries([profileLogPath]);
|
|
633
716
|
const state = readUsageState(statePath);
|
|
634
717
|
const files = state.files || {};
|
|
718
|
+
const sessions = state.sessions || {};
|
|
635
719
|
const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
|
|
636
720
|
const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
|
|
637
721
|
const processFile = (filePath, type) => {
|
|
@@ -661,16 +745,34 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
661
745
|
const resolved = resolveProfileForSession(config, logEntries, type, filePath, stats.sessionId);
|
|
662
746
|
if (!resolved.match)
|
|
663
747
|
return;
|
|
748
|
+
const sessionKey = stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
|
|
749
|
+
const sessionPrev = sessionKey ? sessions[sessionKey] : null;
|
|
664
750
|
const prevInput = prev ? prev.inputTokens : 0;
|
|
665
751
|
const prevOutput = prev ? prev.outputTokens : 0;
|
|
666
752
|
const prevTotal = prev ? prev.totalTokens : 0;
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
753
|
+
const prevInputMax = sessionPrev
|
|
754
|
+
? Math.max(prevInput, sessionPrev.inputTokens)
|
|
755
|
+
: prevInput;
|
|
756
|
+
const prevOutputMax = sessionPrev
|
|
757
|
+
? Math.max(prevOutput, sessionPrev.outputTokens)
|
|
758
|
+
: prevOutput;
|
|
759
|
+
const prevTotalMax = sessionPrev
|
|
760
|
+
? Math.max(prevTotal, sessionPrev.totalTokens)
|
|
761
|
+
: prevTotal;
|
|
762
|
+
let deltaInput = stats.inputTokens - prevInputMax;
|
|
763
|
+
let deltaOutput = stats.outputTokens - prevOutputMax;
|
|
764
|
+
let deltaTotal = stats.totalTokens - prevTotalMax;
|
|
670
765
|
if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
766
|
+
if (sessionPrev) {
|
|
767
|
+
deltaInput = 0;
|
|
768
|
+
deltaOutput = 0;
|
|
769
|
+
deltaTotal = 0;
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
deltaInput = stats.inputTokens;
|
|
773
|
+
deltaOutput = stats.outputTokens;
|
|
774
|
+
deltaTotal = stats.totalTokens;
|
|
775
|
+
}
|
|
674
776
|
}
|
|
675
777
|
if (deltaTotal > 0) {
|
|
676
778
|
const record = {
|
|
@@ -684,6 +786,26 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
684
786
|
};
|
|
685
787
|
appendUsageRecord(usagePath, record);
|
|
686
788
|
}
|
|
789
|
+
if (sessionKey) {
|
|
790
|
+
const nextInput = sessionPrev
|
|
791
|
+
? Math.max(sessionPrev.inputTokens, stats.inputTokens)
|
|
792
|
+
: stats.inputTokens;
|
|
793
|
+
const nextOutput = sessionPrev
|
|
794
|
+
? Math.max(sessionPrev.outputTokens, stats.outputTokens)
|
|
795
|
+
: stats.outputTokens;
|
|
796
|
+
const nextTotal = sessionPrev
|
|
797
|
+
? Math.max(sessionPrev.totalTokens, stats.totalTokens)
|
|
798
|
+
: stats.totalTokens;
|
|
799
|
+
sessions[sessionKey] = {
|
|
800
|
+
type,
|
|
801
|
+
inputTokens: nextInput,
|
|
802
|
+
outputTokens: nextOutput,
|
|
803
|
+
totalTokens: nextTotal,
|
|
804
|
+
startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
|
|
805
|
+
endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
|
|
806
|
+
cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
|
|
807
|
+
};
|
|
808
|
+
}
|
|
687
809
|
files[filePath] = {
|
|
688
810
|
mtimeMs: stat.mtimeMs,
|
|
689
811
|
size: stat.size,
|
|
@@ -701,6 +823,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
701
823
|
for (const filePath of claudeFiles)
|
|
702
824
|
processFile(filePath, "claude");
|
|
703
825
|
state.files = files;
|
|
826
|
+
state.sessions = sessions;
|
|
704
827
|
writeUsageState(statePath, state);
|
|
705
828
|
}
|
|
706
829
|
finally {
|