eminiar 0.9.1 → 0.10.1

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/dist/hook.js CHANGED
@@ -174,14 +174,14 @@ var require_is_extglob = __commonJS({
174
174
  "node_modules/is-extglob/index.js"(exports, module) {
175
175
  "use strict";
176
176
  init_esm_shims();
177
- module.exports = function isExtglob(str) {
178
- if (typeof str !== "string" || str === "") {
177
+ module.exports = function isExtglob(str2) {
178
+ if (typeof str2 !== "string" || str2 === "") {
179
179
  return false;
180
180
  }
181
181
  var match;
182
- while (match = /(\\).|([@?!+*]\(.*\))/g.exec(str)) {
182
+ while (match = /(\\).|([@?!+*]\(.*\))/g.exec(str2)) {
183
183
  if (match[2]) return true;
184
- str = str.slice(match.index + match[0].length);
184
+ str2 = str2.slice(match.index + match[0].length);
185
185
  }
186
186
  return false;
187
187
  };
@@ -195,8 +195,8 @@ var require_is_glob = __commonJS({
195
195
  init_esm_shims();
196
196
  var isExtglob = require_is_extglob();
197
197
  var chars = { "{": "}", "(": ")", "[": "]" };
198
- var strictCheck = function(str) {
199
- if (str[0] === "!") {
198
+ var strictCheck = function(str2) {
199
+ if (str2[0] === "!") {
200
200
  return true;
201
201
  }
202
202
  var index = 0;
@@ -205,70 +205,70 @@ var require_is_glob = __commonJS({
205
205
  var closeCurlyIndex = -2;
206
206
  var closeParenIndex = -2;
207
207
  var backSlashIndex = -2;
208
- while (index < str.length) {
209
- if (str[index] === "*") {
208
+ while (index < str2.length) {
209
+ if (str2[index] === "*") {
210
210
  return true;
211
211
  }
212
- if (str[index + 1] === "?" && /[\].+)]/.test(str[index])) {
212
+ if (str2[index + 1] === "?" && /[\].+)]/.test(str2[index])) {
213
213
  return true;
214
214
  }
215
- if (closeSquareIndex !== -1 && str[index] === "[" && str[index + 1] !== "]") {
215
+ if (closeSquareIndex !== -1 && str2[index] === "[" && str2[index + 1] !== "]") {
216
216
  if (closeSquareIndex < index) {
217
- closeSquareIndex = str.indexOf("]", index);
217
+ closeSquareIndex = str2.indexOf("]", index);
218
218
  }
219
219
  if (closeSquareIndex > index) {
220
220
  if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) {
221
221
  return true;
222
222
  }
223
- backSlashIndex = str.indexOf("\\", index);
223
+ backSlashIndex = str2.indexOf("\\", index);
224
224
  if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) {
225
225
  return true;
226
226
  }
227
227
  }
228
228
  }
229
- if (closeCurlyIndex !== -1 && str[index] === "{" && str[index + 1] !== "}") {
230
- closeCurlyIndex = str.indexOf("}", index);
229
+ if (closeCurlyIndex !== -1 && str2[index] === "{" && str2[index + 1] !== "}") {
230
+ closeCurlyIndex = str2.indexOf("}", index);
231
231
  if (closeCurlyIndex > index) {
232
- backSlashIndex = str.indexOf("\\", index);
232
+ backSlashIndex = str2.indexOf("\\", index);
233
233
  if (backSlashIndex === -1 || backSlashIndex > closeCurlyIndex) {
234
234
  return true;
235
235
  }
236
236
  }
237
237
  }
238
- if (closeParenIndex !== -1 && str[index] === "(" && str[index + 1] === "?" && /[:!=]/.test(str[index + 2]) && str[index + 3] !== ")") {
239
- closeParenIndex = str.indexOf(")", index);
238
+ if (closeParenIndex !== -1 && str2[index] === "(" && str2[index + 1] === "?" && /[:!=]/.test(str2[index + 2]) && str2[index + 3] !== ")") {
239
+ closeParenIndex = str2.indexOf(")", index);
240
240
  if (closeParenIndex > index) {
241
- backSlashIndex = str.indexOf("\\", index);
241
+ backSlashIndex = str2.indexOf("\\", index);
242
242
  if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) {
243
243
  return true;
244
244
  }
245
245
  }
246
246
  }
247
- if (pipeIndex !== -1 && str[index] === "(" && str[index + 1] !== "|") {
247
+ if (pipeIndex !== -1 && str2[index] === "(" && str2[index + 1] !== "|") {
248
248
  if (pipeIndex < index) {
249
- pipeIndex = str.indexOf("|", index);
249
+ pipeIndex = str2.indexOf("|", index);
250
250
  }
251
- if (pipeIndex !== -1 && str[pipeIndex + 1] !== ")") {
252
- closeParenIndex = str.indexOf(")", pipeIndex);
251
+ if (pipeIndex !== -1 && str2[pipeIndex + 1] !== ")") {
252
+ closeParenIndex = str2.indexOf(")", pipeIndex);
253
253
  if (closeParenIndex > pipeIndex) {
254
- backSlashIndex = str.indexOf("\\", pipeIndex);
254
+ backSlashIndex = str2.indexOf("\\", pipeIndex);
255
255
  if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) {
256
256
  return true;
257
257
  }
258
258
  }
259
259
  }
260
260
  }
261
- if (str[index] === "\\") {
262
- var open4 = str[index + 1];
261
+ if (str2[index] === "\\") {
262
+ var open4 = str2[index + 1];
263
263
  index += 2;
264
264
  var close = chars[open4];
265
265
  if (close) {
266
- var n = str.indexOf(close, index);
266
+ var n = str2.indexOf(close, index);
267
267
  if (n !== -1) {
268
268
  index = n + 1;
269
269
  }
270
270
  }
271
- if (str[index] === "!") {
271
+ if (str2[index] === "!") {
272
272
  return true;
273
273
  }
274
274
  } else {
@@ -277,26 +277,26 @@ var require_is_glob = __commonJS({
277
277
  }
278
278
  return false;
279
279
  };
280
- var relaxedCheck = function(str) {
281
- if (str[0] === "!") {
280
+ var relaxedCheck = function(str2) {
281
+ if (str2[0] === "!") {
282
282
  return true;
283
283
  }
284
284
  var index = 0;
285
- while (index < str.length) {
286
- if (/[*?{}()[\]]/.test(str[index])) {
285
+ while (index < str2.length) {
286
+ if (/[*?{}()[\]]/.test(str2[index])) {
287
287
  return true;
288
288
  }
289
- if (str[index] === "\\") {
290
- var open4 = str[index + 1];
289
+ if (str2[index] === "\\") {
290
+ var open4 = str2[index + 1];
291
291
  index += 2;
292
292
  var close = chars[open4];
293
293
  if (close) {
294
- var n = str.indexOf(close, index);
294
+ var n = str2.indexOf(close, index);
295
295
  if (n !== -1) {
296
296
  index = n + 1;
297
297
  }
298
298
  }
299
- if (str[index] === "!") {
299
+ if (str2[index] === "!") {
300
300
  return true;
301
301
  }
302
302
  } else {
@@ -305,18 +305,18 @@ var require_is_glob = __commonJS({
305
305
  }
306
306
  return false;
307
307
  };
308
- module.exports = function isGlob(str, options) {
309
- if (typeof str !== "string" || str === "") {
308
+ module.exports = function isGlob(str2, options) {
309
+ if (typeof str2 !== "string" || str2 === "") {
310
310
  return false;
311
311
  }
312
- if (isExtglob(str)) {
312
+ if (isExtglob(str2)) {
313
313
  return true;
314
314
  }
315
315
  var check2 = strictCheck;
316
316
  if (options && options.strict === false) {
317
317
  check2 = relaxedCheck;
318
318
  }
319
- return check2(str);
319
+ return check2(str2);
320
320
  };
321
321
  }
322
322
  });
@@ -334,19 +334,19 @@ var require_glob_parent = __commonJS({
334
334
  var enclosure = /[\{\[].*[\}\]]$/;
335
335
  var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/;
336
336
  var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g;
337
- module.exports = function globParent(str, opts) {
337
+ module.exports = function globParent(str2, opts) {
338
338
  var options = Object.assign({ flipBackslashes: true }, opts);
339
- if (options.flipBackslashes && isWin32 && str.indexOf(slash) < 0) {
340
- str = str.replace(backslash, slash);
339
+ if (options.flipBackslashes && isWin32 && str2.indexOf(slash) < 0) {
340
+ str2 = str2.replace(backslash, slash);
341
341
  }
342
- if (enclosure.test(str)) {
343
- str += slash;
342
+ if (enclosure.test(str2)) {
343
+ str2 += slash;
344
344
  }
345
- str += "a";
345
+ str2 += "a";
346
346
  do {
347
- str = pathPosixDirname(str);
348
- } while (isGlob(str) || globby.test(str));
349
- return str.replace(escaped, "$1");
347
+ str2 = pathPosixDirname(str2);
348
+ } while (isGlob(str2) || globby.test(str2));
349
+ return str2.replace(escaped, "$1");
350
350
  };
351
351
  }
352
352
  });
@@ -356,12 +356,12 @@ var require_utils = __commonJS({
356
356
  "node_modules/braces/lib/utils.js"(exports) {
357
357
  "use strict";
358
358
  init_esm_shims();
359
- exports.isInteger = (num) => {
360
- if (typeof num === "number") {
361
- return Number.isInteger(num);
359
+ exports.isInteger = (num4) => {
360
+ if (typeof num4 === "number") {
361
+ return Number.isInteger(num4);
362
362
  }
363
- if (typeof num === "string" && num.trim() !== "") {
364
- return Number.isInteger(Number(num));
363
+ if (typeof num4 === "string" && num4.trim() !== "") {
364
+ return Number.isInteger(Number(num4));
365
365
  }
366
366
  return false;
367
367
  };
@@ -471,12 +471,12 @@ var require_is_number = __commonJS({
471
471
  "node_modules/is-number/index.js"(exports, module) {
472
472
  "use strict";
473
473
  init_esm_shims();
474
- module.exports = function(num) {
475
- if (typeof num === "number") {
476
- return num - num === 0;
474
+ module.exports = function(num4) {
475
+ if (typeof num4 === "number") {
476
+ return num4 - num4 === 0;
477
477
  }
478
- if (typeof num === "string" && num.trim() !== "") {
479
- return Number.isFinite ? Number.isFinite(+num) : isFinite(+num);
478
+ if (typeof num4 === "string" && num4.trim() !== "") {
479
+ return Number.isFinite ? Number.isFinite(+num4) : isFinite(+num4);
480
480
  }
481
481
  return false;
482
482
  };
@@ -600,7 +600,7 @@ var require_to_regex_range = __commonJS({
600
600
  }
601
601
  return { pattern, count: [count], digits };
602
602
  }
603
- function splitToPatterns(min, max, tok, options) {
603
+ function splitToPatterns(min, max, tok2, options) {
604
604
  let ranges = splitToRanges(min, max);
605
605
  let tokens = [];
606
606
  let start = min;
@@ -609,7 +609,7 @@ var require_to_regex_range = __commonJS({
609
609
  let max2 = ranges[i];
610
610
  let obj = rangeToPattern(String(start), String(max2), options);
611
611
  let zeros = "";
612
- if (!tok.isPadded && prev && prev.pattern === obj.pattern) {
612
+ if (!tok2.isPadded && prev && prev.pattern === obj.pattern) {
613
613
  if (prev.count.length > 1) {
614
614
  prev.count.pop();
615
615
  }
@@ -618,8 +618,8 @@ var require_to_regex_range = __commonJS({
618
618
  start = max2 + 1;
619
619
  continue;
620
620
  }
621
- if (tok.isPadded) {
622
- zeros = padZeros(max2, tok, options);
621
+ if (tok2.isPadded) {
622
+ zeros = padZeros(max2, tok2, options);
623
623
  }
624
624
  obj.string = zeros + obj.pattern + toQuantifier(obj.count);
625
625
  tokens.push(obj);
@@ -668,14 +668,14 @@ var require_to_regex_range = __commonJS({
668
668
  function toCharacterClass(a, b, options) {
669
669
  return `[${a}${b - a === 1 ? "" : "-"}${b}]`;
670
670
  }
671
- function hasPadding(str) {
672
- return /^-?(0+)\d/.test(str);
671
+ function hasPadding(str2) {
672
+ return /^-?(0+)\d/.test(str2);
673
673
  }
674
- function padZeros(value, tok, options) {
675
- if (!tok.isPadded) {
674
+ function padZeros(value, tok2, options) {
675
+ if (!tok2.isPadded) {
676
676
  return value;
677
677
  }
678
- let diff = Math.abs(tok.maxLen - String(value).length);
678
+ let diff = Math.abs(tok2.maxLen - String(value).length);
679
679
  let relax = options.relaxZeros !== false;
680
680
  switch (diff) {
681
681
  case 0:
@@ -709,7 +709,7 @@ var require_fill_range = __commonJS({
709
709
  var isValidValue = (value) => {
710
710
  return typeof value === "number" || typeof value === "string" && value !== "";
711
711
  };
712
- var isNumber = (num) => Number.isInteger(+num);
712
+ var isNumber = (num4) => Number.isInteger(+num4);
713
713
  var zeros = (input2) => {
714
714
  let value = `${input2}`;
715
715
  let index = -1;
@@ -819,7 +819,7 @@ var require_fill_range = __commonJS({
819
819
  return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options);
820
820
  }
821
821
  let parts = { negatives: [], positives: [] };
822
- let push = (num) => parts[num < 0 ? "negatives" : "positives"].push(Math.abs(num));
822
+ let push = (num4) => parts[num4 < 0 ? "negatives" : "positives"].push(Math.abs(num4));
823
823
  let range = [];
824
824
  let index = 0;
825
825
  while (descending ? a >= b : a <= b) {
@@ -1659,12 +1659,12 @@ var require_utils2 = __commonJS({
1659
1659
  REGEX_SPECIAL_CHARS_GLOBAL
1660
1660
  } = require_constants2();
1661
1661
  exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
1662
- exports.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
1663
- exports.isRegexChar = (str) => str.length === 1 && exports.hasRegexChars(str);
1664
- exports.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
1665
- exports.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
1666
- exports.removeBackslashes = (str) => {
1667
- return str.replace(REGEX_REMOVE_BACKSLASH, (match) => {
1662
+ exports.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
1663
+ exports.isRegexChar = (str2) => str2.length === 1 && exports.hasRegexChars(str2);
1664
+ exports.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
1665
+ exports.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
1666
+ exports.removeBackslashes = (str2) => {
1667
+ return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
1668
1668
  return match === "\\" ? "" : match;
1669
1669
  });
1670
1670
  };
@@ -1760,7 +1760,7 @@ var require_scan = __commonJS({
1760
1760
  const slashes = [];
1761
1761
  const tokens = [];
1762
1762
  const parts = [];
1763
- let str = input2;
1763
+ let str2 = input2;
1764
1764
  let index = -1;
1765
1765
  let start = 0;
1766
1766
  let lastIndex = 0;
@@ -1779,10 +1779,10 @@ var require_scan = __commonJS({
1779
1779
  let code;
1780
1780
  let token = { value: "", depth: 0, isGlob: false };
1781
1781
  const eos = () => index >= length;
1782
- const peek = () => str.charCodeAt(index + 1);
1782
+ const peek = () => str2.charCodeAt(index + 1);
1783
1783
  const advance = () => {
1784
1784
  prev = code;
1785
- return str.charCodeAt(++index);
1785
+ return str2.charCodeAt(++index);
1786
1786
  };
1787
1787
  while (index < length) {
1788
1788
  code = advance();
@@ -1950,24 +1950,24 @@ var require_scan = __commonJS({
1950
1950
  isExtglob = false;
1951
1951
  isGlob = false;
1952
1952
  }
1953
- let base = str;
1953
+ let base = str2;
1954
1954
  let prefix = "";
1955
1955
  let glob = "";
1956
1956
  if (start > 0) {
1957
- prefix = str.slice(0, start);
1958
- str = str.slice(start);
1957
+ prefix = str2.slice(0, start);
1958
+ str2 = str2.slice(start);
1959
1959
  lastIndex -= start;
1960
1960
  }
1961
1961
  if (base && isGlob === true && lastIndex > 0) {
1962
- base = str.slice(0, lastIndex);
1963
- glob = str.slice(lastIndex);
1962
+ base = str2.slice(0, lastIndex);
1963
+ glob = str2.slice(lastIndex);
1964
1964
  } else if (isGlob === true) {
1965
1965
  base = "";
1966
- glob = str;
1966
+ glob = str2;
1967
1967
  } else {
1968
- base = str;
1968
+ base = str2;
1969
1969
  }
1970
- if (base && base !== "" && base !== "/" && base !== str) {
1970
+ if (base && base !== "" && base !== "/" && base !== str2) {
1971
1971
  if (isPathSeparator(base.charCodeAt(base.length - 1))) {
1972
1972
  base = base.slice(0, -1);
1973
1973
  }
@@ -2346,9 +2346,9 @@ var require_parse2 = __commonJS({
2346
2346
  const peek = state.peek = (n = 1) => input2[state.index + n];
2347
2347
  const advance = state.advance = () => input2[++state.index] || "";
2348
2348
  const remaining = () => input2.slice(state.index + 1);
2349
- const consume = (value2 = "", num = 0) => {
2349
+ const consume = (value2 = "", num4 = 0) => {
2350
2350
  state.consumed += value2;
2351
- state.index += num;
2351
+ state.index += num4;
2352
2352
  };
2353
2353
  const append = (token) => {
2354
2354
  state.output += token.output != null ? token.output : token.value;
@@ -2376,11 +2376,11 @@ var require_parse2 = __commonJS({
2376
2376
  state[type]--;
2377
2377
  stack.pop();
2378
2378
  };
2379
- const push = (tok) => {
2379
+ const push = (tok2) => {
2380
2380
  if (prev.type === "globstar") {
2381
- const isBrace = state.braces > 0 && (tok.type === "comma" || tok.type === "brace");
2382
- const isExtglob = tok.extglob === true || extglobs.length && (tok.type === "pipe" || tok.type === "paren");
2383
- if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) {
2381
+ const isBrace = state.braces > 0 && (tok2.type === "comma" || tok2.type === "brace");
2382
+ const isExtglob = tok2.extglob === true || extglobs.length && (tok2.type === "pipe" || tok2.type === "paren");
2383
+ if (tok2.type !== "slash" && tok2.type !== "paren" && !isBrace && !isExtglob) {
2384
2384
  state.output = state.output.slice(0, -prev.output.length);
2385
2385
  prev.type = "star";
2386
2386
  prev.value = "*";
@@ -2388,18 +2388,18 @@ var require_parse2 = __commonJS({
2388
2388
  state.output += prev.output;
2389
2389
  }
2390
2390
  }
2391
- if (extglobs.length && tok.type !== "paren") {
2392
- extglobs[extglobs.length - 1].inner += tok.value;
2391
+ if (extglobs.length && tok2.type !== "paren") {
2392
+ extglobs[extglobs.length - 1].inner += tok2.value;
2393
2393
  }
2394
- if (tok.value || tok.output) append(tok);
2395
- if (prev && prev.type === "text" && tok.type === "text") {
2396
- prev.value += tok.value;
2397
- prev.output = (prev.output || "") + tok.value;
2394
+ if (tok2.value || tok2.output) append(tok2);
2395
+ if (prev && prev.type === "text" && tok2.type === "text") {
2396
+ prev.value += tok2.value;
2397
+ prev.output = (prev.output || "") + tok2.value;
2398
2398
  return;
2399
2399
  }
2400
- tok.prev = prev;
2401
- tokens.push(tok);
2402
- prev = tok;
2400
+ tok2.prev = prev;
2401
+ tokens.push(tok2);
2402
+ prev = tok2;
2403
2403
  };
2404
2404
  const extglobOpen = (type, value2) => {
2405
2405
  const token = { ...EXTGLOB_CHARS[value2], conditions: 1, inner: "" };
@@ -3003,8 +3003,8 @@ var require_parse2 = __commonJS({
3003
3003
  if (opts2.noglobstar === true) return star;
3004
3004
  return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
3005
3005
  };
3006
- const create = (str) => {
3007
- switch (str) {
3006
+ const create = (str2) => {
3007
+ switch (str2) {
3008
3008
  case "*":
3009
3009
  return `${nodot}${ONE_CHAR}${star}`;
3010
3010
  case ".*":
@@ -3022,7 +3022,7 @@ var require_parse2 = __commonJS({
3022
3022
  case "**/.*":
3023
3023
  return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
3024
3024
  default: {
3025
- const match = /^(.*?)\.(\w+)$/.exec(str);
3025
+ const match = /^(.*?)\.(\w+)$/.exec(str2);
3026
3026
  if (!match) return;
3027
3027
  const source2 = create(match[1]);
3028
3028
  if (!source2) return;
@@ -3055,9 +3055,9 @@ var require_picomatch = __commonJS({
3055
3055
  var picomatch = (glob, options, returnState = false) => {
3056
3056
  if (Array.isArray(glob)) {
3057
3057
  const fns = glob.map((input2) => picomatch(input2, options, returnState));
3058
- const arrayMatcher = (str) => {
3058
+ const arrayMatcher = (str2) => {
3059
3059
  for (const isMatch of fns) {
3060
- const state2 = isMatch(str);
3060
+ const state2 = isMatch(str2);
3061
3061
  if (state2) return state2;
3062
3062
  }
3063
3063
  return false;
@@ -3133,7 +3133,7 @@ var require_picomatch = __commonJS({
3133
3133
  const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
3134
3134
  return regex.test(path2.basename(input2));
3135
3135
  };
3136
- picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
3136
+ picomatch.isMatch = (str2, patterns, options) => picomatch(patterns, options)(str2);
3137
3137
  picomatch.parse = (pattern, options) => {
3138
3138
  if (Array.isArray(pattern)) return pattern.map((p) => picomatch.parse(p, options));
3139
3139
  return parse4(pattern, { ...options, fastpaths: false });
@@ -3249,7 +3249,7 @@ var require_micromatch = __commonJS({
3249
3249
  };
3250
3250
  micromatch.match = micromatch;
3251
3251
  micromatch.matcher = (pattern, options) => picomatch(pattern, options);
3252
- micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
3252
+ micromatch.isMatch = (str2, patterns, options) => picomatch(patterns, options)(str2);
3253
3253
  micromatch.any = micromatch.isMatch;
3254
3254
  micromatch.not = (list, patterns, options = {}) => {
3255
3255
  patterns = [].concat(patterns).map(String);
@@ -3267,22 +3267,22 @@ var require_micromatch = __commonJS({
3267
3267
  }
3268
3268
  return [...result];
3269
3269
  };
3270
- micromatch.contains = (str, pattern, options) => {
3271
- if (typeof str !== "string") {
3272
- throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
3270
+ micromatch.contains = (str2, pattern, options) => {
3271
+ if (typeof str2 !== "string") {
3272
+ throw new TypeError(`Expected a string: "${util.inspect(str2)}"`);
3273
3273
  }
3274
3274
  if (Array.isArray(pattern)) {
3275
- return pattern.some((p) => micromatch.contains(str, p, options));
3275
+ return pattern.some((p) => micromatch.contains(str2, p, options));
3276
3276
  }
3277
3277
  if (typeof pattern === "string") {
3278
- if (isEmptyString(str) || isEmptyString(pattern)) {
3278
+ if (isEmptyString(str2) || isEmptyString(pattern)) {
3279
3279
  return false;
3280
3280
  }
3281
- if (str.includes(pattern) || str.startsWith("./") && str.slice(2).includes(pattern)) {
3281
+ if (str2.includes(pattern) || str2.startsWith("./") && str2.slice(2).includes(pattern)) {
3282
3282
  return true;
3283
3283
  }
3284
3284
  }
3285
- return micromatch.isMatch(str, pattern, { ...options, contains: true });
3285
+ return micromatch.isMatch(str2, pattern, { ...options, contains: true });
3286
3286
  };
3287
3287
  micromatch.matchKeys = (obj, patterns, options) => {
3288
3288
  if (!utils.isObject(obj)) {
@@ -3313,11 +3313,11 @@ var require_micromatch = __commonJS({
3313
3313
  }
3314
3314
  return true;
3315
3315
  };
3316
- micromatch.all = (str, patterns, options) => {
3317
- if (typeof str !== "string") {
3318
- throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
3316
+ micromatch.all = (str2, patterns, options) => {
3317
+ if (typeof str2 !== "string") {
3318
+ throw new TypeError(`Expected a string: "${util.inspect(str2)}"`);
3319
3319
  }
3320
- return [].concat(patterns).every((p) => picomatch(p, options)(str));
3320
+ return [].concat(patterns).every((p) => picomatch(p, options)(str2));
3321
3321
  };
3322
3322
  micromatch.capture = (glob, input2, options) => {
3323
3323
  let posix = utils.isWindows(options);
@@ -3332,8 +3332,8 @@ var require_micromatch = __commonJS({
3332
3332
  micromatch.parse = (patterns, options) => {
3333
3333
  let res = [];
3334
3334
  for (let pattern of [].concat(patterns || [])) {
3335
- for (let str of braces(String(pattern), options)) {
3336
- res.push(picomatch.parse(str, options));
3335
+ for (let str2 of braces(String(pattern), options)) {
3336
+ res.push(picomatch.parse(str2, options));
3337
3337
  }
3338
3338
  }
3339
3339
  return res;
@@ -3456,8 +3456,8 @@ var require_pattern = __commonJS({
3456
3456
  }
3457
3457
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
3458
3458
  function isAffectDepthOfReadingPattern(pattern) {
3459
- const basename = path2.basename(pattern);
3460
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename);
3459
+ const basename2 = path2.basename(pattern);
3460
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename2);
3461
3461
  }
3462
3462
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
3463
3463
  function expandPatternsWithBraceExpansion(patterns) {
@@ -3941,11 +3941,11 @@ var require_out = __commonJS({
3941
3941
  async.read(path2, getSettings(optionsOrSettingsOrCallback), callback);
3942
3942
  }
3943
3943
  exports.stat = stat;
3944
- function statSync6(path2, optionsOrSettings) {
3944
+ function statSync7(path2, optionsOrSettings) {
3945
3945
  const settings = getSettings(optionsOrSettings);
3946
3946
  return sync.read(path2, settings);
3947
3947
  }
3948
- exports.statSync = statSync6;
3948
+ exports.statSync = statSync7;
3949
3949
  function getSettings(settingsOrOptions = {}) {
3950
3950
  if (settingsOrOptions instanceof settings_1.default) {
3951
3951
  return settingsOrOptions;
@@ -4626,41 +4626,41 @@ var require_queue = __commonJS({
4626
4626
  queue.drained = drained;
4627
4627
  return queue;
4628
4628
  function push(value) {
4629
- var p = new Promise(function(resolve4, reject) {
4629
+ var p = new Promise(function(resolve5, reject) {
4630
4630
  pushCb(value, function(err, result) {
4631
4631
  if (err) {
4632
4632
  reject(err);
4633
4633
  return;
4634
4634
  }
4635
- resolve4(result);
4635
+ resolve5(result);
4636
4636
  });
4637
4637
  });
4638
4638
  p.catch(noop);
4639
4639
  return p;
4640
4640
  }
4641
4641
  function unshift(value) {
4642
- var p = new Promise(function(resolve4, reject) {
4642
+ var p = new Promise(function(resolve5, reject) {
4643
4643
  unshiftCb(value, function(err, result) {
4644
4644
  if (err) {
4645
4645
  reject(err);
4646
4646
  return;
4647
4647
  }
4648
- resolve4(result);
4648
+ resolve5(result);
4649
4649
  });
4650
4650
  });
4651
4651
  p.catch(noop);
4652
4652
  return p;
4653
4653
  }
4654
4654
  function drained() {
4655
- var p = new Promise(function(resolve4) {
4655
+ var p = new Promise(function(resolve5) {
4656
4656
  process.nextTick(function() {
4657
4657
  if (queue.idle()) {
4658
- resolve4();
4658
+ resolve5();
4659
4659
  } else {
4660
4660
  var previousDrain = queue.drain;
4661
4661
  queue.drain = function() {
4662
4662
  if (typeof previousDrain === "function") previousDrain();
4663
- resolve4();
4663
+ resolve5();
4664
4664
  queue.drain = previousDrain;
4665
4665
  };
4666
4666
  }
@@ -5157,9 +5157,9 @@ var require_stream3 = __commonJS({
5157
5157
  });
5158
5158
  }
5159
5159
  _getStat(filepath) {
5160
- return new Promise((resolve4, reject) => {
5160
+ return new Promise((resolve5, reject) => {
5161
5161
  this._stat(filepath, this._fsStatSettings, (error62, stats) => {
5162
- return error62 === null ? resolve4(stats) : reject(error62);
5162
+ return error62 === null ? resolve5(stats) : reject(error62);
5163
5163
  });
5164
5164
  });
5165
5165
  }
@@ -5184,10 +5184,10 @@ var require_async5 = __commonJS({
5184
5184
  this._readerStream = new stream_1.default(this._settings);
5185
5185
  }
5186
5186
  dynamic(root, options) {
5187
- return new Promise((resolve4, reject) => {
5187
+ return new Promise((resolve5, reject) => {
5188
5188
  this._walkAsync(root, options, (error62, entries) => {
5189
5189
  if (error62 === null) {
5190
- resolve4(entries);
5190
+ resolve5(entries);
5191
5191
  } else {
5192
5192
  reject(error62);
5193
5193
  }
@@ -5197,10 +5197,10 @@ var require_async5 = __commonJS({
5197
5197
  async static(patterns, options) {
5198
5198
  const entries = [];
5199
5199
  const stream = this._readerStream.static(patterns, options);
5200
- return new Promise((resolve4, reject) => {
5200
+ return new Promise((resolve5, reject) => {
5201
5201
  stream.once("error", reject);
5202
5202
  stream.on("data", (entry) => entries.push(entry));
5203
- stream.once("end", () => resolve4(entries));
5203
+ stream.once("end", () => resolve5(entries));
5204
5204
  });
5205
5205
  }
5206
5206
  };
@@ -5970,8 +5970,8 @@ function withPriority(sessionId, drafts) {
5970
5970
  return drafts.map((d) => ({ ...d, sessionId, priority: CATEGORIES[d.category] }));
5971
5971
  }
5972
5972
  var clip = (s, n) => {
5973
- const str = typeof s === "string" ? s : JSON.stringify(s ?? "");
5974
- return str.length > n ? `${str.slice(0, n - 1)}\u2026` : str;
5973
+ const str2 = typeof s === "string" ? s : JSON.stringify(s ?? "");
5974
+ return str2.length > n ? `${str2.slice(0, n - 1)}\u2026` : str2;
5975
5975
  };
5976
5976
  var DECISION = /\b(let'?s go with|we'?ll use|decided|decision:|go with|use (?:the )?\w+ instead|stick with|switch(?:ing)? to|chose|prefer(?:red)?)\b/i;
5977
5977
  var CONSTRAINT = /\b(must(?: not)?|never|always|only|do not|don'?t|should not|required?|hard requirement|no\s+\w+ allowed)\b/i;
@@ -6966,14 +6966,14 @@ function promiseAllObject(promisesObj) {
6966
6966
  }
6967
6967
  function randomString(length = 10) {
6968
6968
  const chars = "abcdefghijklmnopqrstuvwxyz";
6969
- let str = "";
6969
+ let str2 = "";
6970
6970
  for (let i = 0; i < length; i++) {
6971
- str += chars[Math.floor(Math.random() * chars.length)];
6971
+ str2 += chars[Math.floor(Math.random() * chars.length)];
6972
6972
  }
6973
- return str;
6973
+ return str2;
6974
6974
  }
6975
- function esc(str) {
6976
- return JSON.stringify(str);
6975
+ function esc(str2) {
6976
+ return JSON.stringify(str2);
6977
6977
  }
6978
6978
  function slugify(input2) {
6979
6979
  return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -7087,8 +7087,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
7087
7087
  "symbol",
7088
7088
  "undefined"
7089
7089
  ]);
7090
- function escapeRegex(str) {
7091
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7090
+ function escapeRegex(str2) {
7091
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7092
7092
  }
7093
7093
  function clone(inst, def, params) {
7094
7094
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -7354,13 +7354,13 @@ function getSizableOrigin(input2) {
7354
7354
  return "unknown";
7355
7355
  }
7356
7356
  var highSurrogate = /[\uD800-\uDBFF]/;
7357
- function codePointLength(str) {
7358
- const units = str.length;
7359
- if (!highSurrogate.test(str))
7357
+ function codePointLength(str2) {
7358
+ const units = str2.length;
7359
+ if (!highSurrogate.test(str2))
7360
7360
  return units;
7361
7361
  let count = units;
7362
7362
  for (let i = 0; i < units - 1; i++) {
7363
- if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
7363
+ if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
7364
7364
  count--;
7365
7365
  i++;
7366
7366
  }
@@ -11212,7 +11212,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
11212
11212
  var NONE = 0;
11213
11213
  var ASSUMED = 1;
11214
11214
  var PROVEN = 2;
11215
- function isRecursive(inst, stack, resolve4) {
11215
+ function isRecursive(inst, stack, resolve5) {
11216
11216
  const cached3 = recursive.get(inst);
11217
11217
  if (cached3 !== void 0)
11218
11218
  return cached3 ? PROVEN : NONE;
@@ -11222,7 +11222,7 @@ function isRecursive(inst, stack, resolve4) {
11222
11222
  let result = NONE;
11223
11223
  const check2 = (child) => {
11224
11224
  if (result !== PROVEN && child?._zod) {
11225
- const answer = isRecursive(child, stack, resolve4);
11225
+ const answer = isRecursive(child, stack, resolve5);
11226
11226
  if (answer > result)
11227
11227
  result = answer;
11228
11228
  }
@@ -11233,7 +11233,7 @@ function isRecursive(inst, stack, resolve4) {
11233
11233
  const desc = Object.getOwnPropertyDescriptor(sh, key);
11234
11234
  if (spread && !desc.enumerable)
11235
11235
  continue;
11236
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
11236
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
11237
11237
  if (child > answer)
11238
11238
  answer = child;
11239
11239
  }
@@ -11297,7 +11297,7 @@ function isRecursive(inst, stack, resolve4) {
11297
11297
  break;
11298
11298
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
11299
11299
  case "lazy": {
11300
- const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
11300
+ const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
11301
11301
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
11302
11302
  break;
11303
11303
  }
@@ -26147,6 +26147,33 @@ var ConfigSchema = external_exports.object({
26147
26147
  }).optional()
26148
26148
  })
26149
26149
  ).default([])
26150
+ }).prefault({}),
26151
+ usage: external_exports.object({
26152
+ /** Ingest Claude Code transcripts (tokens, cache, cost per model call) and record statusline snapshots. */
26153
+ enabled: external_exports.boolean().default(true),
26154
+ /** Soft budgets; crossing one adds a one-line warning to the next prompt (never blocks). */
26155
+ budgets: external_exports.object({
26156
+ sessionUsd: external_exports.number().positive().optional(),
26157
+ dailyUsd: external_exports.number().positive().optional(),
26158
+ /** A single API call above this is flagged as an expensive turn. */
26159
+ turnUsd: external_exports.number().positive().optional()
26160
+ }).prefault({}),
26161
+ /**
26162
+ * Price overrides per model, USD per million tokens, matched by substring of the model id
26163
+ * (first match wins). Missing fields fall back to the bundled table.
26164
+ */
26165
+ pricing: external_exports.record(
26166
+ external_exports.string(),
26167
+ external_exports.object({
26168
+ input: external_exports.number().nonnegative().optional(),
26169
+ output: external_exports.number().nonnegative().optional(),
26170
+ cacheWrite5m: external_exports.number().nonnegative().optional(),
26171
+ cacheWrite1h: external_exports.number().nonnegative().optional(),
26172
+ cacheRead: external_exports.number().nonnegative().optional()
26173
+ })
26174
+ ).default({}),
26175
+ /** Show cost, context and plan usage segments in `eminiar statusline`. */
26176
+ statusline: external_exports.boolean().default(true)
26150
26177
  }).prefault({})
26151
26178
  });
26152
26179
  function readJson(file3) {
@@ -26407,7 +26434,7 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
26407
26434
  if (/^\s*(```|~~~)/.test(line2)) inFence = !inFence;
26408
26435
  const m = !inFence && /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line2);
26409
26436
  if (m) {
26410
- if (current.lines.length) sections.push(current);
26437
+ sections.push(current);
26411
26438
  const level = m[1].length;
26412
26439
  while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
26413
26440
  stack.push({ level, text: m[2] });
@@ -26416,14 +26443,23 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
26416
26443
  }
26417
26444
  current.lines.push(line2);
26418
26445
  });
26419
- if (current.lines.length) sections.push(current);
26446
+ sections.push(current);
26420
26447
  const out = [];
26421
- for (const s of sections) {
26448
+ sections.forEach((s, k) => {
26422
26449
  const title = s.path.length ? `${baseTitle} > ${s.path.join(" > ")}` : baseTitle;
26423
26450
  const body = s.lines.join("\n");
26451
+ if (!body.trim()) {
26452
+ const next = sections[k + 1];
26453
+ const isParent = next !== void 0 && next.path.length > s.path.length && s.path.every((p, j) => next.path[j] === p);
26454
+ if (s.path.length && !isParent) {
26455
+ const heading = s.path[s.path.length - 1];
26456
+ out.push({ title, body: heading, startLine: s.startLine, endLine: s.startLine });
26457
+ }
26458
+ return;
26459
+ }
26424
26460
  if (body.length <= opts.maxChars) {
26425
26461
  out.push({ title, body, startLine: s.startLine, endLine: s.startLine + s.lines.length - 1 });
26426
- continue;
26462
+ return;
26427
26463
  }
26428
26464
  let fence = false;
26429
26465
  const fenceState = s.lines.map((l) => {
@@ -26439,7 +26475,7 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
26439
26475
  s.startLine - 1
26440
26476
  )
26441
26477
  );
26442
- }
26478
+ });
26443
26479
  return out;
26444
26480
  }
26445
26481
 
@@ -26522,7 +26558,20 @@ function chunk(content, baseTitle, kind, uri, opts = DEFAULT_CHUNKER) {
26522
26558
  // src/index/indexer.ts
26523
26559
  function indexContent(db, input2) {
26524
26560
  const kind = input2.kind ?? (input2.contentType === "code" ? "code" : input2.contentType === "json" ? "json" : void 0);
26525
- const { chunks, kind: k } = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
26561
+ const cut = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
26562
+ const k = cut.kind;
26563
+ const fallback = cut.chunks.length === 0 && input2.content.trim().length > 0;
26564
+ const chunks = fallback ? finalize2(
26565
+ [
26566
+ {
26567
+ title: input2.title,
26568
+ body: input2.content,
26569
+ startLine: 1,
26570
+ endLine: input2.content.split("\n").length
26571
+ }
26572
+ ],
26573
+ kindToContentType(k)
26574
+ ) : cut.chunks;
26526
26575
  const contentHash = hashText(input2.content);
26527
26576
  const existing = input2.uri ? db.findDocumentByUri(input2.uri) : void 0;
26528
26577
  if (existing && existing.content_hash === contentHash) {
@@ -26532,7 +26581,8 @@ function indexContent(db, input2) {
26532
26581
  created: false,
26533
26582
  chunks: chunks.length,
26534
26583
  chars: input2.content.length,
26535
- kind: k
26584
+ kind: k,
26585
+ fallback
26536
26586
  };
26537
26587
  }
26538
26588
  db.deleteDocument(existing.id);
@@ -26549,7 +26599,14 @@ function indexContent(db, input2) {
26549
26599
  },
26550
26600
  chunks
26551
26601
  );
26552
- return { documentId: id, created, chunks: chunks.length, chars: input2.content.length, kind: k };
26602
+ return {
26603
+ documentId: id,
26604
+ created,
26605
+ chunks: chunks.length,
26606
+ chars: input2.content.length,
26607
+ kind: k,
26608
+ fallback
26609
+ };
26553
26610
  }
26554
26611
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
26555
26612
 
@@ -26570,6 +26627,23 @@ function fileOf(id) {
26570
26627
  return void 0;
26571
26628
  }
26572
26629
 
26630
+ // src/graph/metrics.ts
26631
+ init_esm_shims();
26632
+
26633
+ // src/graph/clusters.ts
26634
+ init_esm_shims();
26635
+
26636
+ // src/graph/metrics.ts
26637
+ var DEPENDENCY_KINDS = [
26638
+ "calls",
26639
+ "references",
26640
+ "extends",
26641
+ "implements",
26642
+ "imports",
26643
+ "reexports",
26644
+ "handles"
26645
+ ];
26646
+
26573
26647
  // src/history/model.ts
26574
26648
  init_esm_shims();
26575
26649
 
@@ -27271,6 +27345,9 @@ function memoriesForFile(db, rel, opts = {}) {
27271
27345
  }
27272
27346
  var RISKY = 0.3;
27273
27347
  var FAN_IN = 5;
27348
+ function dependentFiles(db, paths2) {
27349
+ return db.graphDependentFileCounts(paths2, DEPENDENCY_KINDS);
27350
+ }
27274
27351
  function riskLine(path2, f) {
27275
27352
  if (f.risk < RISKY && !f.bugOrigins) return void 0;
27276
27353
  const parts = [`${f.fixes}/${f.commits} commits were fixes`];
@@ -27298,10 +27375,10 @@ function fileContext(db, rel, opts = {}) {
27298
27375
  }
27299
27376
  const fanKey = `fanin:${rel}`;
27300
27377
  if (opts.edit && !seen.has(fanKey)) {
27301
- const fanIn = db.metricsGet([fileId(rel)])[0]?.fan_in ?? 0;
27302
- if (fanIn >= FAN_IN) {
27378
+ const dependents = dependentFiles(db, [rel]).get(rel) ?? 0;
27379
+ if (dependents >= FAN_IN) {
27303
27380
  lines.push(
27304
- `eminiar graph: ${rel} is imported by ${fanIn} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
27381
+ `eminiar graph: ${rel} is imported by ${dependents} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
27305
27382
  );
27306
27383
  keys.push(fanKey);
27307
27384
  }
@@ -27313,7 +27390,7 @@ function commitContext(db, files) {
27313
27390
  if (!files.length) return "";
27314
27391
  const table = readFileRisk(db);
27315
27392
  const risky = files.map((p) => table[p] ? riskLine(p, table[p]) : void 0).filter((l) => !!l).slice(0, 4);
27316
- const wide = db.metricsGet(files.map(fileId)).filter((m) => m.fan_in >= FAN_IN).sort((a, b) => b.fan_in - a.fan_in).slice(0, 4).map((m) => `${m.path} (${m.fan_in} importers)`);
27393
+ const wide = [...dependentFiles(db, files)].filter(([, n]) => n >= FAN_IN).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([p, n]) => `${p} (${n} importers)`);
27317
27394
  if (!risky.length && !wide.length) return "";
27318
27395
  const lines = ["eminiar history: this commit includes files that deserve a second look:"];
27319
27396
  for (const r of risky) lines.push(`- ${r}`);
@@ -27597,6 +27674,10 @@ function topTermsOf(bodies, limit) {
27597
27674
 
27598
27675
  // src/storage/sqlite/schema.ts
27599
27676
  init_esm_shims();
27677
+ function hasColumn(db, table, column) {
27678
+ const cols = db.pragma(`table_info(${table})`);
27679
+ return cols.some((c) => c.name === column);
27680
+ }
27600
27681
  var migrations = [
27601
27682
  {
27602
27683
  version: 1,
@@ -27858,6 +27939,116 @@ CREATE TABLE IF NOT EXISTS index_runs (
27858
27939
  detail TEXT
27859
27940
  );
27860
27941
  `
27942
+ },
27943
+ {
27944
+ // Usage insights: api_calls mirrors one API request per Claude Code transcript entry
27945
+ // (deduped on request_id + message_id; cost cached at ingest), tool_uses pairs each
27946
+ // tool_use block with its tool_result size, context_events records compactions,
27947
+ // transcript_files keeps the byte offset so ingest only reads new bytes, and
27948
+ // statusline_snapshots stores what `eminiar statusline` saw. Column names = row fields.
27949
+ version: 5,
27950
+ sql: `
27951
+ CREATE TABLE IF NOT EXISTS api_calls (
27952
+ request_id TEXT NOT NULL,
27953
+ message_id TEXT NOT NULL,
27954
+ session_id TEXT NOT NULL,
27955
+ agent_id TEXT,
27956
+ sidechain INTEGER NOT NULL DEFAULT 0,
27957
+ ts INTEGER NOT NULL,
27958
+ model TEXT NOT NULL,
27959
+ input_tokens INTEGER NOT NULL DEFAULT 0,
27960
+ output_tokens INTEGER NOT NULL DEFAULT 0,
27961
+ cache_create_5m INTEGER NOT NULL DEFAULT 0,
27962
+ cache_create_1h INTEGER NOT NULL DEFAULT 0,
27963
+ cache_read INTEGER NOT NULL DEFAULT 0,
27964
+ thinking_tokens INTEGER NOT NULL DEFAULT 0,
27965
+ web_searches INTEGER NOT NULL DEFAULT 0,
27966
+ web_fetches INTEGER NOT NULL DEFAULT 0,
27967
+ service_tier TEXT,
27968
+ speed TEXT,
27969
+ stop_reason TEXT,
27970
+ prompt_id TEXT,
27971
+ git_branch TEXT,
27972
+ cwd TEXT,
27973
+ effort TEXT,
27974
+ version TEXT,
27975
+ entrypoint TEXT,
27976
+ cost_usd REAL,
27977
+ priced INTEGER NOT NULL DEFAULT 0,
27978
+ PRIMARY KEY (request_id, message_id)
27979
+ );
27980
+ CREATE INDEX IF NOT EXISTS api_calls_session_ts ON api_calls(session_id, ts);
27981
+ CREATE INDEX IF NOT EXISTS api_calls_ts ON api_calls(ts);
27982
+
27983
+ CREATE TABLE IF NOT EXISTS tool_uses (
27984
+ tool_use_id TEXT PRIMARY KEY,
27985
+ session_id TEXT NOT NULL,
27986
+ agent_id TEXT,
27987
+ ts INTEGER NOT NULL,
27988
+ tool TEXT NOT NULL,
27989
+ input_chars INTEGER NOT NULL DEFAULT 0,
27990
+ result_chars INTEGER,
27991
+ is_error INTEGER NOT NULL DEFAULT 0,
27992
+ request_id TEXT
27993
+ );
27994
+ CREATE INDEX IF NOT EXISTS tool_uses_session_ts ON tool_uses(session_id, ts);
27995
+ CREATE INDEX IF NOT EXISTS tool_uses_ts ON tool_uses(ts);
27996
+
27997
+ CREATE TABLE IF NOT EXISTS context_events (
27998
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27999
+ session_id TEXT NOT NULL,
28000
+ ts INTEGER NOT NULL,
28001
+ kind TEXT NOT NULL,
28002
+ trigger TEXT,
28003
+ pre_tokens INTEGER,
28004
+ post_tokens INTEGER
28005
+ );
28006
+ CREATE INDEX IF NOT EXISTS context_events_session_ts ON context_events(session_id, ts);
28007
+ CREATE INDEX IF NOT EXISTS context_events_ts ON context_events(ts);
28008
+
28009
+ CREATE TABLE IF NOT EXISTS transcript_files (
28010
+ path TEXT PRIMARY KEY,
28011
+ session_id TEXT,
28012
+ offset INTEGER NOT NULL DEFAULT 0,
28013
+ size INTEGER NOT NULL DEFAULT 0,
28014
+ mtime INTEGER NOT NULL DEFAULT 0,
28015
+ ingested_at INTEGER NOT NULL
28016
+ );
28017
+
28018
+ CREATE TABLE IF NOT EXISTS statusline_snapshots (
28019
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28020
+ session_id TEXT NOT NULL,
28021
+ ts INTEGER NOT NULL,
28022
+ model_id TEXT,
28023
+ cost_usd REAL,
28024
+ api_ms INTEGER,
28025
+ duration_ms INTEGER,
28026
+ ctx_input_tokens INTEGER,
28027
+ ctx_output_tokens INTEGER,
28028
+ ctx_size INTEGER,
28029
+ ctx_used_pct REAL,
28030
+ cache_warm INTEGER,
28031
+ cache_ttl TEXT,
28032
+ five_hour_pct REAL,
28033
+ five_hour_resets_at INTEGER,
28034
+ seven_day_pct REAL,
28035
+ seven_day_resets_at INTEGER,
28036
+ lines_added INTEGER,
28037
+ lines_removed INTEGER
28038
+ );
28039
+ CREATE INDEX IF NOT EXISTS statusline_snapshots_session_ts ON statusline_snapshots(session_id, ts);
28040
+ CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
28041
+ `
28042
+ },
28043
+ {
28044
+ // transcript_files.head_hash (fingerprint of the file head, so a rewrite with the same
28045
+ // size is noticed) was first added to v5 after v5 had already run on real databases, so
28046
+ // version-5 databases exist both with and without the column. Add it only when missing.
28047
+ version: 6,
28048
+ up(db) {
28049
+ if (!hasColumn(db, "transcript_files", "head_hash"))
28050
+ db.exec("ALTER TABLE transcript_files ADD COLUMN head_hash TEXT");
28051
+ }
27861
28052
  }
27862
28053
  ];
27863
28054
  function vecTableSql(dims) {
@@ -27896,6 +28087,27 @@ function filterSql(f) {
27896
28087
  }
27897
28088
  return { where: clauses.length ? ` AND ${clauses.join(" AND ")}` : "", params };
27898
28089
  }
28090
+ function usageWhere(f, opts = {}) {
28091
+ const clauses = ["1=1"];
28092
+ const params = [];
28093
+ if (f.sessionId) {
28094
+ clauses.push("session_id = ?");
28095
+ params.push(f.sessionId);
28096
+ }
28097
+ if (f.since !== void 0) {
28098
+ clauses.push("ts >= ?");
28099
+ params.push(f.since);
28100
+ }
28101
+ if (f.until !== void 0) {
28102
+ clauses.push("ts < ?");
28103
+ params.push(f.until);
28104
+ }
28105
+ if (f.sidechain !== void 0 && opts.sidechain !== null) {
28106
+ const expr = opts.sidechain ?? "sidechain = 1";
28107
+ clauses.push(f.sidechain ? expr : `NOT (${expr})`);
28108
+ }
28109
+ return { where: clauses.join(" AND "), params };
28110
+ }
27899
28111
  var SqliteAdapter = class _SqliteAdapter {
27900
28112
  constructor(path2, opts = {}) {
27901
28113
  this.path = path2;
@@ -27948,7 +28160,8 @@ var SqliteAdapter = class _SqliteAdapter {
27948
28160
  if (pending.length) {
27949
28161
  this.transaction(() => {
27950
28162
  for (const m of pending) {
27951
- this.db.exec(m.sql);
28163
+ if (m.sql) this.db.exec(m.sql);
28164
+ m.up?.(this.db);
27952
28165
  this.metaSet("schema_version", String(m.version));
27953
28166
  }
27954
28167
  });
@@ -28167,18 +28380,42 @@ var SqliteAdapter = class _SqliteAdapter {
28167
28380
  }
28168
28381
  vecSearch(embedding, k, filters) {
28169
28382
  if (!this.vectors) return [];
28170
- const raw = this.db.prepare(
28171
- "SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ORDER BY distance"
28172
- ).all(embedding, BigInt(filters ? k * 4 : k));
28173
- if (!filters || !raw.length) return raw.map((r) => ({ chunkId: r.id, distance: r.distance }));
28174
28383
  const { where, params } = filterSql(filters);
28175
- const allowed = new Set(
28176
- this.db.prepare(
28177
- `SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
28178
- WHERE c.id IN (${raw.map(() => "?").join(",")})${where}`
28179
- ).all(...raw.map((r) => r.id), ...params).map((r) => r.id)
28180
- );
28181
- return raw.filter((r) => allowed.has(r.id)).slice(0, k).map((r) => ({ chunkId: r.id, distance: r.distance }));
28384
+ if (!where) return this.knn(embedding, k);
28385
+ try {
28386
+ return this.knn(
28387
+ embedding,
28388
+ k,
28389
+ `AND chunk_id IN (SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id WHERE 1=1${where})`,
28390
+ params
28391
+ );
28392
+ } catch {
28393
+ return this.vecSearchWidening(embedding, k, where, params);
28394
+ }
28395
+ }
28396
+ knn(embedding, k, extra = "", params = []) {
28397
+ return this.db.prepare(
28398
+ `SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ${extra} ORDER BY distance`
28399
+ ).all(embedding, BigInt(k), ...params).map((r) => ({ chunkId: r.id, distance: r.distance }));
28400
+ }
28401
+ /** Fallback filtered KNN: fetch k*4, k*16, ... nearest until k pass the filter or all were seen. */
28402
+ vecSearchWidening(embedding, k, where, params) {
28403
+ const total = this.vecCount();
28404
+ for (let fetch = k * 4; ; fetch *= 4) {
28405
+ const raw = this.knn(embedding, Math.min(fetch, total));
28406
+ if (!raw.length) return [];
28407
+ const allowed = /* @__PURE__ */ new Set();
28408
+ for (let i = 0; i < raw.length; i += 400) {
28409
+ const ids = raw.slice(i, i + 400).map((r) => r.chunkId);
28410
+ for (const r of this.db.prepare(
28411
+ `SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
28412
+ WHERE c.id IN (${ids.map(() => "?").join(",")})${where}`
28413
+ ).all(...ids, ...params))
28414
+ allowed.add(r.id);
28415
+ }
28416
+ const hits = raw.filter((r) => allowed.has(r.chunkId)).slice(0, k);
28417
+ if (hits.length >= k || fetch >= total) return hits;
28418
+ }
28182
28419
  }
28183
28420
  unembeddedChunkIds(limit) {
28184
28421
  return this.db.prepare("SELECT id FROM chunks WHERE embedded = 0 ORDER BY id LIMIT ?").all(limit).map((r) => r.id);
@@ -28457,6 +28694,26 @@ var SqliteAdapter = class _SqliteAdapter {
28457
28694
  }
28458
28695
  return out;
28459
28696
  }
28697
+ graphDependentFileCounts(paths2, kinds) {
28698
+ const out = /* @__PURE__ */ new Map();
28699
+ if (!paths2.length || !kinds.length) return out;
28700
+ const kph = kinds.map(() => "?").join(",");
28701
+ for (let i = 0; i < paths2.length; i += 400) {
28702
+ const batch = paths2.slice(i, i + 400);
28703
+ const pph = batch.map(() => "?").join(",");
28704
+ for (const r of this.db.prepare(
28705
+ `SELECT d.path p, COUNT(DISTINCT s.path) n
28706
+ FROM graph_nodes d
28707
+ JOIN graph_edges e ON e.dst = d.id AND e.kind IN (${kph})
28708
+ JOIN graph_nodes s ON s.id = e.src
28709
+ WHERE d.path IN (${pph}) AND s.path IS NOT NULL AND s.path <> d.path
28710
+ GROUP BY d.path`
28711
+ ).all(...kinds, ...batch)) {
28712
+ out.set(r.p, r.n);
28713
+ }
28714
+ }
28715
+ return out;
28716
+ }
28460
28717
  edgesWhere(col, ids, kinds) {
28461
28718
  if (!ids.length) return [];
28462
28719
  const out = [];
@@ -28771,6 +29028,233 @@ var SqliteAdapter = class _SqliteAdapter {
28771
29028
  memoryCount() {
28772
29029
  return this.db.prepare("SELECT COUNT(*) n FROM memories").get().n;
28773
29030
  }
29031
+ // --- usage: model calls, tool results, context events, statusline ------------
29032
+ apiCallsUpsert(rows) {
29033
+ if (!rows.length) return 0;
29034
+ const ins = this.prep(
29035
+ `INSERT INTO api_calls (request_id, message_id, session_id, agent_id, sidechain, ts, model,
29036
+ input_tokens, output_tokens, cache_create_5m, cache_create_1h, cache_read, thinking_tokens,
29037
+ web_searches, web_fetches, service_tier, speed, stop_reason, prompt_id, git_branch, cwd, effort,
29038
+ version, entrypoint, cost_usd, priced)
29039
+ VALUES (@request_id, @message_id, @session_id, @agent_id, @sidechain, @ts, @model,
29040
+ @input_tokens, @output_tokens, @cache_create_5m, @cache_create_1h, @cache_read, @thinking_tokens,
29041
+ @web_searches, @web_fetches, @service_tier, @speed, @stop_reason, @prompt_id, @git_branch, @cwd, @effort,
29042
+ @version, @entrypoint, @cost_usd, @priced)
29043
+ ON CONFLICT(request_id, message_id) DO UPDATE SET
29044
+ cost_usd = CASE WHEN excluded.output_tokens >= api_calls.output_tokens THEN excluded.cost_usd ELSE api_calls.cost_usd END,
29045
+ priced = CASE WHEN excluded.output_tokens >= api_calls.output_tokens THEN excluded.priced ELSE api_calls.priced END,
29046
+ input_tokens = MAX(api_calls.input_tokens, excluded.input_tokens),
29047
+ output_tokens = MAX(api_calls.output_tokens, excluded.output_tokens),
29048
+ cache_create_5m = MAX(api_calls.cache_create_5m, excluded.cache_create_5m),
29049
+ cache_create_1h = MAX(api_calls.cache_create_1h, excluded.cache_create_1h),
29050
+ cache_read = MAX(api_calls.cache_read, excluded.cache_read),
29051
+ thinking_tokens = MAX(api_calls.thinking_tokens, excluded.thinking_tokens),
29052
+ web_searches = MAX(api_calls.web_searches, excluded.web_searches),
29053
+ web_fetches = MAX(api_calls.web_fetches, excluded.web_fetches),
29054
+ stop_reason = COALESCE(excluded.stop_reason, api_calls.stop_reason),
29055
+ service_tier = COALESCE(excluded.service_tier, api_calls.service_tier),
29056
+ speed = COALESCE(excluded.speed, api_calls.speed),
29057
+ prompt_id = COALESCE(api_calls.prompt_id, excluded.prompt_id),
29058
+ effort = COALESCE(api_calls.effort, excluded.effort)`
29059
+ );
29060
+ const count = this.prep("SELECT COUNT(*) AS n FROM api_calls");
29061
+ return this.transaction(() => {
29062
+ const before = count.get().n;
29063
+ for (const r of rows) {
29064
+ ins.run({
29065
+ request_id: r.request_id,
29066
+ message_id: r.message_id,
29067
+ session_id: r.session_id,
29068
+ agent_id: r.agent_id ?? null,
29069
+ sidechain: r.sidechain ? 1 : 0,
29070
+ ts: r.ts,
29071
+ model: r.model,
29072
+ input_tokens: r.input_tokens,
29073
+ output_tokens: r.output_tokens,
29074
+ cache_create_5m: r.cache_create_5m,
29075
+ cache_create_1h: r.cache_create_1h,
29076
+ cache_read: r.cache_read,
29077
+ thinking_tokens: r.thinking_tokens,
29078
+ web_searches: r.web_searches,
29079
+ web_fetches: r.web_fetches,
29080
+ service_tier: r.service_tier ?? null,
29081
+ speed: r.speed ?? null,
29082
+ stop_reason: r.stop_reason ?? null,
29083
+ prompt_id: r.prompt_id ?? null,
29084
+ git_branch: r.git_branch ?? null,
29085
+ cwd: r.cwd ?? null,
29086
+ effort: r.effort ?? null,
29087
+ version: r.version ?? null,
29088
+ entrypoint: r.entrypoint ?? null,
29089
+ cost_usd: r.cost_usd ?? null,
29090
+ priced: r.priced ?? 0
29091
+ });
29092
+ }
29093
+ return count.get().n - before;
29094
+ });
29095
+ }
29096
+ apiCalls(filter = {}) {
29097
+ const { where, params } = usageWhere(filter);
29098
+ params.push(filter.limit ?? 1e5);
29099
+ return this.prep(
29100
+ `SELECT * FROM api_calls WHERE ${where} ORDER BY ts ASC, rowid ASC LIMIT ?`
29101
+ ).all(...params);
29102
+ }
29103
+ apiCallsLatestTs() {
29104
+ const row = this.prep("SELECT MAX(ts) AS ts FROM api_calls").get();
29105
+ return row?.ts ?? void 0;
29106
+ }
29107
+ toolUsesUpsert(rows) {
29108
+ if (!rows.length) return 0;
29109
+ const ins = this.prep(
29110
+ `INSERT INTO tool_uses (tool_use_id, session_id, agent_id, ts, tool, input_chars, result_chars, is_error, request_id)
29111
+ VALUES (@tool_use_id, @session_id, @agent_id, @ts, @tool, @input_chars, @result_chars, @is_error, @request_id)
29112
+ ON CONFLICT(tool_use_id) DO UPDATE SET
29113
+ tool = CASE WHEN tool_uses.tool = '' THEN excluded.tool ELSE tool_uses.tool END,
29114
+ input_chars = MAX(tool_uses.input_chars, excluded.input_chars),
29115
+ result_chars = COALESCE(excluded.result_chars, tool_uses.result_chars),
29116
+ is_error = MAX(tool_uses.is_error, excluded.is_error),
29117
+ request_id = COALESCE(tool_uses.request_id, excluded.request_id),
29118
+ agent_id = COALESCE(tool_uses.agent_id, excluded.agent_id),
29119
+ ts = MIN(tool_uses.ts, excluded.ts)`
29120
+ );
29121
+ const count = this.prep("SELECT COUNT(*) AS n FROM tool_uses");
29122
+ return this.transaction(() => {
29123
+ const before = count.get().n;
29124
+ for (const r of rows) {
29125
+ ins.run({
29126
+ tool_use_id: r.tool_use_id,
29127
+ session_id: r.session_id,
29128
+ agent_id: r.agent_id ?? null,
29129
+ ts: r.ts,
29130
+ tool: r.tool,
29131
+ input_chars: r.input_chars,
29132
+ result_chars: r.result_chars ?? null,
29133
+ is_error: r.is_error ? 1 : 0,
29134
+ request_id: r.request_id ?? null
29135
+ });
29136
+ }
29137
+ return count.get().n - before;
29138
+ });
29139
+ }
29140
+ toolUses(filter = {}) {
29141
+ const { where, params } = usageWhere(filter, { sidechain: "agent_id IS NOT NULL" });
29142
+ params.push(filter.limit ?? 1e5);
29143
+ return this.prep(
29144
+ `SELECT * FROM tool_uses WHERE ${where} ORDER BY ts ASC, rowid ASC LIMIT ?`
29145
+ ).all(...params);
29146
+ }
29147
+ contextEventInsert(e) {
29148
+ const info = this.prep(
29149
+ `INSERT INTO context_events (session_id, ts, kind, trigger, pre_tokens, post_tokens)
29150
+ VALUES (?, ?, ?, ?, ?, ?)`
29151
+ ).run(
29152
+ e.session_id,
29153
+ e.ts,
29154
+ e.kind,
29155
+ e.trigger ?? null,
29156
+ e.pre_tokens ?? null,
29157
+ e.post_tokens ?? null
29158
+ );
29159
+ return Number(info.lastInsertRowid);
29160
+ }
29161
+ contextEvents(filter = {}) {
29162
+ const { where, params } = usageWhere(filter, { sidechain: null });
29163
+ params.push(filter.limit ?? 1e5);
29164
+ return this.prep(
29165
+ `SELECT * FROM context_events WHERE ${where} ORDER BY ts ASC, id ASC LIMIT ?`
29166
+ ).all(...params);
29167
+ }
29168
+ transcriptFileGet(path2) {
29169
+ return this.prep("SELECT * FROM transcript_files WHERE path = ?").get(path2);
29170
+ }
29171
+ transcriptFileSet(row) {
29172
+ this.prep(
29173
+ `INSERT INTO transcript_files (path, session_id, offset, size, mtime, head_hash, ingested_at)
29174
+ VALUES (@path, @session_id, @offset, @size, @mtime, @head_hash, @ingested_at)
29175
+ ON CONFLICT(path) DO UPDATE SET
29176
+ session_id = COALESCE(excluded.session_id, transcript_files.session_id),
29177
+ offset = excluded.offset, size = excluded.size, mtime = excluded.mtime,
29178
+ head_hash = excluded.head_hash, ingested_at = excluded.ingested_at`
29179
+ ).run({
29180
+ path: row.path,
29181
+ session_id: row.session_id ?? null,
29182
+ offset: row.offset,
29183
+ size: row.size,
29184
+ mtime: row.mtime,
29185
+ head_hash: row.head_hash ?? null,
29186
+ ingested_at: row.ingested_at
29187
+ });
29188
+ }
29189
+ transcriptFiles() {
29190
+ return this.prep("SELECT * FROM transcript_files ORDER BY path").all();
29191
+ }
29192
+ statuslineSnapshotInsert(s) {
29193
+ const info = this.prep(
29194
+ `INSERT INTO statusline_snapshots (session_id, ts, model_id, cost_usd, api_ms, duration_ms,
29195
+ ctx_input_tokens, ctx_output_tokens, ctx_size, ctx_used_pct, cache_warm, cache_ttl,
29196
+ five_hour_pct, five_hour_resets_at, seven_day_pct, seven_day_resets_at, lines_added, lines_removed)
29197
+ VALUES (@session_id, @ts, @model_id, @cost_usd, @api_ms, @duration_ms,
29198
+ @ctx_input_tokens, @ctx_output_tokens, @ctx_size, @ctx_used_pct, @cache_warm, @cache_ttl,
29199
+ @five_hour_pct, @five_hour_resets_at, @seven_day_pct, @seven_day_resets_at, @lines_added, @lines_removed)`
29200
+ ).run({
29201
+ session_id: s.session_id,
29202
+ ts: s.ts,
29203
+ model_id: s.model_id ?? null,
29204
+ cost_usd: s.cost_usd ?? null,
29205
+ api_ms: s.api_ms ?? null,
29206
+ duration_ms: s.duration_ms ?? null,
29207
+ ctx_input_tokens: s.ctx_input_tokens ?? null,
29208
+ ctx_output_tokens: s.ctx_output_tokens ?? null,
29209
+ ctx_size: s.ctx_size ?? null,
29210
+ ctx_used_pct: s.ctx_used_pct ?? null,
29211
+ cache_warm: s.cache_warm ?? null,
29212
+ cache_ttl: s.cache_ttl ?? null,
29213
+ five_hour_pct: s.five_hour_pct ?? null,
29214
+ five_hour_resets_at: s.five_hour_resets_at ?? null,
29215
+ seven_day_pct: s.seven_day_pct ?? null,
29216
+ seven_day_resets_at: s.seven_day_resets_at ?? null,
29217
+ lines_added: s.lines_added ?? null,
29218
+ lines_removed: s.lines_removed ?? null
29219
+ });
29220
+ return Number(info.lastInsertRowid);
29221
+ }
29222
+ statuslineSnapshots(filter = {}) {
29223
+ const { where, params } = usageWhere(filter, { sidechain: null });
29224
+ params.push(filter.limit ?? 1e5);
29225
+ return this.prep(
29226
+ `SELECT * FROM statusline_snapshots WHERE ${where} ORDER BY ts ASC, id ASC LIMIT ?`
29227
+ ).all(...params);
29228
+ }
29229
+ statuslineLatest(sessionId) {
29230
+ if (sessionId) {
29231
+ return this.prep(
29232
+ "SELECT * FROM statusline_snapshots WHERE session_id = ? ORDER BY ts DESC, id DESC LIMIT 1"
29233
+ ).get(sessionId);
29234
+ }
29235
+ return this.prep(
29236
+ "SELECT * FROM statusline_snapshots ORDER BY ts DESC, id DESC LIMIT 1"
29237
+ ).get();
29238
+ }
29239
+ statuslineSnapshotsPrune(opts) {
29240
+ const keep = Math.max(0, Math.floor(opts.keepPerSession));
29241
+ return this.transaction(() => {
29242
+ let n = this.prep("DELETE FROM statusline_snapshots WHERE ts < ?").run(
29243
+ opts.olderThan
29244
+ ).changes;
29245
+ n += this.prep(
29246
+ `DELETE FROM statusline_snapshots WHERE id IN (
29247
+ SELECT s.id FROM statusline_snapshots s
29248
+ WHERE s.id NOT IN (
29249
+ SELECT k.id FROM statusline_snapshots k
29250
+ WHERE k.session_id = s.session_id
29251
+ ORDER BY k.ts DESC, k.id DESC LIMIT ?
29252
+ )
29253
+ )`
29254
+ ).run(keep).changes;
29255
+ return n;
29256
+ });
29257
+ }
28774
29258
  // --- misc --------------------------------------------------------------
28775
29259
  metaGet(key) {
28776
29260
  const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
@@ -28801,6 +29285,11 @@ var SqliteAdapter = class _SqliteAdapter {
28801
29285
  "DELETE FROM graph_edges; DELETE FROM graph_nodes; DELETE FROM graph_metrics;"
28802
29286
  );
28803
29287
  }
29288
+ if (scope === "usage" || scope === "all") {
29289
+ this.db.exec(
29290
+ "DELETE FROM api_calls; DELETE FROM tool_uses; DELETE FROM context_events; DELETE FROM transcript_files; DELETE FROM statusline_snapshots;"
29291
+ );
29292
+ }
28804
29293
  if (scope === "memories" || scope === "all") {
28805
29294
  this.db.exec("DELETE FROM memories;");
28806
29295
  if (scope === "memories") {
@@ -28836,6 +29325,1556 @@ function getStorage(ctx, opts = {}) {
28836
29325
  return adapter;
28837
29326
  }
28838
29327
 
29328
+ // src/usage/brief.ts
29329
+ init_esm_shims();
29330
+
29331
+ // src/usage/aggregate.ts
29332
+ init_esm_shims();
29333
+
29334
+ // src/usage/blocks.ts
29335
+ init_esm_shims();
29336
+
29337
+ // src/usage/pricing.ts
29338
+ init_esm_shims();
29339
+ var WEB_SEARCH_PER_1K = 10;
29340
+ function price(input2, output2, cacheRead, extra = {}) {
29341
+ return {
29342
+ input: input2,
29343
+ output: output2,
29344
+ cacheWrite5m: input2 * 1.25,
29345
+ cacheWrite1h: input2 * 2,
29346
+ cacheRead,
29347
+ webSearch: WEB_SEARCH_PER_1K,
29348
+ ...extra
29349
+ };
29350
+ }
29351
+ var FABLE_5_1 = price(10, 50, 0.25);
29352
+ var FABLE_5 = price(10, 50, 1);
29353
+ var OPUS_5_5 = price(4, 20, 0.2);
29354
+ var OPUS_5 = price(5, 25, 0.5);
29355
+ var OPUS_4_LATE = price(5, 25, 0.5);
29356
+ var OPUS_4_EARLY = price(15, 75, 1.5);
29357
+ var SONNET_5_5 = price(2, 10, 0.1);
29358
+ var SONNET_5 = price(2, 10, 0.2);
29359
+ var SONNET_4 = price(3, 15, 0.3);
29360
+ var HAIKU_5_5 = price(0.1, 0.5, 0.01, {
29361
+ longContext: { threshold: 1e5, input: 0.5, output: 2.5 }
29362
+ });
29363
+ var HAIKU_4_5 = price(1, 5, 0.1);
29364
+ var HAIKU_3_5 = price(0.8, 4, 0.08);
29365
+ var FAMILIES = [
29366
+ ["fable-5-1", FABLE_5_1],
29367
+ ["mythos-5-1", FABLE_5_1],
29368
+ ["fable-5", FABLE_5],
29369
+ ["mythos-5", FABLE_5],
29370
+ ["opus-5-5", OPUS_5_5],
29371
+ ["opus-5", OPUS_5],
29372
+ ["opus-4-8", OPUS_4_LATE],
29373
+ ["opus-4-7", OPUS_4_LATE],
29374
+ ["opus-4-6", OPUS_4_LATE],
29375
+ ["opus-4-5", OPUS_4_LATE],
29376
+ ["opus-4-1", OPUS_4_EARLY],
29377
+ ["opus-4", OPUS_4_EARLY],
29378
+ ["sonnet-5-5", SONNET_5_5],
29379
+ ["sonnet-5", SONNET_5],
29380
+ ["sonnet-4-6", SONNET_4],
29381
+ ["sonnet-4-5", SONNET_4],
29382
+ ["sonnet-4", SONNET_4],
29383
+ ["haiku-5-5", HAIKU_5_5],
29384
+ ["haiku-4-5", HAIKU_4_5],
29385
+ ["3-5-haiku", HAIKU_3_5]
29386
+ ];
29387
+ var ALIASES = [
29388
+ ["fable", FABLE_5_1],
29389
+ ["mythos", FABLE_5_1],
29390
+ ["opus", OPUS_5_5],
29391
+ ["sonnet", SONNET_5_5],
29392
+ ["haiku", HAIKU_4_5]
29393
+ ];
29394
+ function bundledPrice(id) {
29395
+ for (const [pattern, p] of FAMILIES) if (id.includes(pattern)) return p;
29396
+ if (/\d/.test(id)) return void 0;
29397
+ for (const [pattern, p] of ALIASES) if (id.includes(pattern)) return p;
29398
+ return void 0;
29399
+ }
29400
+ function finite(n) {
29401
+ return typeof n === "number" && Number.isFinite(n) && n >= 0;
29402
+ }
29403
+ function resolvePrice(model, overrides) {
29404
+ const id = String(model ?? "").toLowerCase();
29405
+ if (!id) return void 0;
29406
+ const base = bundledPrice(id);
29407
+ if (!overrides) return base;
29408
+ for (const [pattern, o] of Object.entries(overrides)) {
29409
+ if (!pattern || !id.includes(pattern.toLowerCase())) continue;
29410
+ const input2 = finite(o.input) ? o.input : base?.input;
29411
+ const output2 = finite(o.output) ? o.output : base?.output;
29412
+ if (input2 === void 0 || output2 === void 0) return base;
29413
+ const merged = {
29414
+ input: input2,
29415
+ output: output2,
29416
+ cacheWrite5m: finite(o.cacheWrite5m) ? o.cacheWrite5m : base && !finite(o.input) ? base.cacheWrite5m : input2 * 1.25,
29417
+ cacheWrite1h: finite(o.cacheWrite1h) ? o.cacheWrite1h : base && !finite(o.input) ? base.cacheWrite1h : input2 * 2,
29418
+ cacheRead: finite(o.cacheRead) ? o.cacheRead : base && !finite(o.input) ? base.cacheRead : input2 * 0.1,
29419
+ webSearch: finite(o.webSearch) ? o.webSearch : base?.webSearch ?? WEB_SEARCH_PER_1K
29420
+ };
29421
+ if (o.longContext) merged.longContext = o.longContext;
29422
+ else if (base?.longContext) merged.longContext = base.longContext;
29423
+ return merged;
29424
+ }
29425
+ return base;
29426
+ }
29427
+ var tok = (n) => finite(n) ? n : 0;
29428
+ function costOfCall(call, price2) {
29429
+ const input2 = tok(call.input_tokens);
29430
+ const output2 = tok(call.output_tokens);
29431
+ const c5m = tok(call.cache_create_5m);
29432
+ const c1h = tok(call.cache_create_1h);
29433
+ const read = tok(call.cache_read);
29434
+ const searches = tok(call.web_searches);
29435
+ let p = price2;
29436
+ const lc = price2.longContext;
29437
+ if (lc && input2 + c5m + c1h + read > lc.threshold) {
29438
+ const ratio = price2.input > 0 ? lc.input / price2.input : 1;
29439
+ p = {
29440
+ ...price2,
29441
+ input: lc.input,
29442
+ output: lc.output,
29443
+ cacheWrite5m: price2.cacheWrite5m * ratio,
29444
+ cacheWrite1h: price2.cacheWrite1h * ratio,
29445
+ cacheRead: price2.cacheRead * ratio
29446
+ };
29447
+ }
29448
+ const M = 1e6;
29449
+ const breakdown = {
29450
+ input: input2 * p.input / M,
29451
+ output: output2 * p.output / M,
29452
+ cacheWrite: (c5m * p.cacheWrite5m + c1h * p.cacheWrite1h) / M,
29453
+ cacheRead: read * p.cacheRead / M,
29454
+ tools: searches / 1e3 * (p.webSearch ?? 0)
29455
+ };
29456
+ return {
29457
+ ...breakdown,
29458
+ total: breakdown.input + breakdown.output + breakdown.cacheWrite + breakdown.cacheRead + breakdown.tools
29459
+ };
29460
+ }
29461
+ function formatUsd(n) {
29462
+ if (!Number.isFinite(n) || n === 0) return "$0.00";
29463
+ const sign = n < 0 ? "-" : "";
29464
+ const v = Math.abs(n);
29465
+ if (v < 1e-4) return sign ? "-$0.0001" : "<$0.0001";
29466
+ if (v < 1) {
29467
+ const fixed = v.toFixed(4);
29468
+ if (fixed.startsWith("1")) return `${sign}$1.00`;
29469
+ return `${sign}$${fixed.replace(/0+$/, "").replace(/\.(\d)$/, ".$10")}`;
29470
+ }
29471
+ const two = v.toFixed(2);
29472
+ if (Number(two) >= 1e3) return `${sign}$${Math.round(v).toLocaleString("en-US")}`;
29473
+ return `${sign}$${two}`;
29474
+ }
29475
+ function estTokensFromChars(chars) {
29476
+ return finite(chars) ? Math.ceil(chars / 4) : 0;
29477
+ }
29478
+
29479
+ // src/usage/aggregate.ts
29480
+ var num = (n) => typeof n === "number" && Number.isFinite(n) ? n : 0;
29481
+ var isRealCall = (c) => c.model !== "<synthetic>";
29482
+ function totals(calls) {
29483
+ const t = {
29484
+ calls: 0,
29485
+ input: 0,
29486
+ output: 0,
29487
+ cacheCreate5m: 0,
29488
+ cacheCreate1h: 0,
29489
+ cacheRead: 0,
29490
+ thinking: 0,
29491
+ costUsd: 0,
29492
+ unpricedCalls: 0,
29493
+ cacheHitRate: 0,
29494
+ models: {}
29495
+ };
29496
+ for (const c of calls) {
29497
+ if (!isRealCall(c)) continue;
29498
+ t.calls += 1;
29499
+ t.input += num(c.input_tokens);
29500
+ t.output += num(c.output_tokens);
29501
+ t.cacheCreate5m += num(c.cache_create_5m);
29502
+ t.cacheCreate1h += num(c.cache_create_1h);
29503
+ t.cacheRead += num(c.cache_read);
29504
+ t.thinking += num(c.thinking_tokens);
29505
+ if (c.priced && c.cost_usd != null && Number.isFinite(c.cost_usd)) t.costUsd += c.cost_usd;
29506
+ else t.unpricedCalls += 1;
29507
+ const m = c.model || "unknown";
29508
+ t.models[m] = (t.models[m] ?? 0) + 1;
29509
+ }
29510
+ const denom = t.input + t.cacheCreate5m + t.cacheCreate1h + t.cacheRead;
29511
+ t.cacheHitRate = denom > 0 ? t.cacheRead / denom : 0;
29512
+ return t;
29513
+ }
29514
+ function groupBy(calls, keyFn) {
29515
+ const buckets = /* @__PURE__ */ new Map();
29516
+ for (const c of calls) {
29517
+ if (!isRealCall(c)) continue;
29518
+ const k = keyFn(c);
29519
+ if (k == null) continue;
29520
+ const list = buckets.get(k);
29521
+ if (list) list.push(c);
29522
+ else buckets.set(k, [c]);
29523
+ }
29524
+ return [...buckets.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, rows]) => ({ key, totals: totals(rows) }));
29525
+ }
29526
+ var fmtCache = /* @__PURE__ */ new Map();
29527
+ function formatter(tz) {
29528
+ const key = tz ?? "";
29529
+ let f = fmtCache.get(key);
29530
+ if (!f) {
29531
+ f = new Intl.DateTimeFormat("en-CA", {
29532
+ year: "numeric",
29533
+ month: "2-digit",
29534
+ day: "2-digit",
29535
+ ...tz ? { timeZone: tz } : {}
29536
+ });
29537
+ fmtCache.set(key, f);
29538
+ }
29539
+ return f;
29540
+ }
29541
+ function localYmd(ts, tz) {
29542
+ const parts = formatter(tz).formatToParts(new Date(ts));
29543
+ const get = (type) => Number(parts.find((p) => p.type === type)?.value ?? 0);
29544
+ return { y: get("year"), m: get("month"), d: get("day") };
29545
+ }
29546
+ var pad2 = (n) => String(n).padStart(2, "0");
29547
+ var ymdKey = ({ y, m, d }) => `${y}-${pad2(m)}-${pad2(d)}`;
29548
+ function dayKey(ts, tz) {
29549
+ return ymdKey(localYmd(ts, tz));
29550
+ }
29551
+ function byAgent(calls) {
29552
+ const mainRows = calls.filter((c) => !c.sidechain);
29553
+ const side = calls.filter((c) => !!c.sidechain);
29554
+ const main2 = totals(mainRows);
29555
+ const subagents = groupBy(side, (c) => c.agent_id || "unknown").map((g) => ({ agentId: g.key, totals: g.totals })).sort((a, b) => b.totals.costUsd - a.totals.costUsd || b.totals.calls - a.totals.calls);
29556
+ const subCost = subagents.reduce((s, a) => s + a.totals.costUsd, 0);
29557
+ const total = main2.costUsd + subCost;
29558
+ return { main: main2, subagents, delegationShare: total > 0 ? subCost / total : 0 };
29559
+ }
29560
+
29561
+ // src/usage/insights.ts
29562
+ init_esm_shims();
29563
+
29564
+ // src/usage/savings.ts
29565
+ init_esm_shims();
29566
+ var FALLBACK_MODEL = "claude-sonnet-5-5";
29567
+
29568
+ // src/usage/insights.ts
29569
+ var THRASH_WRITE_SHARE = 0.2;
29570
+ var THRASH_RUN = 3;
29571
+ var TTL_5M_MS = 5 * 6e4;
29572
+ var TTL_1H_MS = 60 * 6e4;
29573
+ var COLD_WRITE_SHARE = 0.5;
29574
+ var BLOAT_TOKENS = 8e3;
29575
+ var BLOAT_TOOLS = /* @__PURE__ */ new Set(["Read", "Bash", "Grep", "WebFetch", "Glob"]);
29576
+ var CHURN_COMPACTIONS = 3;
29577
+ var CHURN_EARLY_SHARE = 0.5;
29578
+ var DEFAULT_WINDOW = 2e5;
29579
+ var TRIVIAL_OUTPUT = 100;
29580
+ var TRIVIAL_SHARE = 0.3;
29581
+ var MIN_PREMIUM_CALLS = 5;
29582
+ var FAST_COST_SHARE = 0.25;
29583
+ var THINKING_SHARE = 0.4;
29584
+ var MIN_OUTPUT_FOR_THINKING = 1e3;
29585
+ var DELEGATION_SHARE = 0.6;
29586
+ var DEFAULT_TURN_USD = 1;
29587
+ var ERROR_RUN = 3;
29588
+ var NEAR_LIMIT_PCT = 85;
29589
+ var NEAR_LIMIT_HIGH = 95;
29590
+ var num2 = (n) => typeof n === "number" && Number.isFinite(n) ? n : 0;
29591
+ var pct = (x) => `${Math.round(x * 100)}%`;
29592
+ var usd = (n) => n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(3)}`;
29593
+ var round4 = (n) => Math.round(n * 1e4) / 1e4;
29594
+ var clamp = (n) => Math.max(0, Math.min(100, Math.round(n)));
29595
+ var fmtK = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${Math.round(n / 1e3)}k` : `${n}`;
29596
+ var contextOf = (c) => num2(c.input_tokens) + num2(c.cache_create_5m) + num2(c.cache_create_1h) + num2(c.cache_read);
29597
+ var writesOf = (c) => num2(c.cache_create_5m) + num2(c.cache_create_1h);
29598
+ var costOf = (c) => c.priced && c.cost_usd != null && Number.isFinite(c.cost_usd) ? c.cost_usd : 0;
29599
+ var isPremium = (model) => /opus|fable|mythos/i.test(model);
29600
+ function threads(calls) {
29601
+ const out = /* @__PURE__ */ new Map();
29602
+ for (const c of calls) {
29603
+ const key = `${c.session_id}\0${c.sidechain ? c.agent_id ?? "sidechain" : ""}`;
29604
+ const list = out.get(key);
29605
+ if (list) list.push(c);
29606
+ else out.set(key, [c]);
29607
+ }
29608
+ for (const list of out.values()) list.sort((a, b) => a.ts - b.ts);
29609
+ return out;
29610
+ }
29611
+ function groupBySession(rows) {
29612
+ const out = /* @__PURE__ */ new Map();
29613
+ for (const r of rows) {
29614
+ const list = out.get(r.session_id);
29615
+ if (list) list.push(r);
29616
+ else out.set(r.session_id, [r]);
29617
+ }
29618
+ return out;
29619
+ }
29620
+ function dominantModel(calls) {
29621
+ const counts = /* @__PURE__ */ new Map();
29622
+ for (const c of calls) counts.set(c.model, (counts.get(c.model) ?? 0) + 1);
29623
+ let best;
29624
+ let n = 0;
29625
+ for (const [m, k] of counts)
29626
+ if (k > n) {
29627
+ best = m;
29628
+ n = k;
29629
+ }
29630
+ return best;
29631
+ }
29632
+ function writePrice(c, p) {
29633
+ return num2(c.cache_create_1h) > num2(c.cache_create_5m) ? p.cacheWrite1h : p.cacheWrite5m;
29634
+ }
29635
+ function scopeFor(sessionIds) {
29636
+ const ids = [...new Set(sessionIds)];
29637
+ return ids.length === 1 ? { scope: "session", sessionId: ids[0] } : { scope: "project" };
29638
+ }
29639
+ function withScope(base, sessionIds) {
29640
+ const s = scopeFor(sessionIds);
29641
+ const out = { ...base, scope: s.scope };
29642
+ if (s.sessionId) out.sessionId = s.sessionId;
29643
+ return out;
29644
+ }
29645
+ var SEVERITY_RANK = { high: 0, warn: 1, info: 2 };
29646
+ function hasCompactionBetween(ctx, sessionId, from, to) {
29647
+ const list = ctx.compactionsBySession.get(sessionId);
29648
+ if (!list) return false;
29649
+ return list.some((t) => t > from && t <= to);
29650
+ }
29651
+ function detectCacheThrash(ctx) {
29652
+ let runs = 0;
29653
+ let longest = 0;
29654
+ let rewritten = 0;
29655
+ let savings = 0;
29656
+ const sessions = /* @__PURE__ */ new Set();
29657
+ for (const list of ctx.threads.values()) {
29658
+ let run = [];
29659
+ const flush = () => {
29660
+ if (run.length >= THRASH_RUN) {
29661
+ runs += 1;
29662
+ longest = Math.max(longest, run.length);
29663
+ for (const c of run) {
29664
+ const w = writesOf(c);
29665
+ rewritten += w;
29666
+ sessions.add(c.session_id);
29667
+ const p = ctx.priceOf(c.model);
29668
+ if (p) savings += w * (writePrice(c, p) - p.cacheRead) / 1e6;
29669
+ }
29670
+ }
29671
+ run = [];
29672
+ };
29673
+ for (let i = 1; i < list.length; i++) {
29674
+ const c = list[i];
29675
+ const prev = list[i - 1];
29676
+ const ctxTokens = contextOf(c);
29677
+ const heavy = ctxTokens > 0 && writesOf(c) / ctxTokens >= THRASH_WRITE_SHARE;
29678
+ const broken = hasCompactionBetween(ctx, c.session_id, prev.ts, c.ts) || c.ts - prev.ts > ttlFor(list);
29679
+ if (!heavy || broken) {
29680
+ flush();
29681
+ continue;
29682
+ }
29683
+ run.push(c);
29684
+ }
29685
+ flush();
29686
+ }
29687
+ if (runs === 0) return void 0;
29688
+ return withScope(
29689
+ {
29690
+ id: "cache-thrash",
29691
+ severity: runs >= 3 || rewritten >= 5e5 ? "high" : "warn",
29692
+ title: "Prompt cache keeps being rewritten",
29693
+ reason: `${runs} run${runs === 1 ? "" : "s"} of ${THRASH_RUN}+ consecutive calls rewrote \u2265 ${pct(THRASH_WRITE_SHARE)} of their context (longest run ${longest} calls, ${fmtK(rewritten)} tokens written again).`,
29694
+ evidence: { runs, longestRun: longest, tokensRewritten: rewritten },
29695
+ estimatedSavingsUsd: round4(savings),
29696
+ recommendation: "Something early in the prompt changes every turn (system prompt, tool list, a file re-read with a timestamp); keep the stable prefix stable so later calls hit the cache."
29697
+ },
29698
+ sessions
29699
+ );
29700
+ }
29701
+ function ttlFor(list) {
29702
+ let w5 = 0;
29703
+ let w1 = 0;
29704
+ for (const c of list) {
29705
+ w5 += num2(c.cache_create_5m);
29706
+ w1 += num2(c.cache_create_1h);
29707
+ }
29708
+ return w1 > w5 ? TTL_1H_MS : TTL_5M_MS;
29709
+ }
29710
+ function detectCacheCold(ctx) {
29711
+ let restarts = 0;
29712
+ let rewritten = 0;
29713
+ let savings = 0;
29714
+ let longestGap = 0;
29715
+ const sessions = /* @__PURE__ */ new Set();
29716
+ for (const list of ctx.threads.values()) {
29717
+ const ttl = ttlFor(list);
29718
+ for (let i = 1; i < list.length; i++) {
29719
+ const c = list[i];
29720
+ const prev = list[i - 1];
29721
+ const gap = c.ts - prev.ts;
29722
+ if (gap <= ttl) continue;
29723
+ if (hasCompactionBetween(ctx, c.session_id, prev.ts, c.ts)) continue;
29724
+ const ctxTokens = contextOf(c);
29725
+ const w = writesOf(c);
29726
+ if (ctxTokens === 0 || w / ctxTokens < COLD_WRITE_SHARE) continue;
29727
+ restarts += 1;
29728
+ rewritten += w;
29729
+ longestGap = Math.max(longestGap, gap);
29730
+ sessions.add(c.session_id);
29731
+ const p = ctx.priceOf(c.model);
29732
+ if (p) savings += w * (writePrice(c, p) - p.cacheRead) / 1e6;
29733
+ }
29734
+ }
29735
+ if (restarts === 0) return void 0;
29736
+ const minutes = Math.round(longestGap / 6e4);
29737
+ return withScope(
29738
+ {
29739
+ id: "cache-cold",
29740
+ severity: restarts >= 2 ? "warn" : "info",
29741
+ title: `Cache went cold ${restarts === 1 ? "once" : `${restarts} times`}`,
29742
+ reason: `${restarts} pause${restarts === 1 ? "" : "s"} longer than the cache TTL (longest ${minutes} min) forced ${fmtK(rewritten)} tokens to be written to cache again (~${usd(savings)}).`,
29743
+ evidence: { restarts, tokensRewritten: rewritten, longestGapMinutes: minutes },
29744
+ estimatedSavingsUsd: round4(savings),
29745
+ recommendation: "Long pauses mid-session re-pay the cache write; for walk-away breaks start a new session, or keep the turn cadence inside the TTL."
29746
+ },
29747
+ sessions
29748
+ );
29749
+ }
29750
+ function detectContextBloat(ctx) {
29751
+ const bySession = groupBySession(ctx.calls);
29752
+ const modelFor = /* @__PURE__ */ new Map();
29753
+ for (const [sid, rows] of bySession) modelFor.set(sid, dominantModel(rows));
29754
+ let count = 0;
29755
+ let tokens = 0;
29756
+ let savings = 0;
29757
+ const byTool = /* @__PURE__ */ new Map();
29758
+ const sessions = /* @__PURE__ */ new Set();
29759
+ for (const u of ctx.toolUses) {
29760
+ if (!BLOAT_TOOLS.has(u.tool)) continue;
29761
+ const est = estTokensFromChars(num2(u.result_chars));
29762
+ if (est <= BLOAT_TOKENS) continue;
29763
+ count += 1;
29764
+ tokens += est;
29765
+ byTool.set(u.tool, (byTool.get(u.tool) ?? 0) + 1);
29766
+ sessions.add(u.session_id);
29767
+ const p = ctx.priceOf(modelFor.get(u.session_id) ?? FALLBACK_MODEL) ?? ctx.priceOf(FALLBACK_MODEL);
29768
+ if (p) {
29769
+ const later = ctx.laterCalls(u.session_id, u.ts);
29770
+ savings += (est * p.cacheWrite5m + est * p.cacheRead * later) / 1e6;
29771
+ }
29772
+ }
29773
+ if (count === 0) return void 0;
29774
+ const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
29775
+ return withScope(
29776
+ {
29777
+ id: "context-bloat",
29778
+ severity: count >= 5 || tokens >= 1e5 ? "high" : "warn",
29779
+ title: "Oversized tool results in context",
29780
+ reason: `${count} ${[...byTool.keys()].join("/")} result${count === 1 ? "" : "s"} above ${fmtK(BLOAT_TOKENS)} tokens put ~${fmtK(tokens)} tokens into context (mostly ${top[0]}, ${top[1]}\xD7), re-read on every later call.`,
29781
+ evidence: { results: count, estTokens: tokens, topTool: top[0], topToolCount: top[1] },
29782
+ estimatedSavingsUsd: round4(savings),
29783
+ recommendation: "Route large outputs through em_execute / em_execute_file / em_search so only the matching sections come back."
29784
+ },
29785
+ sessions
29786
+ );
29787
+ }
29788
+ function detectCompactionChurn(ctx) {
29789
+ const windowFor = /* @__PURE__ */ new Map();
29790
+ for (const s of ctx.snapshots) {
29791
+ if (num2(s.ctx_size) > 0) {
29792
+ const prev = windowFor.get(s.session_id);
29793
+ if (prev === void 0) windowFor.set(s.session_id, num2(s.ctx_size));
29794
+ }
29795
+ }
29796
+ let worstSession;
29797
+ let worstCount = 0;
29798
+ let early = 0;
29799
+ const sessions = /* @__PURE__ */ new Set();
29800
+ for (const [sid, evs] of groupBySession(ctx.contextEvents)) {
29801
+ const compactions = evs.filter((e) => e.kind === "compact");
29802
+ const window2 = windowFor.get(sid) ?? DEFAULT_WINDOW;
29803
+ const earlyHere = compactions.filter(
29804
+ (e) => e.pre_tokens != null && e.pre_tokens > 0 && e.pre_tokens < window2 * CHURN_EARLY_SHARE
29805
+ ).length;
29806
+ if (compactions.length >= CHURN_COMPACTIONS || earlyHere > 0) {
29807
+ sessions.add(sid);
29808
+ early += earlyHere;
29809
+ if (compactions.length > worstCount) {
29810
+ worstCount = compactions.length;
29811
+ worstSession = sid;
29812
+ }
29813
+ }
29814
+ }
29815
+ if (sessions.size === 0) return void 0;
29816
+ return withScope(
29817
+ {
29818
+ id: "compaction-churn",
29819
+ severity: worstCount >= CHURN_COMPACTIONS * 2 ? "high" : "warn",
29820
+ title: "Frequent context compactions",
29821
+ reason: `${sessions.size} session${sessions.size === 1 ? "" : "s"} compacted repeatedly (up to ${worstCount} times in ${(worstSession ?? "").slice(0, 8)}; ${early} below ${pct(CHURN_EARLY_SHARE)} of the window).`,
29822
+ evidence: { sessions: sessions.size, maxCompactions: worstCount, earlyCompactions: early },
29823
+ recommendation: "Each compaction rewrites the whole cache and loses detail; keep raw output out of context (em_* tools) and split long tasks into sessions."
29824
+ },
29825
+ sessions
29826
+ );
29827
+ }
29828
+ function detectOpusForTrivial(ctx) {
29829
+ const toolRequests = /* @__PURE__ */ new Set();
29830
+ for (const u of ctx.toolUses) if (u.request_id) toolRequests.add(u.request_id);
29831
+ const premium = ctx.calls.filter((c) => isPremium(c.model));
29832
+ if (premium.length < MIN_PREMIUM_CALLS) return void 0;
29833
+ const trivial = premium.filter(
29834
+ (c) => num2(c.output_tokens) < TRIVIAL_OUTPUT && c.stop_reason !== "tool_use" && !toolRequests.has(c.request_id)
29835
+ );
29836
+ const share = trivial.length / premium.length;
29837
+ if (share <= TRIVIAL_SHARE) return void 0;
29838
+ const sonnet = resolvePrice(FALLBACK_MODEL, ctx.overrides);
29839
+ let spent = 0;
29840
+ let repriced = 0;
29841
+ for (const c of trivial) {
29842
+ spent += costOf(c);
29843
+ if (sonnet) repriced += costOfCall(c, sonnet).total;
29844
+ }
29845
+ const savings = Math.max(0, spent - repriced);
29846
+ return withScope(
29847
+ {
29848
+ id: "opus-for-trivial",
29849
+ severity: share >= 0.6 ? "warn" : "info",
29850
+ title: "Premium model on trivial turns",
29851
+ reason: `${trivial.length} of ${premium.length} premium-model calls (${pct(share)}) produced under ${TRIVIAL_OUTPUT} output tokens with no tool use, costing ${usd(spent)} (~${usd(repriced)} at Sonnet).`,
29852
+ evidence: {
29853
+ trivialCalls: trivial.length,
29854
+ premiumCalls: premium.length,
29855
+ share: Math.round(share * 100),
29856
+ spentUsd: round4(spent)
29857
+ },
29858
+ estimatedSavingsUsd: round4(savings),
29859
+ recommendation: "Short acknowledgements and lookups do not need the premium model; delegate routine turns to a Sonnet/Haiku subagent or switch model for simple work."
29860
+ },
29861
+ trivial.map((c) => c.session_id)
29862
+ );
29863
+ }
29864
+ function detectFastModeShare(ctx) {
29865
+ let fast = 0;
29866
+ let fastCalls = 0;
29867
+ let total = 0;
29868
+ const sessions = /* @__PURE__ */ new Set();
29869
+ for (const c of ctx.calls) {
29870
+ const cost = costOf(c);
29871
+ total += cost;
29872
+ if (c.speed === "fast") {
29873
+ fast += cost;
29874
+ fastCalls += 1;
29875
+ sessions.add(c.session_id);
29876
+ }
29877
+ }
29878
+ if (total <= 0) return void 0;
29879
+ const share = fast / total;
29880
+ if (share <= FAST_COST_SHARE) return void 0;
29881
+ return withScope(
29882
+ {
29883
+ id: "fast-mode-share",
29884
+ severity: "info",
29885
+ title: "Fast mode is a large share of spend",
29886
+ reason: `${fastCalls} fast-mode calls account for ${usd(fast)} of ${usd(total)} (${pct(share)}) of priced cost.`,
29887
+ evidence: {
29888
+ fastCalls,
29889
+ fastUsd: round4(fast),
29890
+ totalUsd: round4(total),
29891
+ share: Math.round(share * 100)
29892
+ },
29893
+ recommendation: "Fast mode is billed at a premium; reserve it for interactive turns where latency matters."
29894
+ },
29895
+ sessions
29896
+ );
29897
+ }
29898
+ function detectThinkingShare(ctx) {
29899
+ const t = totals(ctx.calls);
29900
+ if (t.output < MIN_OUTPUT_FOR_THINKING || t.thinking <= 0) return void 0;
29901
+ const share = t.thinking / t.output;
29902
+ if (share <= THINKING_SHARE) return void 0;
29903
+ return withScope(
29904
+ {
29905
+ id: "thinking-share",
29906
+ severity: "info",
29907
+ title: "Most output tokens are thinking",
29908
+ reason: `${fmtK(t.thinking)} of ${fmtK(t.output)} output tokens (${pct(share)}) were thinking, billed at the output rate.`,
29909
+ evidence: {
29910
+ thinkingTokens: t.thinking,
29911
+ outputTokens: t.output,
29912
+ share: Math.round(share * 100)
29913
+ },
29914
+ recommendation: "Lower the effort level for routine edits and lookups; keep high effort for design and debugging."
29915
+ },
29916
+ ctx.calls.map((c) => c.session_id)
29917
+ );
29918
+ }
29919
+ function detectSubagentCost(ctx) {
29920
+ const a = byAgent(ctx.calls);
29921
+ if (a.delegationShare <= DELEGATION_SHARE) return void 0;
29922
+ const subCost = a.subagents.reduce((s, x) => s + x.totals.costUsd, 0);
29923
+ const subCalls = a.subagents.reduce((s, x) => s + x.totals.calls, 0);
29924
+ return withScope(
29925
+ {
29926
+ id: "subagent-cost",
29927
+ severity: "info",
29928
+ title: "Subagents dominate spend",
29929
+ reason: `${a.subagents.length} subagent${a.subagents.length === 1 ? "" : "s"} made ${subCalls} calls for ${usd(subCost)}, ${pct(a.delegationShare)} of priced cost (main thread ${usd(a.main.costUsd)}).`,
29930
+ evidence: {
29931
+ subagents: a.subagents.length,
29932
+ subagentCalls: subCalls,
29933
+ subagentUsd: round4(subCost),
29934
+ share: Math.round(a.delegationShare * 100)
29935
+ },
29936
+ recommendation: "Check that subagents run on the cheapest model that fits (model: sonnet/haiku) and return summaries, not transcripts."
29937
+ },
29938
+ ctx.calls.filter((c) => c.sidechain).map((c) => c.session_id)
29939
+ );
29940
+ }
29941
+ var DRIVER_ADVICE = {
29942
+ "cache-write": {
29943
+ what: "cache writes",
29944
+ recommendation: "Cache writes dominate: a long idle gap or a cache-busting change (edited system prompt, reordered tools) re-wrote the prefix. Keep the prefix stable, or use the 1h cache deliberately for long pauses."
29945
+ },
29946
+ output: {
29947
+ what: "output",
29948
+ recommendation: "Output dominates: a long answer or heavy thinking. Lower the effort level or ask for a shorter answer; keep high effort for design and debugging."
29949
+ },
29950
+ context: {
29951
+ what: "context",
29952
+ recommendation: "Context dominates: the call re-read a large conversation. Compact, start a fresh session, or move the bulk into a subagent."
29953
+ }
29954
+ };
29955
+ function detectExpensiveTurn(ctx) {
29956
+ const limit = ctx.budgets.turnUsd ?? DEFAULT_TURN_USD;
29957
+ const hits = ctx.calls.filter((c) => costOf(c) > limit).sort((a, b2) => costOf(b2) - costOf(a));
29958
+ if (hits.length === 0) return void 0;
29959
+ const top = hits[0];
29960
+ const price2 = ctx.priceOf(top.model);
29961
+ const b = price2 ? costOfCall(top, price2) : { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, tools: 0, total: costOf(top) };
29962
+ const contextUsd = b.input + b.cacheRead;
29963
+ const driver = b.cacheWrite > b.output && b.cacheWrite > contextUsd ? "cache-write" : b.output > contextUsd ? "output" : "context";
29964
+ const advice = DRIVER_ADVICE[driver];
29965
+ const n = hits.length;
29966
+ return withScope(
29967
+ {
29968
+ id: "expensive-turn",
29969
+ severity: costOf(top) > limit * 3 ? "high" : "warn",
29970
+ title: `${n} call${n === 1 ? "" : "s"} over ${usd(limit)}`,
29971
+ reason: `${n} call${n === 1 ? "" : "s"} cost more than ${usd(limit)} each; the largest was ${usd(costOf(top))} on ${top.model} with ${fmtK(contextOf(top))} context tokens, driven by ${advice.what} (context ${usd(contextUsd)}, output ${usd(b.output)}, cache writes ${usd(b.cacheWrite)}).`,
29972
+ evidence: {
29973
+ calls: n,
29974
+ maxUsd: round4(costOf(top)),
29975
+ model: top.model,
29976
+ contextTokens: contextOf(top),
29977
+ limitUsd: limit,
29978
+ driver,
29979
+ contextUsd: round4(contextUsd),
29980
+ outputUsd: round4(b.output),
29981
+ cacheWriteUsd: round4(b.cacheWrite)
29982
+ },
29983
+ recommendation: advice.recommendation
29984
+ },
29985
+ hits.map((c) => c.session_id)
29986
+ );
29987
+ }
29988
+ function detectErrorLoop(ctx) {
29989
+ const loops = ctx.events.filter((e) => e.category === "iteration_loop");
29990
+ const sessions = new Set(loops.map((e) => e.session_id));
29991
+ let worstTool = "";
29992
+ let worstRun = 0;
29993
+ const byThread = /* @__PURE__ */ new Map();
29994
+ for (const u of ctx.toolUses) {
29995
+ const key = `${u.session_id}\0${u.agent_id ?? ""}`;
29996
+ const list = byThread.get(key);
29997
+ if (list) list.push(u);
29998
+ else byThread.set(key, [u]);
29999
+ }
30000
+ for (const list of byThread.values()) {
30001
+ list.sort((a, b) => a.ts - b.ts);
30002
+ let run = 0;
30003
+ let tool = "";
30004
+ for (const u of list) {
30005
+ if (u.is_error && u.tool === tool) run += 1;
30006
+ else {
30007
+ run = u.is_error ? 1 : 0;
30008
+ tool = u.is_error ? u.tool : "";
30009
+ }
30010
+ if (run >= ERROR_RUN) {
30011
+ sessions.add(u.session_id);
30012
+ if (run > worstRun) {
30013
+ worstRun = run;
30014
+ worstTool = tool;
30015
+ }
30016
+ }
30017
+ }
30018
+ }
30019
+ if (loops.length === 0 && worstRun === 0) return void 0;
30020
+ const parts = [];
30021
+ if (loops.length)
30022
+ parts.push(`${loops.length} repeated-call loop${loops.length === 1 ? "" : "s"} recorded`);
30023
+ if (worstRun) parts.push(`${worstRun} consecutive ${worstTool} failures`);
30024
+ return withScope(
30025
+ {
30026
+ id: "error-loop",
30027
+ severity: "warn",
30028
+ title: "Repeated failures or loops",
30029
+ reason: `${parts.join(" and ")}; every retry re-reads the whole context.`,
30030
+ evidence: { loops: loops.length, longestErrorRun: worstRun, tool: worstTool },
30031
+ recommendation: "After two identical failures stop and change approach (em_recall for an earlier fix, read the error, try a different tool)."
30032
+ },
30033
+ sessions
30034
+ );
30035
+ }
30036
+ function detectBudget(ctx) {
30037
+ const out = [];
30038
+ const { sessionUsd, dailyUsd } = ctx.budgets;
30039
+ if (sessionUsd) {
30040
+ for (const [sid, rows] of groupBySession(ctx.calls)) {
30041
+ const cost = totals(rows).costUsd;
30042
+ if (cost <= sessionUsd) continue;
30043
+ out.push({
30044
+ id: "budget",
30045
+ severity: "high",
30046
+ title: "Session budget exceeded",
30047
+ reason: `Session ${sid.slice(0, 8)} has cost ${usd(cost)}, over the ${usd(sessionUsd)} session budget (${pct(cost / sessionUsd)}).`,
30048
+ evidence: { sessionUsd: round4(cost), budgetUsd: sessionUsd },
30049
+ scope: "session",
30050
+ sessionId: sid
30051
+ });
30052
+ }
30053
+ }
30054
+ if (dailyUsd) {
30055
+ const today = dayKey(ctx.now);
30056
+ const cost = totals(ctx.calls.filter((c) => dayKey(c.ts) === today)).costUsd;
30057
+ if (cost > dailyUsd)
30058
+ out.push({
30059
+ id: "budget",
30060
+ severity: "high",
30061
+ title: "Daily budget exceeded",
30062
+ reason: `Today (${today}) has cost ${usd(cost)}, over the ${usd(dailyUsd)} daily budget (${pct(cost / dailyUsd)}).`,
30063
+ evidence: { day: today, todayUsd: round4(cost), budgetUsd: dailyUsd },
30064
+ scope: "project"
30065
+ });
30066
+ }
30067
+ return out;
30068
+ }
30069
+ function detectUnpricedModel(ctx) {
30070
+ const unpriced = ctx.calls.filter((c) => !c.priced);
30071
+ if (unpriced.length === 0) return void 0;
30072
+ const models = [...new Set(unpriced.map((c) => c.model || "unknown"))];
30073
+ return withScope(
30074
+ {
30075
+ id: "unpriced-model",
30076
+ severity: "info",
30077
+ title: "Some calls are unpriced",
30078
+ reason: `${unpriced.length} of ${ctx.calls.length} calls use ${models.length} model${models.length === 1 ? "" : "s"} without a price (${models.slice(0, 3).join(", ")}), so cost totals undercount.`,
30079
+ evidence: { calls: unpriced.length, models: models.join(", ") },
30080
+ recommendation: 'Add the model to config.usage.pricing, e.g. {"pricing": {"<model substring>": {"input": 3, "output": 15}}} (USD per MTok).'
30081
+ },
30082
+ unpriced.map((c) => c.session_id)
30083
+ );
30084
+ }
30085
+ function detectContextNearLimit(ctx) {
30086
+ let latest;
30087
+ for (const s of ctx.snapshots) if (!latest || s.ts > latest.ts) latest = s;
30088
+ if (!latest || latest.ctx_used_pct == null || latest.ctx_used_pct < NEAR_LIMIT_PCT)
30089
+ return void 0;
30090
+ const p = latest.ctx_used_pct;
30091
+ return {
30092
+ id: "context-near-limit",
30093
+ severity: p >= NEAR_LIMIT_HIGH ? "high" : "warn",
30094
+ title: "Context window nearly full",
30095
+ reason: `The latest statusline reports ${Math.round(p)}% of the ${fmtK(num2(latest.ctx_size) || DEFAULT_WINDOW)}-token window in use; the next compaction is close.`,
30096
+ evidence: { ctxUsedPct: Math.round(p), ctxSize: num2(latest.ctx_size) },
30097
+ recommendation: "Save durable findings with em_remember now, then compact or start a fresh session.",
30098
+ scope: "session",
30099
+ sessionId: latest.session_id
30100
+ };
30101
+ }
30102
+ function findInsight(insights, id) {
30103
+ return insights.find((i) => i.id === id);
30104
+ }
30105
+ var ev2 = (i, key) => {
30106
+ const v = i?.evidence?.[key];
30107
+ return typeof v === "number" ? v : 0;
30108
+ };
30109
+ function scoreContextHealth(ctx, insights) {
30110
+ const reasons = [];
30111
+ let v = 100;
30112
+ const bloat = ev2(findInsight(insights, "context-bloat"), "results");
30113
+ if (bloat) {
30114
+ const d = Math.min(30, bloat * 5);
30115
+ v -= d;
30116
+ reasons.push(`${bloat} oversized tool result${bloat === 1 ? "" : "s"} in context (\u2212${d})`);
30117
+ }
30118
+ let extra = 0;
30119
+ for (const evs of groupBySession(ctx.contextEvents).values())
30120
+ extra += Math.max(0, evs.filter((e) => e.kind === "compact").length - 1);
30121
+ if (extra) {
30122
+ const d = Math.min(30, extra * 10);
30123
+ v -= d;
30124
+ reasons.push(
30125
+ `${extra} compaction${extra === 1 ? "" : "s"} beyond the first per session (\u2212${d})`
30126
+ );
30127
+ }
30128
+ const near = findInsight(insights, "context-near-limit");
30129
+ if (near) {
30130
+ v -= 20;
30131
+ reasons.push(`context ${ev2(near, "ctxUsedPct")}% full (\u221220)`);
30132
+ }
30133
+ const loop = findInsight(insights, "error-loop");
30134
+ if (loop) {
30135
+ const n = ev2(loop, "loops") + (ev2(loop, "longestErrorRun") ? 1 : 0);
30136
+ const d = Math.min(20, n * 10);
30137
+ v -= d;
30138
+ reasons.push(`${n} error loop${n === 1 ? "" : "s"} (\u2212${d})`);
30139
+ }
30140
+ if (!reasons.length)
30141
+ reasons.push(
30142
+ ctx.calls.length ? "no oversized results, compaction churn or loops" : "no usage recorded yet"
30143
+ );
30144
+ return { id: "context-health", value: clamp(v), reasons };
30145
+ }
30146
+ function scoreCostEfficiency(ctx, insights) {
30147
+ const reasons = [];
30148
+ let v = 100;
30149
+ const trivial = findInsight(insights, "opus-for-trivial");
30150
+ if (trivial) {
30151
+ const d = Math.round(ev2(trivial, "share") / 100 * 50);
30152
+ v -= d;
30153
+ reasons.push(`${ev2(trivial, "share")}% of premium-model calls were trivial (\u2212${d})`);
30154
+ }
30155
+ const fast = findInsight(insights, "fast-mode-share");
30156
+ if (fast) {
30157
+ const d = Math.round(ev2(fast, "share") / 100 * 40);
30158
+ v -= d;
30159
+ reasons.push(`fast mode is ${ev2(fast, "share")}% of spend (\u2212${d})`);
30160
+ }
30161
+ const exp = findInsight(insights, "expensive-turn");
30162
+ if (exp) {
30163
+ const n = ev2(exp, "calls");
30164
+ const d = Math.min(30, n * 10);
30165
+ v -= d;
30166
+ reasons.push(`${n} call${n === 1 ? "" : "s"} over ${usd(ev2(exp, "limitUsd"))} (\u2212${d})`);
30167
+ }
30168
+ const unpriced = findInsight(insights, "unpriced-model");
30169
+ if (unpriced && ctx.calls.length) {
30170
+ const share = ev2(unpriced, "calls") / ctx.calls.length;
30171
+ const d = Math.round(share * 20);
30172
+ v -= d;
30173
+ reasons.push(`${pct(share)} of calls unpriced (\u2212${d})`);
30174
+ }
30175
+ const budgets = insights.filter((i) => i.id === "budget").length;
30176
+ if (budgets) {
30177
+ const d = Math.min(40, budgets * 20);
30178
+ v -= d;
30179
+ reasons.push(`${budgets} budget${budgets === 1 ? "" : "s"} exceeded (\u2212${d})`);
30180
+ }
30181
+ if (!reasons.length)
30182
+ reasons.push(
30183
+ ctx.calls.length ? "no premium waste, fast-mode or budget issues" : "no usage recorded yet"
30184
+ );
30185
+ return { id: "cost-efficiency", value: clamp(v), reasons };
30186
+ }
30187
+ function scoreCacheEfficiency(ctx, insights) {
30188
+ const reasons = [];
30189
+ const t = totals(ctx.calls);
30190
+ if (t.calls === 0)
30191
+ return { id: "cache-efficiency", value: 0, reasons: ["no usage recorded yet"] };
30192
+ let v = t.cacheHitRate * 100;
30193
+ reasons.push(`cache hit rate ${pct(t.cacheHitRate)} over ${t.calls} calls`);
30194
+ const cold = ev2(findInsight(insights, "cache-cold"), "restarts");
30195
+ if (cold) {
30196
+ const d = Math.min(25, cold * 5);
30197
+ v -= d;
30198
+ reasons.push(`${cold} cold restart${cold === 1 ? "" : "s"} (\u2212${d})`);
30199
+ }
30200
+ const thrash = ev2(findInsight(insights, "cache-thrash"), "runs");
30201
+ if (thrash) {
30202
+ const d = Math.min(30, thrash * 10);
30203
+ v -= d;
30204
+ reasons.push(`${thrash} cache-rewrite run${thrash === 1 ? "" : "s"} (\u2212${d})`);
30205
+ }
30206
+ return { id: "cache-efficiency", value: clamp(v), reasons };
30207
+ }
30208
+ function scoreDelegation(ctx) {
30209
+ const reasons = [];
30210
+ const a = byAgent(ctx.calls);
30211
+ if (ctx.calls.length === 0)
30212
+ return { id: "delegation", value: 0, reasons: ["no usage recorded yet"] };
30213
+ if (a.subagents.length === 0)
30214
+ return {
30215
+ id: "delegation",
30216
+ value: 60,
30217
+ reasons: ["no subagent calls: the main thread carries every tool result itself"]
30218
+ };
30219
+ let v;
30220
+ if (a.delegationShare <= DELEGATION_SHARE) {
30221
+ v = 100;
30222
+ reasons.push(
30223
+ `subagents are ${pct(a.delegationShare)} of cost across ${a.subagents.length} agent${a.subagents.length === 1 ? "" : "s"}`
30224
+ );
30225
+ } else {
30226
+ v = Math.max(20, 100 - 150 * (a.delegationShare - DELEGATION_SHARE));
30227
+ reasons.push(
30228
+ `subagents are ${pct(a.delegationShare)} of cost, above ${pct(DELEGATION_SHARE)} (\u2212${100 - Math.round(v)})`
30229
+ );
30230
+ }
30231
+ const sub = totals(ctx.calls.filter((c) => c.sidechain));
30232
+ if (sub.calls && sub.cacheHitRate < 0.5) {
30233
+ v -= 15;
30234
+ reasons.push(`subagent cache hit rate ${pct(sub.cacheHitRate)} (\u221215)`);
30235
+ }
30236
+ return { id: "delegation", value: clamp(v), reasons };
30237
+ }
30238
+ function detectInsights(input2) {
30239
+ const calls = input2.calls.filter(isRealCall);
30240
+ const compactionsBySession = /* @__PURE__ */ new Map();
30241
+ for (const e of input2.contextEvents) {
30242
+ const list = compactionsBySession.get(e.session_id);
30243
+ if (list) list.push(e.ts);
30244
+ else compactionsBySession.set(e.session_id, [e.ts]);
30245
+ }
30246
+ for (const list of compactionsBySession.values()) list.sort((a, b) => a - b);
30247
+ const callTs = /* @__PURE__ */ new Map();
30248
+ for (const [sid, rows] of groupBySession(calls))
30249
+ callTs.set(
30250
+ sid,
30251
+ rows.map((c) => c.ts).sort((a, b) => a - b)
30252
+ );
30253
+ const priceCache = /* @__PURE__ */ new Map();
30254
+ const overrides = input2.overrides;
30255
+ const ctx = {
30256
+ calls,
30257
+ threads: threads(calls),
30258
+ toolUses: input2.toolUses,
30259
+ contextEvents: input2.contextEvents,
30260
+ stats: input2.stats,
30261
+ events: input2.events,
30262
+ snapshots: input2.snapshots,
30263
+ now: input2.now ?? Date.now(),
30264
+ budgets: input2.budgets ?? {},
30265
+ overrides,
30266
+ priceOf: (model) => {
30267
+ if (!priceCache.has(model)) priceCache.set(model, resolvePrice(model, overrides));
30268
+ return priceCache.get(model);
30269
+ },
30270
+ compactionsBySession,
30271
+ laterCalls: (sid, ts) => (callTs.get(sid) ?? []).filter((t) => t > ts).length
30272
+ };
30273
+ const insights = [];
30274
+ const push = (i) => {
30275
+ if (i) insights.push(i);
30276
+ };
30277
+ push(detectCacheThrash(ctx));
30278
+ push(detectCacheCold(ctx));
30279
+ push(detectContextBloat(ctx));
30280
+ push(detectCompactionChurn(ctx));
30281
+ push(detectOpusForTrivial(ctx));
30282
+ push(detectFastModeShare(ctx));
30283
+ push(detectThinkingShare(ctx));
30284
+ push(detectSubagentCost(ctx));
30285
+ push(detectExpensiveTurn(ctx));
30286
+ push(detectErrorLoop(ctx));
30287
+ for (const b of detectBudget(ctx)) push(b);
30288
+ push(detectUnpricedModel(ctx));
30289
+ push(detectContextNearLimit(ctx));
30290
+ insights.sort(
30291
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || (b.estimatedSavingsUsd ?? 0) - (a.estimatedSavingsUsd ?? 0) || a.id.localeCompare(b.id)
30292
+ );
30293
+ const scores = [
30294
+ scoreContextHealth(ctx, insights),
30295
+ scoreCostEfficiency(ctx, insights),
30296
+ scoreCacheEfficiency(ctx, insights),
30297
+ scoreDelegation(ctx)
30298
+ ];
30299
+ return { insights, scores };
30300
+ }
30301
+ var INSIGHT_EVENT_CATEGORIES = ["iteration_loop", "error", "tool_call"];
30302
+ function loadInsightInput(db, ctx, opts = {}) {
30303
+ const filter = { sessionId: opts.sessionId, since: opts.since };
30304
+ const usage = ctx.config.usage;
30305
+ return {
30306
+ calls: db.apiCalls(filter),
30307
+ toolUses: db.toolUses(filter),
30308
+ contextEvents: db.contextEvents(filter),
30309
+ snapshots: db.statuslineSnapshots(filter),
30310
+ stats: opts.sessionId ? db.statsBySession(opts.sessionId) : db.statsAll(),
30311
+ events: opts.sessionId ? db.eventsBySession(opts.sessionId, { categories: INSIGHT_EVENT_CATEGORIES }) : db.eventsRecent(500, INSIGHT_EVENT_CATEGORIES).filter((e) => !opts.since || e.ts >= opts.since),
30312
+ now: opts.now ?? Date.now(),
30313
+ budgets: usage.budgets,
30314
+ overrides: usage.pricing
30315
+ };
30316
+ }
30317
+
30318
+ // src/usage/brief.ts
30319
+ var DAY_MS = 864e5;
30320
+ function todaysCalls(db, now) {
30321
+ const today = dayKey(now);
30322
+ return db.apiCalls({ since: now - 1.5 * DAY_MS }).filter((c) => dayKey(c.ts) === today);
30323
+ }
30324
+ function usageBrief(db, ctx, opts = {}) {
30325
+ const now = opts.now ?? Date.now();
30326
+ const today = totals(todaysCalls(db, now));
30327
+ const session = totals(opts.sessionId ? db.apiCalls({ sessionId: opts.sessionId }) : []);
30328
+ const cacheHitRate = today.calls ? today.cacheHitRate : session.cacheHitRate;
30329
+ const out = {
30330
+ line: "",
30331
+ sessionUsd: session.costUsd,
30332
+ todayUsd: today.costUsd,
30333
+ cacheHitRate
30334
+ };
30335
+ if (today.calls === 0 && session.calls === 0) return out;
30336
+ const parts = [
30337
+ `today ${formatUsd(today.costUsd)} (${today.calls} call${today.calls === 1 ? "" : "s"}, ${Math.round(cacheHitRate * 100)}% cache hits)`
30338
+ ];
30339
+ if (session.calls > 0) parts.push(`session ${formatUsd(session.costUsd)}`);
30340
+ try {
30341
+ const scope = session.calls > 0 ? { sessionId: opts.sessionId } : { since: now - 1.5 * DAY_MS };
30342
+ const { insights } = detectInsights(loadInsightInput(db, ctx, { ...scope, now }));
30343
+ const top = insights[0];
30344
+ if (top) {
30345
+ out.topInsight = top;
30346
+ const saving = top.estimatedSavingsUsd && top.estimatedSavingsUsd >= 5e-3 ? ` (~${formatUsd(top.estimatedSavingsUsd)})` : "";
30347
+ parts.push(`top insight: ${top.title.charAt(0).toLowerCase()}${top.title.slice(1)}${saving}`);
30348
+ }
30349
+ } catch {
30350
+ }
30351
+ out.line = `usage: ${parts.join(" \xB7 ")}`;
30352
+ return out;
30353
+ }
30354
+ function budgetWarnings(db, ctx, sessionId, now = Date.now()) {
30355
+ const { sessionUsd, dailyUsd } = ctx.config.usage.budgets;
30356
+ if (!sessionUsd && !dailyUsd) return [];
30357
+ const out = [];
30358
+ if (sessionUsd) {
30359
+ const key = `usage_alert:session:${sessionId}`;
30360
+ const cost = totals(db.apiCalls({ sessionId })).costUsd;
30361
+ if (cost >= sessionUsd && !db.metaGet(key)) {
30362
+ db.metaSet(key, String(now));
30363
+ out.push(
30364
+ `eminiar usage \u2014 this session has cost ${formatUsd(cost)}, over its ${formatUsd(sessionUsd)} budget (config.usage.budgets.sessionUsd). Keep raw output out of context, delegate bulk work to a cheaper model, or start a fresh session.`
30365
+ );
30366
+ }
30367
+ }
30368
+ if (dailyUsd) {
30369
+ const day = dayKey(now);
30370
+ const key = `usage_alert:daily:${day}`;
30371
+ const cost = totals(todaysCalls(db, now)).costUsd;
30372
+ if (cost >= dailyUsd && !db.metaGet(key)) {
30373
+ db.metaSet(key, String(now));
30374
+ out.push(
30375
+ `eminiar usage \u2014 today's sessions have cost ${formatUsd(cost)}, over the ${formatUsd(dailyUsd)} daily budget (config.usage.budgets.dailyUsd). em_usage {view:"insights"} shows where it went.`
30376
+ );
30377
+ }
30378
+ }
30379
+ return out;
30380
+ }
30381
+
30382
+ // src/usage/ingest.ts
30383
+ init_esm_shims();
30384
+ import { createHash as createHash4 } from "crypto";
30385
+ import { closeSync, existsSync as existsSync3, openSync, readdirSync, readSync, statSync as statSync2 } from "fs";
30386
+ import { homedir as homedir2 } from "os";
30387
+ import { basename, dirname as dirname2, join as join4, resolve as resolve3, sep } from "path";
30388
+
30389
+ // src/usage/transcript.ts
30390
+ init_esm_shims();
30391
+ var NON_TEXT_PART_CHARS = 6400;
30392
+ var DENIAL_RE = /^\s*PreToolUse:|hook error|\bdenied\b/i;
30393
+ function isObj(v) {
30394
+ return typeof v === "object" && v !== null && !Array.isArray(v);
30395
+ }
30396
+ function str(v) {
30397
+ return typeof v === "string" && v.length ? v : null;
30398
+ }
30399
+ function num3(v) {
30400
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
30401
+ }
30402
+ function numOrNull(v) {
30403
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
30404
+ }
30405
+ function tsOf(e) {
30406
+ const t = e.timestamp;
30407
+ if (typeof t === "string") {
30408
+ const ms = Date.parse(t);
30409
+ if (Number.isFinite(ms) && ms > 0) return ms;
30410
+ return void 0;
30411
+ }
30412
+ if (typeof t === "number" && Number.isFinite(t) && t > 0) return t < 1e12 ? t * 1e3 : t;
30413
+ return void 0;
30414
+ }
30415
+ function resultChars(content) {
30416
+ if (typeof content === "string") return content.length;
30417
+ if (Array.isArray(content)) {
30418
+ let n = 0;
30419
+ for (const part of content) {
30420
+ if (!isObj(part)) {
30421
+ n += safeJsonLength(part);
30422
+ continue;
30423
+ }
30424
+ const kind = str(part.type);
30425
+ if (kind === "text" || typeof part.text === "string") n += str(part.text)?.length ?? 0;
30426
+ else if (kind === "image" || kind === "document" || kind === "tool_reference")
30427
+ n += NON_TEXT_PART_CHARS;
30428
+ else n += safeJsonLength(part);
30429
+ }
30430
+ return n;
30431
+ }
30432
+ if (content === void 0 || content === null) return 0;
30433
+ return safeJsonLength(content);
30434
+ }
30435
+ function resultText(content) {
30436
+ if (typeof content === "string") return content;
30437
+ if (Array.isArray(content)) {
30438
+ const parts = [];
30439
+ for (const part of content)
30440
+ if (isObj(part) && typeof part.text === "string") parts.push(part.text);
30441
+ return parts.join("\n");
30442
+ }
30443
+ return "";
30444
+ }
30445
+ function safeJsonLength(v) {
30446
+ try {
30447
+ return JSON.stringify(v)?.length ?? 0;
30448
+ } catch {
30449
+ return 0;
30450
+ }
30451
+ }
30452
+ function parseTranscriptLines(lines, ctx) {
30453
+ const calls = /* @__PURE__ */ new Map();
30454
+ const toolUses = /* @__PURE__ */ new Map();
30455
+ const contextEvents = [];
30456
+ let lastTs;
30457
+ let prevTs;
30458
+ const fallbackTs = typeof ctx.fallbackTs === "number" && Number.isFinite(ctx.fallbackTs) && ctx.fallbackTs > 0 ? ctx.fallbackTs : Date.now();
30459
+ let denied = 0;
30460
+ for (const raw of lines) {
30461
+ const line2 = raw.trim();
30462
+ if (!line2) continue;
30463
+ let e;
30464
+ try {
30465
+ e = JSON.parse(line2);
30466
+ } catch {
30467
+ continue;
30468
+ }
30469
+ if (!isObj(e)) continue;
30470
+ const type = str(e.type);
30471
+ if (!type) continue;
30472
+ const sessionId = str(e.sessionId) ?? ctx.sessionId;
30473
+ const agentId = str(e.agentId);
30474
+ const sidechain = e.isSidechain === true ? 1 : 0;
30475
+ const real = tsOf(e);
30476
+ const ts = real ?? prevTs ?? fallbackTs;
30477
+ prevTs = ts;
30478
+ if (real !== void 0) lastTs = lastTs === void 0 ? real : Math.max(lastTs, real);
30479
+ if (type === "assistant") {
30480
+ const msg = isObj(e.message) ? e.message : void 0;
30481
+ if (!msg) continue;
30482
+ const messageId = str(msg.id);
30483
+ const requestId = str(e.requestId) ?? messageId;
30484
+ if (Array.isArray(msg.content)) {
30485
+ for (const block of msg.content) {
30486
+ if (!isObj(block) || block.type !== "tool_use") continue;
30487
+ const id = str(block.id);
30488
+ if (!id) continue;
30489
+ const prev2 = toolUses.get(id);
30490
+ toolUses.set(id, {
30491
+ tool_use_id: id,
30492
+ session_id: prev2?.session_id ?? sessionId,
30493
+ agent_id: prev2?.agent_id ?? agentId,
30494
+ ts: prev2?.ts ?? ts,
30495
+ tool: str(block.name) ?? prev2?.tool ?? "",
30496
+ input_chars: block.input === void 0 ? 0 : safeJsonLength(block.input),
30497
+ result_chars: prev2?.result_chars ?? null,
30498
+ is_error: prev2?.is_error ?? 0,
30499
+ request_id: requestId ?? prev2?.request_id ?? null
30500
+ });
30501
+ }
30502
+ }
30503
+ const usage = isObj(msg.usage) ? msg.usage : void 0;
30504
+ if (!usage || !messageId || !requestId) continue;
30505
+ const model = str(msg.model) ?? "unknown";
30506
+ const cc = isObj(usage.cache_creation) ? usage.cache_creation : void 0;
30507
+ const create5m = cc ? num3(cc.ephemeral_5m_input_tokens) : num3(usage.cache_creation_input_tokens);
30508
+ const create1h = cc ? num3(cc.ephemeral_1h_input_tokens) : 0;
30509
+ const details = isObj(usage.output_tokens_details) ? usage.output_tokens_details : void 0;
30510
+ const server = isObj(usage.server_tool_use) ? usage.server_tool_use : void 0;
30511
+ const key = `${requestId}\0${messageId}`;
30512
+ const prev = calls.get(key);
30513
+ calls.set(key, {
30514
+ request_id: requestId,
30515
+ message_id: messageId,
30516
+ session_id: prev?.session_id ?? sessionId,
30517
+ agent_id: agentId ?? prev?.agent_id ?? null,
30518
+ sidechain: prev?.sidechain || sidechain,
30519
+ ts: prev?.ts ?? ts,
30520
+ model,
30521
+ input_tokens: num3(usage.input_tokens),
30522
+ output_tokens: num3(usage.output_tokens),
30523
+ cache_create_5m: create5m,
30524
+ cache_create_1h: create1h,
30525
+ cache_read: num3(usage.cache_read_input_tokens),
30526
+ thinking_tokens: details ? num3(details.thinking_tokens) : 0,
30527
+ web_searches: server ? num3(server.web_search_requests) : 0,
30528
+ web_fetches: server ? num3(server.web_fetch_requests) : 0,
30529
+ service_tier: str(usage.service_tier),
30530
+ speed: str(usage.speed),
30531
+ stop_reason: str(msg.stop_reason) ?? prev?.stop_reason ?? null,
30532
+ prompt_id: str(e.promptId) ?? prev?.prompt_id ?? null,
30533
+ git_branch: str(e.gitBranch) ?? prev?.git_branch ?? null,
30534
+ cwd: str(e.cwd) ?? prev?.cwd ?? null,
30535
+ effort: str(e.effort) ?? prev?.effort ?? null,
30536
+ version: str(e.version) ?? prev?.version ?? null,
30537
+ entrypoint: str(e.entrypoint) ?? prev?.entrypoint ?? null
30538
+ });
30539
+ continue;
30540
+ }
30541
+ if (type === "user") {
30542
+ const msg = isObj(e.message) ? e.message : void 0;
30543
+ if (!msg || !Array.isArray(msg.content)) continue;
30544
+ for (const block of msg.content) {
30545
+ if (!isObj(block) || block.type !== "tool_result") continue;
30546
+ const id = str(block.tool_use_id);
30547
+ if (!id) continue;
30548
+ const prev = toolUses.get(id);
30549
+ let isError = prev?.is_error ?? 0;
30550
+ if (block.is_error === true) {
30551
+ if (DENIAL_RE.test(resultText(block.content))) denied += 1;
30552
+ else isError = 1;
30553
+ }
30554
+ toolUses.set(id, {
30555
+ tool_use_id: id,
30556
+ session_id: prev?.session_id ?? sessionId,
30557
+ agent_id: prev?.agent_id ?? agentId,
30558
+ ts: prev?.ts ?? ts,
30559
+ tool: prev?.tool ?? "",
30560
+ input_chars: prev?.input_chars ?? 0,
30561
+ result_chars: resultChars(block.content),
30562
+ is_error: isError,
30563
+ request_id: prev?.request_id ?? null
30564
+ });
30565
+ }
30566
+ continue;
30567
+ }
30568
+ if (type === "system") {
30569
+ const subtype = str(e.subtype);
30570
+ if (subtype === "compact_boundary") {
30571
+ const meta3 = isObj(e.compactMetadata) ? e.compactMetadata : void 0;
30572
+ contextEvents.push({
30573
+ session_id: sessionId,
30574
+ ts,
30575
+ kind: "compact",
30576
+ trigger: meta3 ? str(meta3.trigger) : null,
30577
+ pre_tokens: meta3 ? numOrNull(meta3.preTokens) : null,
30578
+ post_tokens: meta3 ? numOrNull(meta3.postTokens) : null
30579
+ });
30580
+ } else if (subtype === "local_command") {
30581
+ const run = isObj(e.commandRun) ? e.commandRun : void 0;
30582
+ if (run && str(run.command) === "clear") {
30583
+ contextEvents.push({
30584
+ session_id: sessionId,
30585
+ ts,
30586
+ kind: "clear",
30587
+ trigger: "manual",
30588
+ pre_tokens: null,
30589
+ post_tokens: null
30590
+ });
30591
+ }
30592
+ }
30593
+ }
30594
+ }
30595
+ const out = {
30596
+ calls: [...calls.values()],
30597
+ toolUses: [...toolUses.values()],
30598
+ contextEvents,
30599
+ denied
30600
+ };
30601
+ if (lastTs !== void 0) out.lastTs = lastTs;
30602
+ return out;
30603
+ }
30604
+
30605
+ // src/usage/ingest.ts
30606
+ var ZERO = { calls: 0, toolUses: 0, contextEvents: 0 };
30607
+ var HEAD_BYTES = 512;
30608
+ var SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 6e4;
30609
+ var SNAPSHOT_KEEP_PER_SESSION = 500;
30610
+ var SESSION_ID_RE = /^[A-Za-z0-9_-]{1,80}$/;
30611
+ function isValidSessionId(id) {
30612
+ return SESSION_ID_RE.test(id);
30613
+ }
30614
+ function claudeDir() {
30615
+ return process.env.EMINIAR_CLAUDE_DIR || join4(homedir2(), ".claude");
30616
+ }
30617
+ function insideDir(path2, dir) {
30618
+ const d = resolve3(dir);
30619
+ const p = resolve3(path2);
30620
+ return p === d || p.startsWith(d.endsWith(sep) ? d : d + sep);
30621
+ }
30622
+ function isTranscriptFile(path2) {
30623
+ if (!path2.endsWith(".jsonl")) return false;
30624
+ try {
30625
+ return statSync2(path2).isFile();
30626
+ } catch {
30627
+ return false;
30628
+ }
30629
+ }
30630
+ function transcriptSlug(root) {
30631
+ return root.replace(/[^A-Za-z0-9]/g, "-");
30632
+ }
30633
+ function transcriptDirFor(root) {
30634
+ return join4(claudeDir(), "projects", transcriptSlug(root));
30635
+ }
30636
+ function usageEnabled(ctx) {
30637
+ return ctx.config.usage.enabled !== false;
30638
+ }
30639
+ function priceCalls(calls, overrides) {
30640
+ return calls.map((c) => {
30641
+ const price2 = resolvePrice(c.model, overrides);
30642
+ if (!price2) return { ...c, cost_usd: null, priced: 0 };
30643
+ return { ...c, cost_usd: costOfCall(c, price2).total, priced: 1 };
30644
+ });
30645
+ }
30646
+ function readCompleteLines(path2, offset, size) {
30647
+ const length = size - offset;
30648
+ if (length <= 0) return { lines: [], nextOffset: offset };
30649
+ const buf = Buffer.alloc(length);
30650
+ const fd = openSync(path2, "r");
30651
+ let read = 0;
30652
+ try {
30653
+ while (read < length) {
30654
+ const n = readSync(fd, buf, read, length - read, offset + read);
30655
+ if (n <= 0) break;
30656
+ read += n;
30657
+ }
30658
+ } finally {
30659
+ closeSync(fd);
30660
+ }
30661
+ const end = buf.lastIndexOf(10, read - 1);
30662
+ if (end < 0) return { lines: [], nextOffset: offset };
30663
+ const text = buf.toString("utf8", 0, end + 1);
30664
+ return { lines: text.split("\n"), nextOffset: offset + end + 1 };
30665
+ }
30666
+ function headHash(path2, length) {
30667
+ if (length <= 0) return null;
30668
+ const buf = Buffer.alloc(length);
30669
+ let read = 0;
30670
+ const fd = openSync(path2, "r");
30671
+ try {
30672
+ while (read < length) {
30673
+ const n = readSync(fd, buf, read, length - read, read);
30674
+ if (n <= 0) break;
30675
+ read += n;
30676
+ }
30677
+ } finally {
30678
+ closeSync(fd);
30679
+ }
30680
+ return createHash4("sha1").update(buf.subarray(0, read)).digest("hex");
30681
+ }
30682
+ function byteAt(path2, pos) {
30683
+ if (pos < 0) return void 0;
30684
+ const buf = Buffer.alloc(1);
30685
+ const fd = openSync(path2, "r");
30686
+ try {
30687
+ return readSync(fd, buf, 0, 1, pos) === 1 ? buf[0] : void 0;
30688
+ } finally {
30689
+ closeSync(fd);
30690
+ }
30691
+ }
30692
+ function sessionIdFromName(path2) {
30693
+ const dir = dirname2(path2);
30694
+ if (basename(dir) === "subagents") return basename(dirname2(dir));
30695
+ const name = basename(path2, ".jsonl");
30696
+ return name.startsWith("agent-") ? void 0 : name;
30697
+ }
30698
+ function contextEventExists(db, e) {
30699
+ return db.contextEvents({ sessionId: e.session_id, since: e.ts, until: e.ts + 1 }).some((x) => x.kind === e.kind);
30700
+ }
30701
+ function resumeOffset(path2, prev, size, mtime) {
30702
+ if (!prev || prev.offset <= 0) return 0;
30703
+ if (size < prev.offset) return 0;
30704
+ if (prev.head_hash) {
30705
+ const want = Math.min(HEAD_BYTES, prev.size);
30706
+ try {
30707
+ if (headHash(path2, want) !== prev.head_hash) return 0;
30708
+ } catch {
30709
+ return 0;
30710
+ }
30711
+ }
30712
+ if (prev.mtime !== mtime) {
30713
+ try {
30714
+ if (byteAt(path2, prev.offset - 1) !== 10) return 0;
30715
+ } catch {
30716
+ return 0;
30717
+ }
30718
+ }
30719
+ return prev.offset;
30720
+ }
30721
+ function ingestTranscript(db, path2, opts = {}) {
30722
+ if (!path2.endsWith(".jsonl")) return ZERO;
30723
+ let size;
30724
+ let mtime;
30725
+ try {
30726
+ const st = statSync2(path2);
30727
+ if (!st.isFile()) return ZERO;
30728
+ size = st.size;
30729
+ mtime = Math.floor(st.mtimeMs);
30730
+ } catch {
30731
+ return ZERO;
30732
+ }
30733
+ const prev = db.transcriptFileGet(path2);
30734
+ const offset = resumeOffset(path2, prev, size, mtime);
30735
+ const sessionHint = opts.sessionId ?? prev?.session_id ?? sessionIdFromName(path2);
30736
+ if (offset === size) {
30737
+ if (!prev || prev.size !== size || prev.mtime !== mtime || sessionHint && !prev.session_id)
30738
+ db.transcriptFileSet({
30739
+ path: path2,
30740
+ session_id: sessionHint ?? null,
30741
+ offset,
30742
+ size,
30743
+ mtime,
30744
+ head_hash: prev?.size === size ? prev.head_hash ?? null : fingerprint(path2, size),
30745
+ ingested_at: Date.now()
30746
+ });
30747
+ return ZERO;
30748
+ }
30749
+ const { lines, nextOffset } = readCompleteLines(path2, offset, size);
30750
+ const parsed = parseTranscriptLines(lines, {
30751
+ sessionId: sessionHint ?? "unknown",
30752
+ path: path2,
30753
+ fallbackTs: mtime
30754
+ });
30755
+ const calls = priceCalls(parsed.calls, opts.overrides);
30756
+ const counts = { calls: 0, toolUses: 0, contextEvents: 0 };
30757
+ const sessionSeen = parsed.calls[0]?.session_id ?? parsed.toolUses[0]?.session_id;
30758
+ const head = fingerprint(path2, size);
30759
+ db.transaction(() => {
30760
+ counts.calls = db.apiCallsUpsert(calls);
30761
+ counts.toolUses = db.toolUsesUpsert(parsed.toolUses);
30762
+ for (const e of parsed.contextEvents) {
30763
+ if (contextEventExists(db, e)) continue;
30764
+ db.contextEventInsert(e);
30765
+ counts.contextEvents++;
30766
+ }
30767
+ db.transcriptFileSet({
30768
+ path: path2,
30769
+ session_id: sessionHint ?? sessionSeen ?? null,
30770
+ offset: nextOffset,
30771
+ size,
30772
+ mtime,
30773
+ head_hash: head,
30774
+ ingested_at: Date.now()
30775
+ });
30776
+ });
30777
+ return counts;
30778
+ }
30779
+ function fingerprint(path2, size) {
30780
+ try {
30781
+ return headHash(path2, Math.min(HEAD_BYTES, size));
30782
+ } catch {
30783
+ return null;
30784
+ }
30785
+ }
30786
+ function pruneSnapshots(db) {
30787
+ try {
30788
+ db.statuslineSnapshotsPrune({
30789
+ olderThan: Date.now() - SNAPSHOT_MAX_AGE_MS,
30790
+ keepPerSession: SNAPSHOT_KEEP_PER_SESSION
30791
+ });
30792
+ } catch {
30793
+ }
30794
+ }
30795
+ function add(a, b) {
30796
+ return {
30797
+ calls: a.calls + b.calls,
30798
+ toolUses: a.toolUses + b.toolUses,
30799
+ contextEvents: a.contextEvents + b.contextEvents
30800
+ };
30801
+ }
30802
+ function listJsonl(dir) {
30803
+ try {
30804
+ return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => join4(dir, f));
30805
+ } catch {
30806
+ return [];
30807
+ }
30808
+ }
30809
+ function firstEntrySessionId(path2) {
30810
+ try {
30811
+ const { lines } = readCompleteLines(path2, 0, Math.min(statSync2(path2).size, 64 * 1024));
30812
+ for (const line2 of lines) {
30813
+ if (!line2.trim()) continue;
30814
+ const e = JSON.parse(line2);
30815
+ return typeof e.sessionId === "string" ? e.sessionId : void 0;
30816
+ }
30817
+ } catch {
30818
+ }
30819
+ return void 0;
30820
+ }
30821
+ function sidechainFilesFor(db, dir, sessionId) {
30822
+ if (!isValidSessionId(sessionId)) return [];
30823
+ const inside = (p) => insideDir(p, dir) && p.endsWith(".jsonl");
30824
+ const out = listJsonl(join4(dir, sessionId, "subagents")).filter(inside);
30825
+ for (const path2 of listJsonl(dir)) {
30826
+ if (!inside(path2) || !basename(path2).startsWith("agent-")) continue;
30827
+ const known = db.transcriptFileGet(path2)?.session_id;
30828
+ let owner = known ?? void 0;
30829
+ if (!owner) {
30830
+ owner = firstEntrySessionId(path2);
30831
+ if (owner) {
30832
+ db.transcriptFileSet({
30833
+ path: path2,
30834
+ session_id: owner,
30835
+ offset: 0,
30836
+ size: 0,
30837
+ mtime: 0,
30838
+ ingested_at: Date.now()
30839
+ });
30840
+ }
30841
+ }
30842
+ if (owner === sessionId) out.push(path2);
30843
+ }
30844
+ return out;
30845
+ }
30846
+ function ingestSession(db, root, sessionId, transcriptPath, opts = {}) {
30847
+ if (!isValidSessionId(sessionId)) return ZERO;
30848
+ let main2;
30849
+ const trusted = transcriptPath ?? db.sessionGet(sessionId)?.transcript_path ?? void 0;
30850
+ if (trusted) {
30851
+ if (!isTranscriptFile(trusted)) return ZERO;
30852
+ main2 = resolve3(trusted);
30853
+ } else {
30854
+ const built = resolve3(transcriptDirFor(root), `${sessionId}.jsonl`);
30855
+ if (!insideDir(built, claudeDir())) return ZERO;
30856
+ main2 = built;
30857
+ }
30858
+ let total = ingestTranscript(db, main2, { sessionId, overrides: opts.overrides });
30859
+ for (const path2 of sidechainFilesFor(db, dirname2(main2), sessionId)) {
30860
+ total = add(total, ingestTranscript(db, path2, { sessionId, overrides: opts.overrides }));
30861
+ }
30862
+ pruneSnapshots(db);
30863
+ return total;
30864
+ }
30865
+
30866
+ // src/hooks/log.ts
30867
+ init_esm_shims();
30868
+ import { appendFileSync } from "fs";
30869
+ function hookLog(level, msg) {
30870
+ try {
30871
+ const p = ensureDirs();
30872
+ appendFileSync(p.hookLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${msg}
30873
+ `);
30874
+ } catch {
30875
+ }
30876
+ }
30877
+
28839
30878
  // src/hooks/capture.ts
28840
30879
  var INDEXED = /* @__PURE__ */ new Set([
28841
30880
  "decision",
@@ -28878,30 +30917,58 @@ function open3(input2) {
28878
30917
  });
28879
30918
  return { db, root: ctx.root, sessionId };
28880
30919
  }
30920
+ var usageFailureLogged = false;
30921
+ function ingestUsage(db, input2, sessionId) {
30922
+ try {
30923
+ const ctx = getContext(input2.cwd);
30924
+ if (!usageEnabled(ctx)) return;
30925
+ const overrides = ctx.config.usage.pricing;
30926
+ ingestSession(db, ctx.root, sessionId, input2.transcript_path, { overrides });
30927
+ if (input2.agent_transcript_path)
30928
+ ingestTranscript(db, input2.agent_transcript_path, { sessionId, overrides });
30929
+ } catch (err) {
30930
+ if (usageFailureLogged) return;
30931
+ usageFailureLogged = true;
30932
+ hookLog(
30933
+ "error",
30934
+ `usage ingest failed (session ${sessionId}): ${err.message}; run \`eminiar usage ingest\` to see the error`
30935
+ );
30936
+ }
30937
+ }
28881
30938
  var userPromptSubmit = async (input2) => {
28882
30939
  if (!input2.prompt?.trim()) return void 0;
28883
30940
  const { db, sessionId } = open3(input2);
28884
30941
  db.sessionUpsert({ id: sessionId, last_prompt: clip(input2.prompt, 600) });
28885
30942
  persist(db, sessionId, withPriority(sessionId, classifyPrompt(input2.prompt)));
28886
- const { recall } = getContext(input2.cwd).config;
28887
- if (!recall.onPrompt) return void 0;
30943
+ const ctx = getContext(input2.cwd);
30944
+ const { recall } = ctx.config;
30945
+ const parts = [];
30946
+ if (recall.onPrompt) {
30947
+ try {
30948
+ const r = await recallForText(db, input2.prompt, {
30949
+ maxMemories: recall.maxMemories,
30950
+ maxDocs: recall.maxDocs,
30951
+ sessionId,
30952
+ repeatAfter: recall.repeatAfter,
30953
+ trace: true,
30954
+ path: "prompt"
30955
+ });
30956
+ recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
30957
+ if (r.text) parts.push(r.text);
30958
+ } catch {
30959
+ }
30960
+ }
28888
30961
  try {
28889
- const r = await recallForText(db, input2.prompt, {
28890
- maxMemories: recall.maxMemories,
28891
- maxDocs: recall.maxDocs,
28892
- sessionId,
28893
- repeatAfter: recall.repeatAfter,
28894
- trace: true,
28895
- path: "prompt"
28896
- });
28897
- recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
28898
- if (!r.text) return void 0;
28899
- return {
28900
- hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: r.text }
28901
- };
30962
+ if (usageEnabled(ctx)) parts.push(...budgetWarnings(db, ctx, sessionId));
28902
30963
  } catch {
28903
- return void 0;
28904
30964
  }
30965
+ if (!parts.length) return void 0;
30966
+ return {
30967
+ hookSpecificOutput: {
30968
+ hookEventName: "UserPromptSubmit",
30969
+ additionalContext: parts.join("\n\n")
30970
+ }
30971
+ };
28905
30972
  };
28906
30973
  function detectLoop(db, sessionId, key, summary) {
28907
30974
  const recent = db.eventsRecent(30, ["tool_call"]).filter((e) => e.session_id === sessionId);
@@ -28967,23 +31034,26 @@ var postToolUseFailure = async (input2) => {
28967
31034
  var subagentStop = (input2) => {
28968
31035
  const { db, sessionId } = open3(input2);
28969
31036
  const msg = input2.last_assistant_message ?? "";
28970
- if (!msg.trim()) return void 0;
28971
- persist(
28972
- db,
28973
- sessionId,
28974
- withPriority(sessionId, [
28975
- ev("agent_findings", {
28976
- agent: input2.agent_type ?? input2.agent_id,
28977
- text: clip(msg.replace(/\s+/g, " "), 1e3)
28978
- })
28979
- ])
28980
- );
31037
+ if (msg.trim()) {
31038
+ persist(
31039
+ db,
31040
+ sessionId,
31041
+ withPriority(sessionId, [
31042
+ ev("agent_findings", {
31043
+ agent: input2.agent_type ?? input2.agent_id,
31044
+ text: clip(msg.replace(/\s+/g, " "), 1e3)
31045
+ })
31046
+ ])
31047
+ );
31048
+ }
31049
+ ingestUsage(db, input2, sessionId);
28981
31050
  return void 0;
28982
31051
  };
28983
31052
  var stop = (input2) => {
28984
31053
  const { db, sessionId } = open3(input2);
28985
31054
  const msg = input2.last_assistant_message ?? "";
28986
31055
  persist(db, sessionId, withPriority(sessionId, classifyAssistant(msg)));
31056
+ ingestUsage(db, input2, sessionId);
28987
31057
  return void 0;
28988
31058
  };
28989
31059
  var sessionEnd = (input2) => {
@@ -28992,6 +31062,7 @@ var sessionEnd = (input2) => {
28992
31062
  db.eventInsert({
28993
31063
  ...withPriority(sessionId, [ev("session_start", { ended: input2.reason ?? "unknown" })])[0]
28994
31064
  });
31065
+ ingestUsage(db, input2, sessionId);
28995
31066
  globalDb();
28996
31067
  return void 0;
28997
31068
  };
@@ -29120,20 +31191,21 @@ var preCompact = (input2) => {
29120
31191
 
29121
31192
  // src/hooks/routing.ts
29122
31193
  init_esm_shims();
29123
- import { realpathSync as realpathSync4, statSync as statSync4 } from "fs";
29124
- import { homedir as homedir4 } from "os";
29125
- import { extname as extname2, join as join11, relative as relative4, resolve as resolve3 } from "path";
31194
+ import { realpathSync as realpathSync4, statSync as statSync5 } from "fs";
31195
+ import { homedir as homedir5 } from "os";
31196
+ import { extname as extname2, join as join12, relative as relative4, resolve as resolve4 } from "path";
29126
31197
 
29127
31198
  // src/docs/manifest.ts
29128
31199
  init_esm_shims();
29129
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
29130
- import { dirname as dirname2, join as join4 } from "path";
31200
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
31201
+ import { dirname as dirname3, join as join5 } from "path";
31202
+ var DOCS_PREFIX = "docs:";
29131
31203
  var NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
29132
31204
  function isValidName(name) {
29133
31205
  return NAME_RE.test(name);
29134
31206
  }
29135
31207
  function manifestPathAt(root, rel = ".eminiar/docs.json") {
29136
- return join4(root, rel);
31208
+ return join5(root, rel);
29137
31209
  }
29138
31210
  function manifestPath(ctx) {
29139
31211
  return manifestPathAt(ctx.root, ctx.config.docs.manifest);
@@ -29161,7 +31233,7 @@ function normalize(raw) {
29161
31233
  return { version: 1, sources: out };
29162
31234
  }
29163
31235
  function readManifestAt(file3) {
29164
- if (!existsSync3(file3)) return empty();
31236
+ if (!existsSync4(file3)) return empty();
29165
31237
  try {
29166
31238
  return normalize(JSON.parse(readFileSync3(file3, "utf8")));
29167
31239
  } catch {
@@ -29174,9 +31246,9 @@ function readManifest(ctx) {
29174
31246
 
29175
31247
  // src/permissions/settings.ts
29176
31248
  init_esm_shims();
29177
- import { existsSync as existsSync4, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
29178
- import { homedir as homedir2 } from "os";
29179
- import { join as join5 } from "path";
31249
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
31250
+ import { homedir as homedir3 } from "os";
31251
+ import { join as join6 } from "path";
29180
31252
 
29181
31253
  // src/permissions/rules.ts
29182
31254
  init_esm_shims();
@@ -29213,7 +31285,7 @@ init_esm_shims();
29213
31285
 
29214
31286
  // src/permissions/settings.ts
29215
31287
  function readRules(file3) {
29216
- if (!existsSync4(file3)) return {};
31288
+ if (!existsSync5(file3)) return {};
29217
31289
  try {
29218
31290
  const s = JSON.parse(readFileSync4(file3, "utf8"));
29219
31291
  return s.permissions ?? {};
@@ -29223,7 +31295,7 @@ function readRules(file3) {
29223
31295
  }
29224
31296
  function stamp(file3) {
29225
31297
  try {
29226
- return statSync2(file3).mtimeMs;
31298
+ return statSync3(file3).mtimeMs;
29227
31299
  } catch {
29228
31300
  return -1;
29229
31301
  }
@@ -29231,9 +31303,9 @@ function stamp(file3) {
29231
31303
  var cache2 = /* @__PURE__ */ new Map();
29232
31304
  function loadPermissionRules(root, extraDeny = []) {
29233
31305
  const files = [
29234
- join5(homedir2(), ".claude", "settings.json"),
29235
- join5(root, ".claude", "settings.json"),
29236
- join5(root, ".claude", "settings.local.json")
31306
+ join6(homedir3(), ".claude", "settings.json"),
31307
+ join6(root, ".claude", "settings.json"),
31308
+ join6(root, ".claude", "settings.local.json")
29237
31309
  ];
29238
31310
  const key = `${root}\0${extraDeny.join("\0")}`;
29239
31311
  const stamps = files.map(stamp);
@@ -29271,8 +31343,8 @@ function checkReadPath(rules, abs) {
29271
31343
 
29272
31344
  // src/session/brief.ts
29273
31345
  init_esm_shims();
29274
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
29275
- import { join as join7 } from "path";
31346
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
31347
+ import { join as join8 } from "path";
29276
31348
 
29277
31349
  // src/memory/frontmatter.ts
29278
31350
  init_esm_shims();
@@ -29306,15 +31378,15 @@ function parseMemoryFile(text) {
29306
31378
  init_esm_shims();
29307
31379
  var import_fast_glob2 = __toESM(require_out4(), 1);
29308
31380
  import {
29309
- existsSync as existsSync5,
31381
+ existsSync as existsSync6,
29310
31382
  mkdirSync as mkdirSync3,
29311
31383
  readFileSync as readFileSync5,
29312
31384
  renameSync,
29313
- statSync as statSync3,
31385
+ statSync as statSync4,
29314
31386
  unlinkSync,
29315
31387
  writeFileSync as writeFileSync2
29316
31388
  } from "fs";
29317
- import { dirname as dirname3, join as join6, relative as relative3, sep } from "path";
31389
+ import { dirname as dirname4, join as join7, relative as relative3, sep as sep2 } from "path";
29318
31390
 
29319
31391
  // src/storage/types.ts
29320
31392
  init_esm_shims();
@@ -29326,7 +31398,7 @@ function memoriesRel(ctx) {
29326
31398
  return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
29327
31399
  }
29328
31400
  function memoriesDir(ctx) {
29329
- return join6(ctx.root, memoriesRel(ctx));
31401
+ return join7(ctx.root, memoriesRel(ctx));
29330
31402
  }
29331
31403
 
29332
31404
  // src/session/brief.ts
@@ -29369,8 +31441,8 @@ function historyLine(db) {
29369
31441
  return `${parts.join("; ")}. em_history {action:"file", path} before editing, {action:"risk"} before committing.`;
29370
31442
  }
29371
31443
  function overviewParagraph(ctx) {
29372
- const file3 = join7(memoriesDir(ctx), "onboarding", "overview.md");
29373
- if (!existsSync6(file3)) return void 0;
31444
+ const file3 = join8(memoriesDir(ctx), "onboarding", "overview.md");
31445
+ if (!existsSync7(file3)) return void 0;
29374
31446
  try {
29375
31447
  const body = parseMemoryFile(readFileSync6(file3, "utf8")).body.trim();
29376
31448
  const para = body.split(/\n\s*\n/).map(
@@ -29441,10 +31513,10 @@ function recordRouting(db, sessionId, e) {
29441
31513
 
29442
31514
  // src/session/server-alive.ts
29443
31515
  init_esm_shims();
29444
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
29445
- import { join as join8 } from "path";
31516
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
31517
+ import { join as join9 } from "path";
29446
31518
  function markersDir(ctx) {
29447
- return join8(projectDir(ctx.id), "servers");
31519
+ return join9(projectDir(ctx.id), "servers");
29448
31520
  }
29449
31521
  function processAlive(pid) {
29450
31522
  try {
@@ -29456,13 +31528,13 @@ function processAlive(pid) {
29456
31528
  }
29457
31529
  function liveServers(ctx) {
29458
31530
  const dir = markersDir(ctx);
29459
- if (!existsSync7(dir)) return [];
31531
+ if (!existsSync8(dir)) return [];
29460
31532
  const live2 = [];
29461
- for (const name of readdirSync(dir)) {
31533
+ for (const name of readdirSync2(dir)) {
29462
31534
  const pid = Number(name);
29463
31535
  if (!Number.isInteger(pid) || pid <= 0) continue;
29464
31536
  if (processAlive(pid)) live2.push(pid);
29465
- else rmSync(join8(dir, name), { force: true });
31537
+ else rmSync(join9(dir, name), { force: true });
29466
31538
  }
29467
31539
  return live2;
29468
31540
  }
@@ -29473,29 +31545,29 @@ function serverAlive(ctx) {
29473
31545
  // src/hooks/coexist.ts
29474
31546
  init_esm_shims();
29475
31547
  import { readFileSync as readFileSync8 } from "fs";
29476
- import { join as join10 } from "path";
31548
+ import { join as join11 } from "path";
29477
31549
 
29478
31550
  // src/install/claude-config.ts
29479
31551
  init_esm_shims();
29480
31552
  import {
29481
31553
  copyFileSync,
29482
- existsSync as existsSync8,
31554
+ existsSync as existsSync9,
29483
31555
  mkdirSync as mkdirSync5,
29484
- readdirSync as readdirSync2,
31556
+ readdirSync as readdirSync3,
29485
31557
  readFileSync as readFileSync7,
29486
31558
  writeFileSync as writeFileSync4
29487
31559
  } from "fs";
29488
- import { homedir as homedir3 } from "os";
29489
- import { dirname as dirname4, join as join9 } from "path";
29490
- var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join9(homedir3(), ".claude");
29491
- var pluginsDir = () => join9(claudeDir(), "plugins");
31560
+ import { homedir as homedir4 } from "os";
31561
+ import { dirname as dirname5, join as join10 } from "path";
31562
+ var claudeDir2 = () => process.env.CLAUDE_CONFIG_DIR || join10(homedir4(), ".claude");
31563
+ var pluginsDir = () => join10(claudeDir2(), "plugins");
29492
31564
 
29493
31565
  // src/hooks/coexist.ts
29494
31566
  var cached2;
29495
31567
  function contextModeInstalled() {
29496
31568
  if (cached2 !== void 0) return cached2;
29497
31569
  try {
29498
- const reg = JSON.parse(readFileSync8(join10(pluginsDir(), "installed_plugins.json"), "utf8"));
31570
+ const reg = JSON.parse(readFileSync8(join11(pluginsDir(), "installed_plugins.json"), "utf8"));
29499
31571
  cached2 = Object.keys(reg.plugins ?? {}).some((id) => id.startsWith("context-mode@"));
29500
31572
  } catch {
29501
31573
  cached2 = false;
@@ -29507,18 +31579,6 @@ function effectiveRoutingMode(config2) {
29507
31579
  return contextModeInstalled() ? "annotate" : "enforce";
29508
31580
  }
29509
31581
 
29510
- // src/hooks/log.ts
29511
- init_esm_shims();
29512
- import { appendFileSync } from "fs";
29513
- function hookLog(level, msg) {
29514
- try {
29515
- const p = ensureDirs();
29516
- appendFileSync(p.hookLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${msg}
29517
- `);
29518
- } catch {
29519
- }
29520
- }
29521
-
29522
31582
  // src/hooks/routing.ts
29523
31583
  var SYMBOL_KINDS2 = [
29524
31584
  "function",
@@ -29591,7 +31651,7 @@ function historyCall(cmd) {
29591
31651
  }
29592
31652
  function docsSources(root) {
29593
31653
  try {
29594
- return readManifestAt(join11(root, ".eminiar", "docs.json")).sources;
31654
+ return readManifestAt(join12(root, ".eminiar", "docs.json")).sources;
29595
31655
  } catch {
29596
31656
  return [];
29597
31657
  }
@@ -29629,10 +31689,11 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
29629
31689
  ".sql",
29630
31690
  ".har"
29631
31691
  ]);
31692
+ var VISUAL_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".pdf"]);
29632
31693
  function withoutHeredocs(cmd) {
29633
31694
  return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
29634
31695
  }
29635
- function splitUnquoted(s, sep2) {
31696
+ function splitUnquoted(s, sep3) {
29636
31697
  const out = [];
29637
31698
  let cur = "";
29638
31699
  let quote2;
@@ -29648,7 +31709,7 @@ function splitUnquoted(s, sep2) {
29648
31709
  cur += ch;
29649
31710
  continue;
29650
31711
  }
29651
- const n = sep2(s, i);
31712
+ const n = sep3(s, i);
29652
31713
  if (n) {
29653
31714
  out.push(cur);
29654
31715
  cur = "";
@@ -29738,8 +31799,8 @@ function fileOperands(stage, cwd) {
29738
31799
  for (let f of files) {
29739
31800
  if (/[*?[\]{}$`\\]/.test(f)) return void 0;
29740
31801
  f = f.replace(/^['"]|['"]$/g, "");
29741
- if (f.startsWith("~/")) f = join11(homedir4(), f.slice(2));
29742
- out.push(resolve3(cwd, f));
31802
+ if (f.startsWith("~/")) f = join12(homedir5(), f.slice(2));
31803
+ out.push(resolve4(cwd, f));
29743
31804
  }
29744
31805
  return out;
29745
31806
  }
@@ -29749,7 +31810,7 @@ function stageBytes(stage, cwd) {
29749
31810
  let total = 0;
29750
31811
  for (const f of files) {
29751
31812
  try {
29752
- const st = statSync4(f);
31813
+ const st = statSync5(f);
29753
31814
  if (st.isDirectory()) return void 0;
29754
31815
  if (BIG_TEXT_EXT.has(extname2(f).toLowerCase()) && st.size > BIG_TEXT_LIMIT)
29755
31816
  return READ_LIMIT + st.size;
@@ -29830,10 +31891,11 @@ function suggest(toolName, input2, root, extra = {}) {
29830
31891
  case "Read": {
29831
31892
  const fp = String(input2.file_path ?? "");
29832
31893
  if (!fp) return void 0;
31894
+ const ext = extname2(fp).toLowerCase();
31895
+ if (VISUAL_EXT.has(ext)) return void 0;
29833
31896
  try {
29834
- const abs = resolve3(root, fp);
29835
- const st = statSync4(abs);
29836
- const ext = extname2(fp).toLowerCase();
31897
+ const abs = resolve4(root, fp);
31898
+ const st = statSync5(abs);
29837
31899
  if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
29838
31900
  if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
29839
31901
  return {
@@ -29896,7 +31958,7 @@ function suggest(toolName, input2, root, extra = {}) {
29896
31958
  }
29897
31959
  function projectRel(ctx, fp) {
29898
31960
  if (!fp) return void 0;
29899
- let abs = resolve3(ctx.root, fp);
31961
+ let abs = resolve4(ctx.root, fp);
29900
31962
  try {
29901
31963
  abs = realpathSync4(abs);
29902
31964
  } catch {
@@ -30143,15 +32205,15 @@ function annotate(s, note = "") {
30143
32205
 
30144
32206
  // src/hooks/session-start.ts
30145
32207
  init_esm_shims();
30146
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
30147
- import { join as join13 } from "path";
32208
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
32209
+ import { join as join14 } from "path";
30148
32210
 
30149
32211
  // src/session/current.ts
30150
32212
  init_esm_shims();
30151
- import { readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
30152
- import { join as join12 } from "path";
32213
+ import { readFileSync as readFileSync9, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
32214
+ import { join as join13 } from "path";
30153
32215
  function file2(ctx) {
30154
- return join12(projectDir(ctx.id), "current-session");
32216
+ return join13(projectDir(ctx.id), "current-session");
30155
32217
  }
30156
32218
  function setCurrentSession(ctx, sessionId) {
30157
32219
  writeFileSync5(file2(ctx), sessionId);
@@ -30178,7 +32240,7 @@ function routingGuidance(mode) {
30178
32240
  '- em_docs: the documentation knowledge base (local docs plus the libraries, services and APIs the project uses); {action:"discover"} / {action:"add", pick:"recommended"} build it, {action:"sync"} refreshes it.',
30179
32241
  '- em_search {queries, sort:"timeline"}: everything indexed plus session memory.',
30180
32242
  "- em_remember {content, kind, name?, status?}: memories are markdown under .eminiar/memories (survive compaction, shareable via git); em_memory {action: list|read|edit|rename|delete|status} manages them.",
30181
- '- em_architecture {aspects?}: the project map (folders, entry points, routes, hotspots, decisions) in one call; em_symbols {name_path | mode:"overview", file} for a symbol or a file outline; em_graph_impact {node} before a refactor; em_graph_changes for what the current diff reaches; em_graph_query {pattern | preset} to walk the graph; em_index_status for coverage. em_stats shows savings.',
32243
+ '- em_architecture {aspects?}: the project map (folders, entry points, routes, hotspots, decisions) in one call; em_symbols {name_path | mode:"overview", file} for a symbol or a file outline; em_graph_impact {node} before a refactor; em_graph_changes for what the current diff reaches; em_graph_query {pattern | preset} to walk the graph; em_index_status for coverage. em_stats shows savings; em_usage {view} shows model calls, cost, cache and insights.',
30182
32244
  "- em_history {action: overview | file {path} | risk | commit {sha} | branch {name} | symbol {name_path} | search {query}}: the git history classified (feat/fix/refactor\u2026), branches vs base, and each fix traced back to the commit that caused it \u2014 ask it before editing a file or committing instead of running git log/blame/show (those are routed to it); commit messages are searchable via em_search (source git).",
30183
32245
  '- A Grep for an identifier the graph knows is routed to em_symbols {name_path, mode:"references"} / em_search {queries, source:"code"}: symbol-level answers instead of raw matches.',
30184
32246
  "Plain Bash/Read/Grep remain right for short fixed output and files you are about to edit."
@@ -30212,6 +32274,7 @@ export EMINIAR_PROJECT_ROOT=${JSON.stringify(ctx.root)}
30212
32274
  } catch {
30213
32275
  }
30214
32276
  }
32277
+ if (input2.source === "compact" || input2.source === "resume") ingestUsage(db, input2, sessionId);
30215
32278
  const parts = [];
30216
32279
  if (input2.source === "compact" || input2.source === "resume") {
30217
32280
  const snap = db.snapshotLatestUnconsumed(sessionId);
@@ -30235,20 +32298,29 @@ ${guide}`);
30235
32298
  } catch {
30236
32299
  }
30237
32300
  try {
30238
- const onboarding = join13(memoriesDir(ctx), "onboarding");
30239
- const onboarded = existsSync9(onboarding) && readdirSync3(onboarding).some((f) => f.endsWith(".md"));
32301
+ if (ctx.config.usage.enabled) {
32302
+ const { line: line2 } = usageBrief(db, ctx, { sessionId });
32303
+ if (line2) parts.push(`eminiar usage \u2014 ${line2}`);
32304
+ }
32305
+ } catch {
32306
+ }
32307
+ try {
32308
+ const onboarding = join14(memoriesDir(ctx), "onboarding");
32309
+ const onboarded = existsSync10(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
30240
32310
  if (!onboarded)
30241
32311
  parts.push(
30242
32312
  "No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
30243
32313
  );
30244
- else if (!readManifest(ctx).sources.length)
32314
+ else if (!readManifest(ctx).sources.length) {
32315
+ const orphaned = db.documentSourceStats(DOCS_PREFIX).length;
30245
32316
  parts.push(
30246
- `No documentation sources registered yet: em_docs {action:"discover"} maps this project's libraries, services and APIs to their documentation, and {action:"add", pick:"recommended"} indexes it for em_docs lookup.`
32317
+ orphaned ? `No documentation sources registered (${orphaned} orphaned docs:* source(s) still in the index; em_docs {action:"list"} shows them, {action:"remove"} drops them, {action:"add", pick:"recommended"} re-registers).` : `No documentation sources registered yet: em_docs {action:"discover"} maps this project's libraries, services and APIs to their documentation, and {action:"add", pick:"recommended"} indexes it for em_docs lookup.`
30247
32318
  );
32319
+ }
30248
32320
  } catch {
30249
32321
  }
30250
32322
  try {
30251
- if (existsSync9(join13(ctx.root, ".git")) && !db.metaGet("history_files"))
32323
+ if (existsSync10(join14(ctx.root, ".git")) && !db.metaGet("history_files"))
30252
32324
  parts.push(
30253
32325
  'Git history is not classified yet: em_history {action:"overview"} builds it (the server also does this in the background shortly after start); bug-prone files are flagged before you read or edit them once it exists.'
30254
32326
  );