@blogic-cz/agent-tools 0.11.0 → 0.12.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.
@@ -1,217 +0,0 @@
1
- import { Console, Effect } from "effect";
2
- import { Argument, Command, Flag } from "effect/unstable/cli";
3
-
4
- import { formatOption, formatOutput } from "#shared";
5
-
6
- import { GrafanaToolError } from "./errors";
7
- import {
8
- envOption,
9
- formatGrafanaError,
10
- grafanaDsQuery,
11
- profileOption,
12
- resolveConfig,
13
- } from "./shared";
14
- import type { DsQueryResponse } from "./types";
15
-
16
- function parsePrometheusFrames(frames: DsQueryResponse["results"]["A"]["frames"]) {
17
- if (!frames || frames.length === 0) {
18
- return [] as Array<{ metric: Record<string, string>; value: number; timestamp: number }>;
19
- }
20
-
21
- const results: Array<{ metric: Record<string, string>; value: number; timestamp: number }> = [];
22
-
23
- for (const frame of frames) {
24
- const fields = frame.schema.fields;
25
- const values = frame.data.values;
26
- const timeIndex = fields.findIndex((field) => field.type === "time");
27
- const valueIndex = fields.findIndex((field) => field.type === "number");
28
-
29
- if (timeIndex < 0 || valueIndex < 0) {
30
- continue;
31
- }
32
-
33
- const labelFields = fields.filter((field) => field.type === "string");
34
- const timestamps = values[timeIndex] as number[];
35
- const seriesValues = values[valueIndex] as number[];
36
-
37
- for (const [index, timestamp] of timestamps.entries()) {
38
- const metric: Record<string, string> = {};
39
- for (const labelField of labelFields) {
40
- const labelIndex = fields.indexOf(labelField);
41
- const labels = values[labelIndex] as string[];
42
- if (labels[index]) {
43
- metric[labelField.name] = labels[index];
44
- }
45
- }
46
-
47
- results.push({
48
- metric,
49
- value: seriesValues[index] ?? 0,
50
- timestamp,
51
- });
52
- }
53
- }
54
-
55
- return results;
56
- }
57
-
58
- function extractLabelsFromFrame(
59
- frame: NonNullable<DsQueryResponse["results"]["A"]["frames"]>[number],
60
- ) {
61
- const labels: Record<string, string> = {};
62
- for (const [index, field] of frame.schema.fields.entries()) {
63
- if (field.type !== "string") {
64
- continue;
65
- }
66
-
67
- const values = frame.data.values[index] as string[];
68
- if (values[0]) {
69
- labels[field.name] = values[0];
70
- }
71
- }
72
- return labels;
73
- }
74
-
75
- const queryCommand = Command.make(
76
- "query",
77
- {
78
- promql: Argument.string("promql"),
79
- format: formatOption,
80
- env: envOption,
81
- profile: profileOption,
82
- },
83
- ({ promql, format, env, profile }) => {
84
- const startedAt = Date.now();
85
-
86
- return Effect.gen(function* () {
87
- const config = yield* resolveConfig(env, profile);
88
- const response = yield* grafanaDsQuery(config, config.prometheusUid, "prometheus", promql, {
89
- instant: true,
90
- maxDataPoints: 1,
91
- });
92
-
93
- if (response.results.A.error) {
94
- return yield* new GrafanaToolError({
95
- cause: new Error(response.results.A.error),
96
- });
97
- }
98
-
99
- const parsed = parsePrometheusFrames(response.results.A.frames);
100
- const result = {
101
- success: true,
102
- message: `PromQL query returned ${parsed.length} result(s)`,
103
- data: {
104
- results: parsed,
105
- query: promql,
106
- resultCount: parsed.length,
107
- },
108
- executionTimeMs: Date.now() - startedAt,
109
- };
110
-
111
- yield* Console.log(formatOutput(result, format));
112
- }).pipe(
113
- Effect.catch((error) =>
114
- Effect.gen(function* () {
115
- const result = {
116
- success: false,
117
- message: "Failed to execute PromQL query",
118
- error: formatGrafanaError(error),
119
- hint: "Check PromQL syntax and Grafana/Prometheus connectivity",
120
- executionTimeMs: Date.now() - startedAt,
121
- };
122
- yield* Console.log(formatOutput(result, format));
123
- }),
124
- ),
125
- );
126
- },
127
- ).pipe(Command.withDescription("Execute instant PromQL query via Grafana"));
128
-
129
- const rangeCommand = Command.make(
130
- "range",
131
- {
132
- promql: Argument.string("promql"),
133
- format: formatOption,
134
- env: envOption,
135
- profile: profileOption,
136
- start: Flag.string("start").pipe(
137
- Flag.withDescription("Start time (ISO 8601 or relative, e.g. now-1h)"),
138
- Flag.withDefault("now-1h"),
139
- ),
140
- end: Flag.string("end").pipe(
141
- Flag.withDescription("End time (ISO 8601 or relative, e.g. now)"),
142
- Flag.withDefault("now"),
143
- ),
144
- step: Flag.integer("step").pipe(
145
- Flag.withDescription("Step in seconds (default: 60)"),
146
- Flag.withDefault(60),
147
- ),
148
- },
149
- ({ promql, format, env, profile, start, end, step }) => {
150
- const startedAt = Date.now();
151
-
152
- return Effect.gen(function* () {
153
- const config = yield* resolveConfig(env, profile);
154
- const response = yield* grafanaDsQuery(config, config.prometheusUid, "prometheus", promql, {
155
- instant: false,
156
- from: start,
157
- to: end,
158
- step,
159
- });
160
-
161
- if (response.results.A.error) {
162
- return yield* new GrafanaToolError({
163
- cause: new Error(response.results.A.error),
164
- });
165
- }
166
-
167
- const series = (response.results.A.frames ?? []).map((frame) => {
168
- const timeIndex = frame.schema.fields.findIndex((field) => field.type === "time");
169
- const valueIndex = frame.schema.fields.findIndex((field) => field.type === "number");
170
- const timestamps = (timeIndex >= 0 ? frame.data.values[timeIndex] : []) as number[];
171
- const values = (valueIndex >= 0 ? frame.data.values[valueIndex] : []) as number[];
172
-
173
- return {
174
- labels: extractLabelsFromFrame(frame),
175
- values: timestamps.map((timestamp, index) => ({
176
- timestamp,
177
- value: values[index] ?? 0,
178
- })),
179
- };
180
- });
181
-
182
- const result = {
183
- success: true,
184
- message: `PromQL range query returned ${series.length} series`,
185
- data: {
186
- series,
187
- query: promql,
188
- start,
189
- end,
190
- step,
191
- seriesCount: series.length,
192
- },
193
- executionTimeMs: Date.now() - startedAt,
194
- };
195
-
196
- yield* Console.log(formatOutput(result, format));
197
- }).pipe(
198
- Effect.catch((error) =>
199
- Effect.gen(function* () {
200
- const result = {
201
- success: false,
202
- message: "Failed to execute PromQL range query",
203
- error: formatGrafanaError(error),
204
- hint: "Check PromQL syntax and Grafana/Prometheus connectivity",
205
- executionTimeMs: Date.now() - startedAt,
206
- };
207
- yield* Console.log(formatOutput(result, format));
208
- }),
209
- ),
210
- );
211
- },
212
- ).pipe(Command.withDescription("Execute range PromQL query via Grafana"));
213
-
214
- export const metricsCommand = Command.make("metrics", {}).pipe(
215
- Command.withDescription("Prometheus metric operations via Grafana"),
216
- Command.withSubcommands([queryCommand, rangeCommand]),
217
- );
@@ -1,29 +0,0 @@
1
- export type GrafanaEnvConfig = {
2
- url: string;
3
- token?: string;
4
- prometheusUid: string;
5
- lokiUid: string;
6
- };
7
-
8
- export type DsQueryOpts = {
9
- instant?: boolean;
10
- from?: string;
11
- to?: string;
12
- maxLines?: number;
13
- intervalMs?: number;
14
- maxDataPoints?: number;
15
- step?: number;
16
- };
17
-
18
- export type DsQueryResponse = {
19
- results: {
20
- A: {
21
- status?: number;
22
- frames?: Array<{
23
- schema: { fields: Array<{ name: string; type: string }> };
24
- data: { values: unknown[][] };
25
- }>;
26
- error?: string;
27
- };
28
- };
29
- };