@metamask-previews/tooling-insight 1.0.1-preview-898fae5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/daily-anonymizer.cjs +8 -0
  5. package/dist/daily-anonymizer.cjs.map +1 -0
  6. package/dist/daily-anonymizer.d.cts +3 -0
  7. package/dist/daily-anonymizer.d.cts.map +1 -0
  8. package/dist/daily-anonymizer.d.mts +3 -0
  9. package/dist/daily-anonymizer.d.mts.map +1 -0
  10. package/dist/daily-anonymizer.mjs +6 -0
  11. package/dist/daily-anonymizer.mjs.map +1 -0
  12. package/dist/index.cjs +6 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +4 -0
  15. package/dist/index.d.cts.map +1 -0
  16. package/dist/index.d.mts +4 -0
  17. package/dist/index.d.mts.map +1 -0
  18. package/dist/index.mjs +2 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/lib/allowlist.cjs +159 -0
  21. package/dist/lib/allowlist.cjs.map +1 -0
  22. package/dist/lib/allowlist.d.cts +31 -0
  23. package/dist/lib/allowlist.d.cts.map +1 -0
  24. package/dist/lib/allowlist.d.mts +31 -0
  25. package/dist/lib/allowlist.d.mts.map +1 -0
  26. package/dist/lib/allowlist.mjs +155 -0
  27. package/dist/lib/allowlist.mjs.map +1 -0
  28. package/dist/lib/csv.cjs +152 -0
  29. package/dist/lib/csv.cjs.map +1 -0
  30. package/dist/lib/csv.d.cts +16 -0
  31. package/dist/lib/csv.d.cts.map +1 -0
  32. package/dist/lib/csv.d.mts +16 -0
  33. package/dist/lib/csv.d.mts.map +1 -0
  34. package/dist/lib/csv.mjs +149 -0
  35. package/dist/lib/csv.mjs.map +1 -0
  36. package/dist/lib/exposition.cjs +102 -0
  37. package/dist/lib/exposition.cjs.map +1 -0
  38. package/dist/lib/exposition.d.cts +9 -0
  39. package/dist/lib/exposition.d.cts.map +1 -0
  40. package/dist/lib/exposition.d.mts +9 -0
  41. package/dist/lib/exposition.d.mts.map +1 -0
  42. package/dist/lib/exposition.mjs +99 -0
  43. package/dist/lib/exposition.mjs.map +1 -0
  44. package/dist/lib/fold.cjs +294 -0
  45. package/dist/lib/fold.cjs.map +1 -0
  46. package/dist/lib/fold.d.cts +32 -0
  47. package/dist/lib/fold.d.cts.map +1 -0
  48. package/dist/lib/fold.d.mts +32 -0
  49. package/dist/lib/fold.d.mts.map +1 -0
  50. package/dist/lib/fold.mjs +288 -0
  51. package/dist/lib/fold.mjs.map +1 -0
  52. package/dist/lib/log.cjs +116 -0
  53. package/dist/lib/log.cjs.map +1 -0
  54. package/dist/lib/log.d.cts +32 -0
  55. package/dist/lib/log.d.cts.map +1 -0
  56. package/dist/lib/log.d.mts +32 -0
  57. package/dist/lib/log.d.mts.map +1 -0
  58. package/dist/lib/log.mjs +113 -0
  59. package/dist/lib/log.mjs.map +1 -0
  60. package/dist/lib/paths.cjs +91 -0
  61. package/dist/lib/paths.cjs.map +1 -0
  62. package/dist/lib/paths.d.cts +45 -0
  63. package/dist/lib/paths.d.cts.map +1 -0
  64. package/dist/lib/paths.d.mts +45 -0
  65. package/dist/lib/paths.d.mts.map +1 -0
  66. package/dist/lib/paths.mjs +82 -0
  67. package/dist/lib/paths.mjs.map +1 -0
  68. package/dist/lib/push.cjs +122 -0
  69. package/dist/lib/push.cjs.map +1 -0
  70. package/dist/lib/push.d.cts +58 -0
  71. package/dist/lib/push.d.cts.map +1 -0
  72. package/dist/lib/push.d.mts +58 -0
  73. package/dist/lib/push.d.mts.map +1 -0
  74. package/dist/lib/push.mjs +116 -0
  75. package/dist/lib/push.mjs.map +1 -0
  76. package/dist/lib/remoteWrite.cjs +177 -0
  77. package/dist/lib/remoteWrite.cjs.map +1 -0
  78. package/dist/lib/remoteWrite.d.cts +24 -0
  79. package/dist/lib/remoteWrite.d.cts.map +1 -0
  80. package/dist/lib/remoteWrite.d.mts +24 -0
  81. package/dist/lib/remoteWrite.d.mts.map +1 -0
  82. package/dist/lib/remoteWrite.mjs +172 -0
  83. package/dist/lib/remoteWrite.mjs.map +1 -0
  84. package/dist/lib/state.cjs +100 -0
  85. package/dist/lib/state.cjs.map +1 -0
  86. package/dist/lib/state.d.cts +28 -0
  87. package/dist/lib/state.d.cts.map +1 -0
  88. package/dist/lib/state.d.mts +28 -0
  89. package/dist/lib/state.d.mts.map +1 -0
  90. package/dist/lib/state.mjs +95 -0
  91. package/dist/lib/state.mjs.map +1 -0
  92. package/dist/lib/types.cjs +3 -0
  93. package/dist/lib/types.cjs.map +1 -0
  94. package/dist/lib/types.d.cts +82 -0
  95. package/dist/lib/types.d.cts.map +1 -0
  96. package/dist/lib/types.d.mts +82 -0
  97. package/dist/lib/types.d.mts.map +1 -0
  98. package/dist/lib/types.mjs +2 -0
  99. package/dist/lib/types.mjs.map +1 -0
  100. package/dist/run.cjs +137 -0
  101. package/dist/run.cjs.map +1 -0
  102. package/dist/run.d.cts +20 -0
  103. package/dist/run.d.cts.map +1 -0
  104. package/dist/run.d.mts +20 -0
  105. package/dist/run.d.mts.map +1 -0
  106. package/dist/run.mjs +134 -0
  107. package/dist/run.mjs.map +1 -0
  108. package/package.json +100 -0
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildExpositionBody = buildExpositionBody;
4
+ const METRIC_HELP = {
5
+ metamask_devtools_usage_count: 'Count of dev tooling usage events',
6
+ metamask_devtools_duration_sum_ms: 'Sum of terminal event durations in milliseconds',
7
+ metamask_devtools_duration_ms_bucket: 'Classic histogram of terminal event durations in milliseconds',
8
+ metamask_devtools_duration_ms_sum: 'Classic histogram of terminal event durations in milliseconds',
9
+ metamask_devtools_duration_ms_count: 'Classic histogram of terminal event durations in milliseconds',
10
+ };
11
+ /**
12
+ * The base name of the duration histogram family.
13
+ * All three component metric names share this one `# HELP` / `# TYPE` header.
14
+ */
15
+ const DURATION_HISTOGRAM_BASE = 'metamask_devtools_duration_ms';
16
+ /**
17
+ * Escape special characters in a Prometheus label value.
18
+ *
19
+ * @param value - Raw label value string.
20
+ * @returns The value with backslashes, newlines, and double-quotes escaped.
21
+ */
22
+ function escapeLabelValue(value) {
23
+ return value
24
+ .replace(/\\/gu, '\\\\')
25
+ .replace(/\n/gu, '\\n')
26
+ .replace(/"/gu, '\\"');
27
+ }
28
+ /**
29
+ * Serialise a sample's label set as a Prometheus `{key="value",...}` string.
30
+ * Includes `le` when present (histogram bucket samples only).
31
+ *
32
+ * @param sample - The sample point whose labels to format.
33
+ * @returns A formatted Prometheus label selector string.
34
+ */
35
+ function formatLabels(sample) {
36
+ const { labels } = sample;
37
+ const keys = [
38
+ 'repo',
39
+ 'tool_name',
40
+ 'tool_type',
41
+ 'event_type',
42
+ 'agent_vendor',
43
+ 'instance',
44
+ ];
45
+ const parts = keys.map((key) => `${key}="${escapeLabelValue(labels[key])}"`);
46
+ if (sample.le !== undefined) {
47
+ parts.push(`le="${escapeLabelValue(sample.le)}"`);
48
+ }
49
+ return `{${parts.join(',')}}`;
50
+ }
51
+ /**
52
+ * Build Prometheus text exposition for a batch of sample points.
53
+ *
54
+ * @param samples - Sample points to render as Prometheus text exposition.
55
+ * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.
56
+ */
57
+ function buildExpositionBody(samples) {
58
+ const byMetric = new Map();
59
+ for (const sample of samples) {
60
+ const list = byMetric.get(sample.metric) ?? [];
61
+ list.push(sample);
62
+ byMetric.set(sample.metric, list);
63
+ }
64
+ const lines = [];
65
+ const counterMetrics = [
66
+ 'metamask_devtools_usage_count',
67
+ 'metamask_devtools_duration_sum_ms',
68
+ ];
69
+ for (const metric of counterMetrics) {
70
+ const metricSamples = byMetric.get(metric);
71
+ if (!metricSamples || metricSamples.length === 0) {
72
+ continue;
73
+ }
74
+ lines.push(`# HELP ${metric} ${METRIC_HELP[metric]}`);
75
+ lines.push(`# TYPE ${metric} counter`);
76
+ for (const sample of metricSamples) {
77
+ lines.push(`${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`);
78
+ }
79
+ }
80
+ // Emit the three histogram component metrics under a single family header.
81
+ const histogramMetrics = [
82
+ 'metamask_devtools_duration_ms_bucket',
83
+ 'metamask_devtools_duration_ms_sum',
84
+ 'metamask_devtools_duration_ms_count',
85
+ ];
86
+ const histogramSamples = histogramMetrics.flatMap((metric) => byMetric.get(metric) ?? []);
87
+ if (histogramSamples.length > 0) {
88
+ lines.push(`# HELP ${DURATION_HISTOGRAM_BASE} ${METRIC_HELP.metamask_devtools_duration_ms_bucket}`);
89
+ lines.push(`# TYPE ${DURATION_HISTOGRAM_BASE} histogram`);
90
+ for (const metric of histogramMetrics) {
91
+ const metricSamples = byMetric.get(metric);
92
+ if (!metricSamples || metricSamples.length === 0) {
93
+ continue;
94
+ }
95
+ for (const sample of metricSamples) {
96
+ lines.push(`${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`);
97
+ }
98
+ }
99
+ }
100
+ return `${lines.join('\n')}\n`;
101
+ }
102
+ //# sourceMappingURL=exposition.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposition.cjs","sourceRoot":"","sources":["../../src/lib/exposition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MetricName, SamplePoint } from './types';\n\nconst METRIC_HELP: Record<MetricName, string> = {\n metamask_devtools_usage_count: 'Count of dev tooling usage events',\n metamask_devtools_duration_sum_ms:\n 'Sum of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_bucket:\n 'Classic histogram of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_sum:\n 'Classic histogram of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_count:\n 'Classic histogram of terminal event durations in milliseconds',\n};\n\n/**\n * The base name of the duration histogram family.\n * All three component metric names share this one `# HELP` / `# TYPE` header.\n */\nconst DURATION_HISTOGRAM_BASE = 'metamask_devtools_duration_ms';\n\n/**\n * Escape special characters in a Prometheus label value.\n *\n * @param value - Raw label value string.\n * @returns The value with backslashes, newlines, and double-quotes escaped.\n */\nfunction escapeLabelValue(value: string): string {\n return value\n .replace(/\\\\/gu, '\\\\\\\\')\n .replace(/\\n/gu, '\\\\n')\n .replace(/\"/gu, '\\\\\"');\n}\n\n/**\n * Serialise a sample's label set as a Prometheus `{key=\"value\",...}` string.\n * Includes `le` when present (histogram bucket samples only).\n *\n * @param sample - The sample point whose labels to format.\n * @returns A formatted Prometheus label selector string.\n */\nfunction formatLabels(sample: SamplePoint): string {\n const { labels } = sample;\n const keys = [\n 'repo',\n 'tool_name',\n 'tool_type',\n 'event_type',\n 'agent_vendor',\n 'instance',\n ] as const;\n const parts = keys.map((key) => `${key}=\"${escapeLabelValue(labels[key])}\"`);\n if (sample.le !== undefined) {\n parts.push(`le=\"${escapeLabelValue(sample.le)}\"`);\n }\n return `{${parts.join(',')}}`;\n}\n\n/**\n * Build Prometheus text exposition for a batch of sample points.\n *\n * @param samples - Sample points to render as Prometheus text exposition.\n * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.\n */\nexport function buildExpositionBody(samples: SamplePoint[]): string {\n const byMetric = new Map<MetricName, SamplePoint[]>();\n for (const sample of samples) {\n const list = byMetric.get(sample.metric) ?? [];\n list.push(sample);\n byMetric.set(sample.metric, list);\n }\n\n const lines: string[] = [];\n const counterMetrics: MetricName[] = [\n 'metamask_devtools_usage_count',\n 'metamask_devtools_duration_sum_ms',\n ];\n\n for (const metric of counterMetrics) {\n const metricSamples = byMetric.get(metric);\n if (!metricSamples || metricSamples.length === 0) {\n continue;\n }\n lines.push(`# HELP ${metric} ${METRIC_HELP[metric]}`);\n lines.push(`# TYPE ${metric} counter`);\n for (const sample of metricSamples) {\n lines.push(\n `${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`,\n );\n }\n }\n\n // Emit the three histogram component metrics under a single family header.\n const histogramMetrics: MetricName[] = [\n 'metamask_devtools_duration_ms_bucket',\n 'metamask_devtools_duration_ms_sum',\n 'metamask_devtools_duration_ms_count',\n ];\n const histogramSamples = histogramMetrics.flatMap(\n (metric) => byMetric.get(metric) ?? [],\n );\n if (histogramSamples.length > 0) {\n lines.push(\n `# HELP ${DURATION_HISTOGRAM_BASE} ${METRIC_HELP.metamask_devtools_duration_ms_bucket}`,\n );\n lines.push(`# TYPE ${DURATION_HISTOGRAM_BASE} histogram`);\n for (const metric of histogramMetrics) {\n const metricSamples = byMetric.get(metric);\n if (!metricSamples || metricSamples.length === 0) {\n continue;\n }\n for (const sample of metricSamples) {\n lines.push(\n `${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`,\n );\n }\n }\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { SamplePoint } from "./types.cjs";
2
+ /**
3
+ * Build Prometheus text exposition for a batch of sample points.
4
+ *
5
+ * @param samples - Sample points to render as Prometheus text exposition.
6
+ * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.
7
+ */
8
+ export declare function buildExpositionBody(samples: SamplePoint[]): string;
9
+ //# sourceMappingURL=exposition.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposition.d.cts","sourceRoot":"","sources":["../../src/lib/exposition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,WAAW,EAAE,oBAAgB;AAyDvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAwDlE"}
@@ -0,0 +1,9 @@
1
+ import type { SamplePoint } from "./types.mjs";
2
+ /**
3
+ * Build Prometheus text exposition for a batch of sample points.
4
+ *
5
+ * @param samples - Sample points to render as Prometheus text exposition.
6
+ * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.
7
+ */
8
+ export declare function buildExpositionBody(samples: SamplePoint[]): string;
9
+ //# sourceMappingURL=exposition.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposition.d.mts","sourceRoot":"","sources":["../../src/lib/exposition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,WAAW,EAAE,oBAAgB;AAyDvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAwDlE"}
@@ -0,0 +1,99 @@
1
+ const METRIC_HELP = {
2
+ metamask_devtools_usage_count: 'Count of dev tooling usage events',
3
+ metamask_devtools_duration_sum_ms: 'Sum of terminal event durations in milliseconds',
4
+ metamask_devtools_duration_ms_bucket: 'Classic histogram of terminal event durations in milliseconds',
5
+ metamask_devtools_duration_ms_sum: 'Classic histogram of terminal event durations in milliseconds',
6
+ metamask_devtools_duration_ms_count: 'Classic histogram of terminal event durations in milliseconds',
7
+ };
8
+ /**
9
+ * The base name of the duration histogram family.
10
+ * All three component metric names share this one `# HELP` / `# TYPE` header.
11
+ */
12
+ const DURATION_HISTOGRAM_BASE = 'metamask_devtools_duration_ms';
13
+ /**
14
+ * Escape special characters in a Prometheus label value.
15
+ *
16
+ * @param value - Raw label value string.
17
+ * @returns The value with backslashes, newlines, and double-quotes escaped.
18
+ */
19
+ function escapeLabelValue(value) {
20
+ return value
21
+ .replace(/\\/gu, '\\\\')
22
+ .replace(/\n/gu, '\\n')
23
+ .replace(/"/gu, '\\"');
24
+ }
25
+ /**
26
+ * Serialise a sample's label set as a Prometheus `{key="value",...}` string.
27
+ * Includes `le` when present (histogram bucket samples only).
28
+ *
29
+ * @param sample - The sample point whose labels to format.
30
+ * @returns A formatted Prometheus label selector string.
31
+ */
32
+ function formatLabels(sample) {
33
+ const { labels } = sample;
34
+ const keys = [
35
+ 'repo',
36
+ 'tool_name',
37
+ 'tool_type',
38
+ 'event_type',
39
+ 'agent_vendor',
40
+ 'instance',
41
+ ];
42
+ const parts = keys.map((key) => `${key}="${escapeLabelValue(labels[key])}"`);
43
+ if (sample.le !== undefined) {
44
+ parts.push(`le="${escapeLabelValue(sample.le)}"`);
45
+ }
46
+ return `{${parts.join(',')}}`;
47
+ }
48
+ /**
49
+ * Build Prometheus text exposition for a batch of sample points.
50
+ *
51
+ * @param samples - Sample points to render as Prometheus text exposition.
52
+ * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.
53
+ */
54
+ export function buildExpositionBody(samples) {
55
+ const byMetric = new Map();
56
+ for (const sample of samples) {
57
+ const list = byMetric.get(sample.metric) ?? [];
58
+ list.push(sample);
59
+ byMetric.set(sample.metric, list);
60
+ }
61
+ const lines = [];
62
+ const counterMetrics = [
63
+ 'metamask_devtools_usage_count',
64
+ 'metamask_devtools_duration_sum_ms',
65
+ ];
66
+ for (const metric of counterMetrics) {
67
+ const metricSamples = byMetric.get(metric);
68
+ if (!metricSamples || metricSamples.length === 0) {
69
+ continue;
70
+ }
71
+ lines.push(`# HELP ${metric} ${METRIC_HELP[metric]}`);
72
+ lines.push(`# TYPE ${metric} counter`);
73
+ for (const sample of metricSamples) {
74
+ lines.push(`${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`);
75
+ }
76
+ }
77
+ // Emit the three histogram component metrics under a single family header.
78
+ const histogramMetrics = [
79
+ 'metamask_devtools_duration_ms_bucket',
80
+ 'metamask_devtools_duration_ms_sum',
81
+ 'metamask_devtools_duration_ms_count',
82
+ ];
83
+ const histogramSamples = histogramMetrics.flatMap((metric) => byMetric.get(metric) ?? []);
84
+ if (histogramSamples.length > 0) {
85
+ lines.push(`# HELP ${DURATION_HISTOGRAM_BASE} ${METRIC_HELP.metamask_devtools_duration_ms_bucket}`);
86
+ lines.push(`# TYPE ${DURATION_HISTOGRAM_BASE} histogram`);
87
+ for (const metric of histogramMetrics) {
88
+ const metricSamples = byMetric.get(metric);
89
+ if (!metricSamples || metricSamples.length === 0) {
90
+ continue;
91
+ }
92
+ for (const sample of metricSamples) {
93
+ lines.push(`${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`);
94
+ }
95
+ }
96
+ }
97
+ return `${lines.join('\n')}\n`;
98
+ }
99
+ //# sourceMappingURL=exposition.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposition.mjs","sourceRoot":"","sources":["../../src/lib/exposition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MetricName, SamplePoint } from './types';\n\nconst METRIC_HELP: Record<MetricName, string> = {\n metamask_devtools_usage_count: 'Count of dev tooling usage events',\n metamask_devtools_duration_sum_ms:\n 'Sum of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_bucket:\n 'Classic histogram of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_sum:\n 'Classic histogram of terminal event durations in milliseconds',\n metamask_devtools_duration_ms_count:\n 'Classic histogram of terminal event durations in milliseconds',\n};\n\n/**\n * The base name of the duration histogram family.\n * All three component metric names share this one `# HELP` / `# TYPE` header.\n */\nconst DURATION_HISTOGRAM_BASE = 'metamask_devtools_duration_ms';\n\n/**\n * Escape special characters in a Prometheus label value.\n *\n * @param value - Raw label value string.\n * @returns The value with backslashes, newlines, and double-quotes escaped.\n */\nfunction escapeLabelValue(value: string): string {\n return value\n .replace(/\\\\/gu, '\\\\\\\\')\n .replace(/\\n/gu, '\\\\n')\n .replace(/\"/gu, '\\\\\"');\n}\n\n/**\n * Serialise a sample's label set as a Prometheus `{key=\"value\",...}` string.\n * Includes `le` when present (histogram bucket samples only).\n *\n * @param sample - The sample point whose labels to format.\n * @returns A formatted Prometheus label selector string.\n */\nfunction formatLabels(sample: SamplePoint): string {\n const { labels } = sample;\n const keys = [\n 'repo',\n 'tool_name',\n 'tool_type',\n 'event_type',\n 'agent_vendor',\n 'instance',\n ] as const;\n const parts = keys.map((key) => `${key}=\"${escapeLabelValue(labels[key])}\"`);\n if (sample.le !== undefined) {\n parts.push(`le=\"${escapeLabelValue(sample.le)}\"`);\n }\n return `{${parts.join(',')}}`;\n}\n\n/**\n * Build Prometheus text exposition for a batch of sample points.\n *\n * @param samples - Sample points to render as Prometheus text exposition.\n * @returns A Prometheus text exposition string with `# HELP`, `# TYPE`, and metric lines.\n */\nexport function buildExpositionBody(samples: SamplePoint[]): string {\n const byMetric = new Map<MetricName, SamplePoint[]>();\n for (const sample of samples) {\n const list = byMetric.get(sample.metric) ?? [];\n list.push(sample);\n byMetric.set(sample.metric, list);\n }\n\n const lines: string[] = [];\n const counterMetrics: MetricName[] = [\n 'metamask_devtools_usage_count',\n 'metamask_devtools_duration_sum_ms',\n ];\n\n for (const metric of counterMetrics) {\n const metricSamples = byMetric.get(metric);\n if (!metricSamples || metricSamples.length === 0) {\n continue;\n }\n lines.push(`# HELP ${metric} ${METRIC_HELP[metric]}`);\n lines.push(`# TYPE ${metric} counter`);\n for (const sample of metricSamples) {\n lines.push(\n `${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`,\n );\n }\n }\n\n // Emit the three histogram component metrics under a single family header.\n const histogramMetrics: MetricName[] = [\n 'metamask_devtools_duration_ms_bucket',\n 'metamask_devtools_duration_ms_sum',\n 'metamask_devtools_duration_ms_count',\n ];\n const histogramSamples = histogramMetrics.flatMap(\n (metric) => byMetric.get(metric) ?? [],\n );\n if (histogramSamples.length > 0) {\n lines.push(\n `# HELP ${DURATION_HISTOGRAM_BASE} ${METRIC_HELP.metamask_devtools_duration_ms_bucket}`,\n );\n lines.push(`# TYPE ${DURATION_HISTOGRAM_BASE} histogram`);\n for (const metric of histogramMetrics) {\n const metricSamples = byMetric.get(metric);\n if (!metricSamples || metricSamples.length === 0) {\n continue;\n }\n for (const sample of metricSamples) {\n lines.push(\n `${metric}${formatLabels(sample)} ${sample.value} ${sample.timestampMs}`,\n );\n }\n }\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n"]}
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DURATION_BUCKETS_MS = void 0;
4
+ exports.fold = fold;
5
+ exports.todayUtcDay = todayUtcDay;
6
+ exports.maxDay = maxDay;
7
+ const TERMINAL_EVENT_TYPES = new Set([
8
+ 'success',
9
+ 'failure',
10
+ 'interrupted',
11
+ ]);
12
+ /**
13
+ * Classic histogram bucket upper bounds in milliseconds, covering sub-second
14
+ * scripts up to ~10 minutes. A synthetic `+Inf` bucket is always appended.
15
+ */
16
+ exports.DURATION_BUCKETS_MS = [
17
+ 100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000, 120000, 300000, 600000,
18
+ ];
19
+ /**
20
+ * Extract the UTC calendar day from an ISO-8601 timestamp string.
21
+ *
22
+ * @param iso - An ISO-8601 timestamp string.
23
+ * @returns The `YYYY-MM-DD` portion of the timestamp.
24
+ */
25
+ function utcDayFromIso(iso) {
26
+ return iso.slice(0, 10);
27
+ }
28
+ /**
29
+ * Convert a `YYYY-MM-DD` UTC day string to a Unix timestamp at midnight UTC.
30
+ *
31
+ * @param day - A UTC day string in `YYYY-MM-DD` format.
32
+ * @returns Milliseconds since epoch for midnight UTC on that day.
33
+ */
34
+ function utcMidnightMs(day) {
35
+ const parts = day.split('-');
36
+ const year = Number(parts[0]);
37
+ const month = Number(parts[1]);
38
+ const dayOfMonth = Number(parts[2]);
39
+ return Date.UTC(year, month - 1, dayOfMonth);
40
+ }
41
+ /**
42
+ * Returns true when a given day has pending (unpushed) events.
43
+ * Today's events are excluded so the day is complete before pushing.
44
+ *
45
+ * @param day - UTC calendar day of the event.
46
+ * @param lastPushedDay - The most recently pushed day, or `null` if never pushed.
47
+ * @param todayUtc - The current UTC calendar day.
48
+ * @returns `true` when the day is after `lastPushedDay` and before today.
49
+ */
50
+ function isDayPending(day, lastPushedDay, todayUtc) {
51
+ if (day >= todayUtc) {
52
+ return false;
53
+ }
54
+ if (lastPushedDay === null) {
55
+ return true;
56
+ }
57
+ return day > lastPushedDay;
58
+ }
59
+ /**
60
+ * Strip the well-known tool-type prefix (`skill:` or `yarn:`) from a tool name.
61
+ *
62
+ * @param toolName - Raw tool name from the CSV event.
63
+ * @param toolType - The parsed tool type for the event.
64
+ * @returns The tool name without its prefix, or unchanged when no prefix matches.
65
+ */
66
+ function stripToolName(toolName, toolType) {
67
+ if (toolType === 'skill' && toolName.startsWith('skill:')) {
68
+ return toolName.slice('skill:'.length);
69
+ }
70
+ if (toolType === 'yarn_script' && toolName.startsWith('yarn:')) {
71
+ return toolName.slice('yarn:'.length);
72
+ }
73
+ return toolName;
74
+ }
75
+ /**
76
+ * Map a raw CSV event to its Prometheus `event_type` label value.
77
+ * Returns `null` for `end` events whose `success` field is not a boolean.
78
+ *
79
+ * @param event - The raw tool event to classify.
80
+ * @returns The corresponding `EventTypeLabel`, or `null` to drop the event.
81
+ */
82
+ function mapEventType(event) {
83
+ // Switch over the exhaustive CsvEventType union.
84
+ switch (event.event_type) {
85
+ case 'start':
86
+ return 'start';
87
+ case 'interrupted':
88
+ return 'interrupted';
89
+ case 'end':
90
+ if (event.success === true) {
91
+ return 'success';
92
+ }
93
+ if (event.success === false) {
94
+ return 'failure';
95
+ }
96
+ return null;
97
+ default:
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Returns true when the stripped tool name appears on the allowlist for its type.
103
+ *
104
+ * @param strippedName - Tool name after prefix stripping.
105
+ * @param toolType - The tool type (skill or yarn_script).
106
+ * @param allowlist - The combined skill and script allowlist.
107
+ * @returns `true` when the tool is allowlisted.
108
+ */
109
+ function isAllowlisted(strippedName, toolType, allowlist) {
110
+ if (toolType === 'skill') {
111
+ // The allowlist is built from the MetaMask/ConsenSys skills repo cache:
112
+ // extractSkillNames reads directory names (e.g. `create-pr`) and stores
113
+ // them with the `mms-` prefix (e.g. `mms-create-pr`). A skill event passes
114
+ // only when its logged name — after stripping the `skill:` type prefix —
115
+ // is found verbatim in that Set. Skills not present in the repo cache
116
+ // (e.g. personal Cursor skills logged as `skill:pr-review-queue`) never
117
+ // match because the Set has `mms-pr-review-queue`, not `pr-review-queue`.
118
+ return allowlist.skills.has(strippedName);
119
+ }
120
+ return allowlist.scripts.has(strippedName);
121
+ }
122
+ /**
123
+ * Build a stable string key that uniquely identifies a (day, labels, metric) combination.
124
+ *
125
+ * @param day - UTC calendar day string.
126
+ * @param labels - Prometheus label set for the sample.
127
+ * @param metric - Prometheus metric name.
128
+ * @returns A JSON-serialised key.
129
+ */
130
+ function groupKey(day, labels, metric) {
131
+ return JSON.stringify({ day, metric, labels });
132
+ }
133
+ /**
134
+ * Fold raw CSV events into anonymized Prometheus sample points for pending days.
135
+ *
136
+ * @param events - All parsed tool events.
137
+ * @param allowlist - Skill and script allowlists used to filter events.
138
+ * @param instanceUuid - Per-install UUID used as the Prometheus `instance` label.
139
+ * @param lastPushedDay - The last UTC day that was successfully pushed, or null.
140
+ * @param todayUtc - The current UTC day (events on this day are not yet pushed).
141
+ * @returns An array of sample points ready for Prometheus exposition.
142
+ */
143
+ function fold(events, allowlist, instanceUuid, lastPushedDay, todayUtc) {
144
+ const usageCounts = new Map();
145
+ const durationSums = new Map();
146
+ // Keyed by the same group key as durationSums (terminal events with a duration).
147
+ const durationHistograms = new Map();
148
+ for (const event of events) {
149
+ const day = utcDayFromIso(event.created_at);
150
+ if (!isDayPending(day, lastPushedDay, todayUtc)) {
151
+ continue;
152
+ }
153
+ const strippedName = stripToolName(event.tool_name, event.tool_type);
154
+ if (!isAllowlisted(strippedName, event.tool_type, allowlist)) {
155
+ continue;
156
+ }
157
+ const eventTypeLabel = mapEventType(event);
158
+ if (!eventTypeLabel) {
159
+ continue;
160
+ }
161
+ const labels = {
162
+ repo: event.repo,
163
+ tool_name: strippedName,
164
+ tool_type: event.tool_type,
165
+ event_type: eventTypeLabel,
166
+ agent_vendor: event.agent_vendor,
167
+ instance: instanceUuid,
168
+ };
169
+ const usageMetric = 'metamask_devtools_usage_count';
170
+ const usageKey = groupKey(day, labels, usageMetric);
171
+ const existingUsage = usageCounts.get(usageKey);
172
+ if (existingUsage) {
173
+ existingUsage.value += 1;
174
+ }
175
+ else {
176
+ usageCounts.set(usageKey, {
177
+ metric: usageMetric,
178
+ labels,
179
+ value: 1,
180
+ day,
181
+ timestampMs: utcMidnightMs(day),
182
+ });
183
+ }
184
+ if (TERMINAL_EVENT_TYPES.has(eventTypeLabel) &&
185
+ event.duration_ms !== null) {
186
+ const durationMetric = 'metamask_devtools_duration_sum_ms';
187
+ const durationKey = groupKey(day, labels, durationMetric);
188
+ const existingDuration = durationSums.get(durationKey);
189
+ if (existingDuration) {
190
+ existingDuration.value += event.duration_ms;
191
+ }
192
+ else {
193
+ durationSums.set(durationKey, {
194
+ metric: durationMetric,
195
+ labels,
196
+ value: event.duration_ms,
197
+ day,
198
+ timestampMs: utcMidnightMs(day),
199
+ });
200
+ }
201
+ // Accumulate the histogram for the same group key (reuse durationKey
202
+ // since both are keyed on the same (day, labels) combination).
203
+ const existing = durationHistograms.get(durationKey);
204
+ if (existing) {
205
+ existing.sum += event.duration_ms;
206
+ existing.count += 1;
207
+ // Cumulative: increment every bucket whose upper bound >= duration.
208
+ existing.bucketCounts = existing.bucketCounts.map((count, i) => event.duration_ms !== null &&
209
+ event.duration_ms <= exports.DURATION_BUCKETS_MS[i]
210
+ ? count + 1
211
+ : count);
212
+ }
213
+ else {
214
+ const bucketCounts = exports.DURATION_BUCKETS_MS.map((bound) => event.duration_ms !== null && event.duration_ms <= bound ? 1 : 0);
215
+ durationHistograms.set(durationKey, {
216
+ day,
217
+ labels,
218
+ sum: event.duration_ms,
219
+ count: 1,
220
+ bucketCounts,
221
+ });
222
+ }
223
+ }
224
+ }
225
+ const histogramSamples = [];
226
+ for (const acc of durationHistograms.values()) {
227
+ const { day, labels } = acc;
228
+ const timestampMs = utcMidnightMs(day);
229
+ histogramSamples.push({
230
+ metric: 'metamask_devtools_duration_ms_sum',
231
+ labels,
232
+ value: acc.sum,
233
+ day,
234
+ timestampMs,
235
+ });
236
+ histogramSamples.push({
237
+ metric: 'metamask_devtools_duration_ms_count',
238
+ labels,
239
+ value: acc.count,
240
+ day,
241
+ timestampMs,
242
+ });
243
+ acc.bucketCounts.forEach((count, i) => {
244
+ histogramSamples.push({
245
+ metric: 'metamask_devtools_duration_ms_bucket',
246
+ labels,
247
+ value: count,
248
+ day,
249
+ timestampMs,
250
+ le: String(exports.DURATION_BUCKETS_MS[i]),
251
+ });
252
+ });
253
+ // The +Inf bucket equals total count (every observation falls below +Inf).
254
+ histogramSamples.push({
255
+ metric: 'metamask_devtools_duration_ms_bucket',
256
+ labels,
257
+ value: acc.count,
258
+ day,
259
+ timestampMs,
260
+ le: '+Inf',
261
+ });
262
+ }
263
+ return [
264
+ ...usageCounts.values(),
265
+ ...durationSums.values(),
266
+ ...histogramSamples,
267
+ ];
268
+ }
269
+ /**
270
+ * Returns the current UTC calendar day as `YYYY-MM-DD`.
271
+ *
272
+ * @param now - The current date; defaults to `new Date()`.
273
+ * @returns The UTC day string.
274
+ */
275
+ function todayUtcDay(now = new Date()) {
276
+ return now.toISOString().slice(0, 10);
277
+ }
278
+ /**
279
+ * Returns the latest `day` value across all sample points, or null for an empty array.
280
+ *
281
+ * @param samples - The sample points to inspect.
282
+ * @returns The maximum UTC day string, or null.
283
+ */
284
+ function maxDay(samples) {
285
+ if (samples.length === 0) {
286
+ return null;
287
+ }
288
+ // Reduce over string[] so there is no undefined initial value issue — reduce without
289
+ // initialValue on a non-empty array is always safe and avoids a dead branch.
290
+ return samples
291
+ .map((sample) => sample.day)
292
+ .reduce((max, day) => (day > max ? day : max));
293
+ }
294
+ //# sourceMappingURL=fold.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.cjs","sourceRoot":"","sources":["../../src/lib/fold.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Allowlist,\n EventTypeLabel,\n MetricName,\n SampleLabels,\n SamplePoint,\n ToolEvent,\n} from './types';\n\nconst TERMINAL_EVENT_TYPES: ReadonlySet<EventTypeLabel> = new Set([\n 'success',\n 'failure',\n 'interrupted',\n]);\n\n/**\n * Classic histogram bucket upper bounds in milliseconds, covering sub-second\n * scripts up to ~10 minutes. A synthetic `+Inf` bucket is always appended.\n */\nexport const DURATION_BUCKETS_MS: readonly number[] = [\n 100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000, 120000, 300000, 600000,\n];\n\n/** Accumulated histogram state for a single group/day. */\ntype HistogramAccumulator = {\n /** UTC calendar day this group belongs to. */\n day: string;\n /** Prometheus label set shared by every sample in this group. */\n labels: SampleLabels;\n /** Sum of all observed durations for this group. */\n sum: number;\n /** Count of all observed durations for this group. */\n count: number;\n /**\n * Cumulative bucket counts, one entry per `DURATION_BUCKETS_MS` boundary.\n * `bucketCounts[i]` is the number of observations where `duration_ms <= DURATION_BUCKETS_MS[i]`.\n */\n bucketCounts: number[];\n};\n\n/**\n * Extract the UTC calendar day from an ISO-8601 timestamp string.\n *\n * @param iso - An ISO-8601 timestamp string.\n * @returns The `YYYY-MM-DD` portion of the timestamp.\n */\nfunction utcDayFromIso(iso: string): string {\n return iso.slice(0, 10);\n}\n\n/**\n * Convert a `YYYY-MM-DD` UTC day string to a Unix timestamp at midnight UTC.\n *\n * @param day - A UTC day string in `YYYY-MM-DD` format.\n * @returns Milliseconds since epoch for midnight UTC on that day.\n */\nfunction utcMidnightMs(day: string): number {\n const parts = day.split('-');\n const year = Number(parts[0]);\n const month = Number(parts[1]);\n const dayOfMonth = Number(parts[2]);\n return Date.UTC(year, month - 1, dayOfMonth);\n}\n\n/**\n * Returns true when a given day has pending (unpushed) events.\n * Today's events are excluded so the day is complete before pushing.\n *\n * @param day - UTC calendar day of the event.\n * @param lastPushedDay - The most recently pushed day, or `null` if never pushed.\n * @param todayUtc - The current UTC calendar day.\n * @returns `true` when the day is after `lastPushedDay` and before today.\n */\nfunction isDayPending(\n day: string,\n lastPushedDay: string | null,\n todayUtc: string,\n): boolean {\n if (day >= todayUtc) {\n return false;\n }\n if (lastPushedDay === null) {\n return true;\n }\n return day > lastPushedDay;\n}\n\n/**\n * Strip the well-known tool-type prefix (`skill:` or `yarn:`) from a tool name.\n *\n * @param toolName - Raw tool name from the CSV event.\n * @param toolType - The parsed tool type for the event.\n * @returns The tool name without its prefix, or unchanged when no prefix matches.\n */\nfunction stripToolName(\n toolName: string,\n toolType: ToolEvent['tool_type'],\n): string {\n if (toolType === 'skill' && toolName.startsWith('skill:')) {\n return toolName.slice('skill:'.length);\n }\n if (toolType === 'yarn_script' && toolName.startsWith('yarn:')) {\n return toolName.slice('yarn:'.length);\n }\n return toolName;\n}\n\n/**\n * Map a raw CSV event to its Prometheus `event_type` label value.\n * Returns `null` for `end` events whose `success` field is not a boolean.\n *\n * @param event - The raw tool event to classify.\n * @returns The corresponding `EventTypeLabel`, or `null` to drop the event.\n */\nfunction mapEventType(event: ToolEvent): EventTypeLabel | null {\n // Switch over the exhaustive CsvEventType union.\n switch (event.event_type) {\n case 'start':\n return 'start';\n case 'interrupted':\n return 'interrupted';\n case 'end':\n if (event.success === true) {\n return 'success';\n }\n if (event.success === false) {\n return 'failure';\n }\n return null;\n default:\n return null;\n }\n}\n\n/**\n * Returns true when the stripped tool name appears on the allowlist for its type.\n *\n * @param strippedName - Tool name after prefix stripping.\n * @param toolType - The tool type (skill or yarn_script).\n * @param allowlist - The combined skill and script allowlist.\n * @returns `true` when the tool is allowlisted.\n */\nfunction isAllowlisted(\n strippedName: string,\n toolType: ToolEvent['tool_type'],\n allowlist: Allowlist,\n): boolean {\n if (toolType === 'skill') {\n // The allowlist is built from the MetaMask/ConsenSys skills repo cache:\n // extractSkillNames reads directory names (e.g. `create-pr`) and stores\n // them with the `mms-` prefix (e.g. `mms-create-pr`). A skill event passes\n // only when its logged name — after stripping the `skill:` type prefix —\n // is found verbatim in that Set. Skills not present in the repo cache\n // (e.g. personal Cursor skills logged as `skill:pr-review-queue`) never\n // match because the Set has `mms-pr-review-queue`, not `pr-review-queue`.\n return allowlist.skills.has(strippedName);\n }\n return allowlist.scripts.has(strippedName);\n}\n\n/** Opaque key used to group events into a single `SamplePoint`. */\ntype GroupKey = string;\n\n/**\n * Build a stable string key that uniquely identifies a (day, labels, metric) combination.\n *\n * @param day - UTC calendar day string.\n * @param labels - Prometheus label set for the sample.\n * @param metric - Prometheus metric name.\n * @returns A JSON-serialised key.\n */\nfunction groupKey(\n day: string,\n labels: SampleLabels,\n metric: MetricName,\n): GroupKey {\n return JSON.stringify({ day, metric, labels });\n}\n\n/**\n * Fold raw CSV events into anonymized Prometheus sample points for pending days.\n *\n * @param events - All parsed tool events.\n * @param allowlist - Skill and script allowlists used to filter events.\n * @param instanceUuid - Per-install UUID used as the Prometheus `instance` label.\n * @param lastPushedDay - The last UTC day that was successfully pushed, or null.\n * @param todayUtc - The current UTC day (events on this day are not yet pushed).\n * @returns An array of sample points ready for Prometheus exposition.\n */\nexport function fold(\n events: ToolEvent[],\n allowlist: Allowlist,\n instanceUuid: string,\n lastPushedDay: string | null,\n todayUtc: string,\n): SamplePoint[] {\n const usageCounts = new Map<GroupKey, SamplePoint>();\n const durationSums = new Map<GroupKey, SamplePoint>();\n // Keyed by the same group key as durationSums (terminal events with a duration).\n const durationHistograms = new Map<GroupKey, HistogramAccumulator>();\n\n for (const event of events) {\n const day = utcDayFromIso(event.created_at);\n if (!isDayPending(day, lastPushedDay, todayUtc)) {\n continue;\n }\n\n const strippedName = stripToolName(event.tool_name, event.tool_type);\n if (!isAllowlisted(strippedName, event.tool_type, allowlist)) {\n continue;\n }\n\n const eventTypeLabel = mapEventType(event);\n if (!eventTypeLabel) {\n continue;\n }\n\n const labels: SampleLabels = {\n repo: event.repo,\n tool_name: strippedName,\n tool_type: event.tool_type,\n event_type: eventTypeLabel,\n agent_vendor: event.agent_vendor,\n instance: instanceUuid,\n };\n\n const usageMetric: MetricName = 'metamask_devtools_usage_count';\n const usageKey = groupKey(day, labels, usageMetric);\n const existingUsage = usageCounts.get(usageKey);\n if (existingUsage) {\n existingUsage.value += 1;\n } else {\n usageCounts.set(usageKey, {\n metric: usageMetric,\n labels,\n value: 1,\n day,\n timestampMs: utcMidnightMs(day),\n });\n }\n\n if (\n TERMINAL_EVENT_TYPES.has(eventTypeLabel) &&\n event.duration_ms !== null\n ) {\n const durationMetric: MetricName = 'metamask_devtools_duration_sum_ms';\n const durationKey = groupKey(day, labels, durationMetric);\n const existingDuration = durationSums.get(durationKey);\n if (existingDuration) {\n existingDuration.value += event.duration_ms;\n } else {\n durationSums.set(durationKey, {\n metric: durationMetric,\n labels,\n value: event.duration_ms,\n day,\n timestampMs: utcMidnightMs(day),\n });\n }\n\n // Accumulate the histogram for the same group key (reuse durationKey\n // since both are keyed on the same (day, labels) combination).\n const existing = durationHistograms.get(durationKey);\n if (existing) {\n existing.sum += event.duration_ms;\n existing.count += 1;\n // Cumulative: increment every bucket whose upper bound >= duration.\n existing.bucketCounts = existing.bucketCounts.map((count, i) =>\n event.duration_ms !== null &&\n event.duration_ms <= (DURATION_BUCKETS_MS[i] as number)\n ? count + 1\n : count,\n );\n } else {\n const bucketCounts = DURATION_BUCKETS_MS.map((bound) =>\n event.duration_ms !== null && event.duration_ms <= bound ? 1 : 0,\n );\n durationHistograms.set(durationKey, {\n day,\n labels,\n sum: event.duration_ms,\n count: 1,\n bucketCounts,\n });\n }\n }\n }\n\n const histogramSamples: SamplePoint[] = [];\n for (const acc of durationHistograms.values()) {\n const { day, labels } = acc;\n const timestampMs = utcMidnightMs(day);\n\n histogramSamples.push({\n metric: 'metamask_devtools_duration_ms_sum',\n labels,\n value: acc.sum,\n day,\n timestampMs,\n });\n\n histogramSamples.push({\n metric: 'metamask_devtools_duration_ms_count',\n labels,\n value: acc.count,\n day,\n timestampMs,\n });\n\n acc.bucketCounts.forEach((count, i) => {\n histogramSamples.push({\n metric: 'metamask_devtools_duration_ms_bucket',\n labels,\n value: count,\n day,\n timestampMs,\n le: String(DURATION_BUCKETS_MS[i]),\n });\n });\n\n // The +Inf bucket equals total count (every observation falls below +Inf).\n histogramSamples.push({\n metric: 'metamask_devtools_duration_ms_bucket',\n labels,\n value: acc.count,\n day,\n timestampMs,\n le: '+Inf',\n });\n }\n\n return [\n ...usageCounts.values(),\n ...durationSums.values(),\n ...histogramSamples,\n ];\n}\n\n/**\n * Returns the current UTC calendar day as `YYYY-MM-DD`.\n *\n * @param now - The current date; defaults to `new Date()`.\n * @returns The UTC day string.\n */\nexport function todayUtcDay(now = new Date()): string {\n return now.toISOString().slice(0, 10);\n}\n\n/**\n * Returns the latest `day` value across all sample points, or null for an empty array.\n *\n * @param samples - The sample points to inspect.\n * @returns The maximum UTC day string, or null.\n */\nexport function maxDay(samples: SamplePoint[]): string | null {\n if (samples.length === 0) {\n return null;\n }\n // Reduce over string[] so there is no undefined initial value issue — reduce without\n // initialValue on a non-empty array is always safe and avoids a dead branch.\n return samples\n .map((sample) => sample.day)\n .reduce((max, day) => (day > max ? day : max));\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import type { Allowlist, SamplePoint, ToolEvent } from "./types.cjs";
2
+ /**
3
+ * Classic histogram bucket upper bounds in milliseconds, covering sub-second
4
+ * scripts up to ~10 minutes. A synthetic `+Inf` bucket is always appended.
5
+ */
6
+ export declare const DURATION_BUCKETS_MS: readonly number[];
7
+ /**
8
+ * Fold raw CSV events into anonymized Prometheus sample points for pending days.
9
+ *
10
+ * @param events - All parsed tool events.
11
+ * @param allowlist - Skill and script allowlists used to filter events.
12
+ * @param instanceUuid - Per-install UUID used as the Prometheus `instance` label.
13
+ * @param lastPushedDay - The last UTC day that was successfully pushed, or null.
14
+ * @param todayUtc - The current UTC day (events on this day are not yet pushed).
15
+ * @returns An array of sample points ready for Prometheus exposition.
16
+ */
17
+ export declare function fold(events: ToolEvent[], allowlist: Allowlist, instanceUuid: string, lastPushedDay: string | null, todayUtc: string): SamplePoint[];
18
+ /**
19
+ * Returns the current UTC calendar day as `YYYY-MM-DD`.
20
+ *
21
+ * @param now - The current date; defaults to `new Date()`.
22
+ * @returns The UTC day string.
23
+ */
24
+ export declare function todayUtcDay(now?: Date): string;
25
+ /**
26
+ * Returns the latest `day` value across all sample points, or null for an empty array.
27
+ *
28
+ * @param samples - The sample points to inspect.
29
+ * @returns The maximum UTC day string, or null.
30
+ */
31
+ export declare function maxDay(samples: SamplePoint[]): string | null;
32
+ //# sourceMappingURL=fold.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.d.cts","sourceRoot":"","sources":["../../src/lib/fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAIT,WAAW,EACX,SAAS,EACV,oBAAgB;AAQjB;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAEhD,CAAC;AA8JF;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAClB,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,QAAQ,EAAE,MAAM,GACf,WAAW,EAAE,CA6If;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,OAAa,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAS5D"}