@blogic-cz/agent-tools 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -20
- package/package.json +13 -1
- package/schemas/agent-tools.schema.json +48 -1
- package/src/config/index.ts +2 -0
- package/src/config/loader.ts +17 -1
- package/src/config/types.ts +15 -0
- package/src/gh-tool/index.ts +2 -0
- package/src/gh-tool/pr/commands.ts +39 -0
- package/src/gh-tool/pr/core.ts +22 -0
- package/src/gh-tool/pr/index.ts +1 -0
- package/src/grafana-tool/alerts.ts +159 -0
- package/src/grafana-tool/dashboards.ts +151 -0
- package/src/grafana-tool/datasources.ts +72 -0
- package/src/grafana-tool/errors.ts +8 -0
- package/src/grafana-tool/health.ts +57 -0
- package/src/grafana-tool/index.ts +42 -0
- package/src/grafana-tool/logs.ts +124 -0
- package/src/grafana-tool/metrics.ts +217 -0
- package/src/grafana-tool/shared.ts +194 -0
- package/src/grafana-tool/types.ts +29 -0
- package/src/index.ts +9 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @blogic-cz/agent-tools
|
|
2
2
|
|
|
3
|
-
Safe CLI wrappers for AI coding agents.
|
|
3
|
+
Safe CLI wrappers for AI coding agents. 8 tools for GitHub, Grafana, databases, Kubernetes, Azure DevOps, logs, OpenCode sessions, and audit history — with JSON5 config and a credential guard that blocks agents from touching secrets.
|
|
4
4
|
|
|
5
5
|
## Why
|
|
6
6
|
|
|
@@ -33,6 +33,7 @@ Install per-tool skills for only the tools your project uses:
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
npx skills add blogic-cz/agent-tools --path skill/gh-tool
|
|
36
|
+
npx skills add blogic-cz/agent-tools --path skill/grafana-tool
|
|
36
37
|
npx skills add blogic-cz/agent-tools --path skill/db-tool
|
|
37
38
|
npx skills add blogic-cz/agent-tools --path skill/k8s-tool
|
|
38
39
|
npx skills add blogic-cz/agent-tools --path skill/az-tool
|
|
@@ -61,12 +62,13 @@ Follow these steps to set up agent-tools in the consumer project.
|
|
|
61
62
|
- **Non-JavaScript project** (no `package.json`): Skip installation — run tools directly with `bunx`:
|
|
62
63
|
```bash
|
|
63
64
|
bunx gh-tool pr status
|
|
65
|
+
bunx grafana-tool health
|
|
64
66
|
bunx k8s-tool pods --env test
|
|
65
67
|
```
|
|
66
68
|
|
|
67
69
|
**Step 2 — Configure**
|
|
68
70
|
|
|
69
|
-
Ask the user which tools they need (gh, db, k8s, az, logs, session, audit). Create `agent-tools.json5` in the project root with **only the selected tools configured**. See [`examples/agent-tools.json5`](./examples/agent-tools.json5) for the full config reference with all options documented.
|
|
71
|
+
Ask the user which tools they need (gh, grafana, db, k8s, az, logs, session, audit). Create `agent-tools.json5` in the project root with **only the selected tools configured**. See [`examples/agent-tools.json5`](./examples/agent-tools.json5) for the full config reference with all options documented.
|
|
70
72
|
|
|
71
73
|
Minimal starting config:
|
|
72
74
|
|
|
@@ -98,6 +100,7 @@ Install **only the per-tool skills the project needs** (recommended), or the all
|
|
|
98
100
|
```bash
|
|
99
101
|
# Per-tool skills (recommended) — install only what the project uses
|
|
100
102
|
npx skills add blogic-cz/agent-tools --path skill/gh-tool
|
|
103
|
+
npx skills add blogic-cz/agent-tools --path skill/grafana-tool
|
|
101
104
|
npx skills add blogic-cz/agent-tools --path skill/db-tool
|
|
102
105
|
npx skills add blogic-cz/agent-tools --path skill/k8s-tool
|
|
103
106
|
npx skills add blogic-cz/agent-tools --path skill/az-tool
|
|
@@ -115,6 +118,7 @@ Available per-tool skills:
|
|
|
115
118
|
| Skill | Install when project uses |
|
|
116
119
|
| -------------- | ------------------------------------------ |
|
|
117
120
|
| `gh-tool` | GitHub PRs, issues, workflows, CI checks |
|
|
121
|
+
| `grafana-tool` | Grafana dashboards, alerts, PromQL, LogQL |
|
|
118
122
|
| `db-tool` | SQL queries, schema introspection |
|
|
119
123
|
| `k8s-tool` | Kubernetes pods, logs, deployments |
|
|
120
124
|
| `az-tool` | Azure DevOps pipelines, builds |
|
|
@@ -181,6 +185,19 @@ bun run agent-tools/example-tool/index.ts ping
|
|
|
181
185
|
remotePath: "/app/logs",
|
|
182
186
|
},
|
|
183
187
|
},
|
|
188
|
+
grafana: {
|
|
189
|
+
// Profile name (selected via --profile, or used automatically when it's the only one)
|
|
190
|
+
default: {
|
|
191
|
+
// Environment name (selected via --env)
|
|
192
|
+
environments: {
|
|
193
|
+
local: {
|
|
194
|
+
url: "http://localhost:40300",
|
|
195
|
+
prometheusUid: "prometheus",
|
|
196
|
+
lokiUid: "loki",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
184
201
|
}
|
|
185
202
|
```
|
|
186
203
|
|
|
@@ -188,6 +205,7 @@ bun run agent-tools/example-tool/index.ts ping
|
|
|
188
205
|
|
|
189
206
|
```bash
|
|
190
207
|
bun gh-tool pr status
|
|
208
|
+
bun grafana-tool dashboards list --env local
|
|
191
209
|
bun k8s-tool kubectl --env test --cmd "get pods"
|
|
192
210
|
bun logs-tool list --env local
|
|
193
211
|
bun audit-tool list --limit 20
|
|
@@ -211,6 +229,7 @@ export default { handleToolExecuteBefore };
|
|
|
211
229
|
| Binary | Description |
|
|
212
230
|
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
213
231
|
| `gh-tool` | GitHub CLI wrapper — PR management, issues, workflows, composite commands (`review-triage`, `reply-and-resolve`) |
|
|
232
|
+
| `grafana-tool` | Grafana API wrapper — health, dashboards, datasources, alerts, PromQL, and LogQL queries |
|
|
214
233
|
| `audit-tool` | Audit trail browser — inspect recent tool invocations and purge old entries |
|
|
215
234
|
| `db-tool` | Database query tool — SQL execution, schema introspection |
|
|
216
235
|
| `k8s-tool` | Kubernetes tool — kubectl wrapper + structured commands (`pods`, `logs`, `describe`, `exec`, `top`) |
|
|
@@ -228,7 +247,7 @@ Every tool invocation is automatically recorded to a local SQLite database — z
|
|
|
228
247
|
|
|
229
248
|
### How it works
|
|
230
249
|
|
|
231
|
-
Each CLI wrapper (`gh`, `k8s`, `db`, `az`, `logs`, `session`, `audit`) writes a row to `~/.agent-tools/audit.sqlite` on every execution. Logging is fire-and-forget — if the database is unavailable or write fails, the tool continues normally. Audit never blocks or slows down your workflow.
|
|
250
|
+
Each CLI wrapper (`gh`, `grafana`, `k8s`, `db`, `az`, `logs`, `session`, `audit`) writes a row to `~/.agent-tools/audit.sqlite` on every execution. Logging is fire-and-forget — if the database is unavailable or write fails, the tool continues normally. Audit never blocks or slows down your workflow.
|
|
232
251
|
|
|
233
252
|
Entries older than `retentionDays` (default: 90) are automatically purged on each write.
|
|
234
253
|
|
|
@@ -268,16 +287,16 @@ All settings are optional — audit works out of the box with sensible defaults.
|
|
|
268
287
|
|
|
269
288
|
### What gets recorded
|
|
270
289
|
|
|
271
|
-
| Column | Description
|
|
272
|
-
| ----------- |
|
|
273
|
-
| `ts` | ISO 8601 timestamp
|
|
274
|
-
| `tool` | Tool name (`gh`, `k8s`, `db`, `az`, `logs`, `session`) |
|
|
275
|
-
| `project` | Working directory (`process.cwd()`)
|
|
276
|
-
| `args` | Command-line arguments (JSON array)
|
|
277
|
-
| `duration` | Execution time in milliseconds
|
|
278
|
-
| `success` | `1` (success) or `0` (failure)
|
|
279
|
-
| `error` | Error message if failed, `null` otherwise
|
|
280
|
-
| `exit_code` | Process exit code
|
|
290
|
+
| Column | Description |
|
|
291
|
+
| ----------- | ----------------------------------------------------------------- |
|
|
292
|
+
| `ts` | ISO 8601 timestamp |
|
|
293
|
+
| `tool` | Tool name (`gh`, `grafana`, `k8s`, `db`, `az`, `logs`, `session`) |
|
|
294
|
+
| `project` | Working directory (`process.cwd()`) |
|
|
295
|
+
| `args` | Command-line arguments (JSON array) |
|
|
296
|
+
| `duration` | Execution time in milliseconds |
|
|
297
|
+
| `success` | `1` (success) or `0` (failure) |
|
|
298
|
+
| `error` | Error message if failed, `null` otherwise |
|
|
299
|
+
| `exit_code` | Process exit code |
|
|
281
300
|
|
|
282
301
|
## Configuration
|
|
283
302
|
|
|
@@ -331,13 +350,14 @@ See [`examples/agent-tools.json5`](./examples/agent-tools.json5) for a complete
|
|
|
331
350
|
|
|
332
351
|
Each tool uses its own auth method — no unified token store:
|
|
333
352
|
|
|
334
|
-
| Tool
|
|
335
|
-
|
|
|
336
|
-
| `gh-tool`
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
353
|
+
| Tool | Auth Method |
|
|
354
|
+
| -------------- | -------------------------------------------------------------------------------------------- |
|
|
355
|
+
| `gh-tool` | `gh` CLI session (`gh auth login`) or `GITHUB_TOKEN` env var |
|
|
356
|
+
| `grafana-tool` | Grafana URL from config plus optional token from `tokenEnvVar` |
|
|
357
|
+
| `k8s-tool` | Existing kubectl context (kubeconfig). Cluster ID from config resolves context automatically |
|
|
358
|
+
| `az-tool` | `az` CLI session (`az login`) |
|
|
359
|
+
| `db-tool` | Password from env var defined by `passwordEnvVar` in config (e.g. `AGENT_TOOLS_DB_PASSWORD`) |
|
|
360
|
+
| `logs-tool` | No auth — reads local files or uses k8s-tool for remote access |
|
|
341
361
|
|
|
342
362
|
Secrets are **never** stored in the config file. The `db-tool` config references env var **names** only:
|
|
343
363
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blogic-cz/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, sessions, and audit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"database",
|
|
10
10
|
"devops",
|
|
11
11
|
"github",
|
|
12
|
+
"grafana",
|
|
12
13
|
"kubernetes",
|
|
13
14
|
"tools"
|
|
14
15
|
],
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"agent-tools-az": "./src/az-tool/index.ts",
|
|
24
25
|
"agent-tools-db": "./src/db-tool/index.ts",
|
|
25
26
|
"agent-tools-gh": "./src/gh-tool/index.ts",
|
|
27
|
+
"agent-tools-grafana": "./src/grafana-tool/index.ts",
|
|
26
28
|
"agent-tools-k8s": "./src/k8s-tool/index.ts",
|
|
27
29
|
"agent-tools-logs": "./src/logs-tool/index.ts",
|
|
28
30
|
"agent-tools-session": "./src/session-tool/index.ts",
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
"az-tool": "./src/az-tool/index.ts",
|
|
31
33
|
"db-tool": "./src/db-tool/index.ts",
|
|
32
34
|
"gh-tool": "./src/gh-tool/index.ts",
|
|
35
|
+
"grafana-tool": "./src/grafana-tool/index.ts",
|
|
33
36
|
"k8s-tool": "./src/k8s-tool/index.ts",
|
|
34
37
|
"logs-tool": "./src/logs-tool/index.ts",
|
|
35
38
|
"session-tool": "./src/session-tool/index.ts"
|
|
@@ -72,6 +75,10 @@
|
|
|
72
75
|
"types": "./dist/db-tool/*.d.ts",
|
|
73
76
|
"default": "./src/db-tool/*.ts"
|
|
74
77
|
},
|
|
78
|
+
"#grafana/*": {
|
|
79
|
+
"types": "./dist/grafana-tool/*.d.ts",
|
|
80
|
+
"default": "./src/grafana-tool/*.ts"
|
|
81
|
+
},
|
|
75
82
|
"#logs/*": {
|
|
76
83
|
"types": "./dist/logs-tool/*.d.ts",
|
|
77
84
|
"default": "./src/logs-tool/*.ts"
|
|
@@ -101,6 +108,10 @@
|
|
|
101
108
|
"./config": {
|
|
102
109
|
"types": "./dist/config/index.d.ts",
|
|
103
110
|
"default": "./src/config/index.ts"
|
|
111
|
+
},
|
|
112
|
+
"./audit": {
|
|
113
|
+
"types": "./dist/shared/audit.d.ts",
|
|
114
|
+
"default": "./src/shared/audit.ts"
|
|
104
115
|
}
|
|
105
116
|
},
|
|
106
117
|
"publishConfig": {
|
|
@@ -112,6 +123,7 @@
|
|
|
112
123
|
"check:ci": "bun check.ts ci",
|
|
113
124
|
"format": "oxfmt",
|
|
114
125
|
"format:check": "oxfmt --check",
|
|
126
|
+
"grafana-tool": "bun src/grafana-tool/index.ts",
|
|
115
127
|
"gh-tool": "bun src/gh-tool/index.ts",
|
|
116
128
|
"lint": "oxlint -c ./.oxlintrc.json --deny-warnings",
|
|
117
129
|
"lint:fix": "oxlint -c ./.oxlintrc.json --fix",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/blogic-cz/agent-tools/main/schemas/agent-tools.schema.json",
|
|
4
4
|
"title": "Agent Tools Configuration",
|
|
5
|
-
"description": "Root configuration for the agent-tools package. Tool-specific sections (azure, kubernetes, database, logs) are maps of named profiles keyed by profile name. Tools choose a profile via --profile <name> (default key is 'default', single-entry maps can be auto-selected). session, audit, and credentialGuard are global sections.",
|
|
5
|
+
"description": "Root configuration for the agent-tools package. Tool-specific sections (azure, kubernetes, database, grafana, logs) are maps of named profiles keyed by profile name. Tools choose a profile via --profile <name> (default key is 'default', single-entry maps can be auto-selected). session, audit, and credentialGuard are global sections.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": true,
|
|
8
8
|
"properties": {
|
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
"$ref": "#/definitions/DatabaseConfig"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
+
"grafana": {
|
|
38
|
+
"description": "Named Grafana profiles as Record<string, GrafanaConfig>.",
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": {
|
|
41
|
+
"description": "Grafana profile configuration containing one or more environments.",
|
|
42
|
+
"$ref": "#/definitions/GrafanaConfig"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
37
45
|
"logs": {
|
|
38
46
|
"description": "Named logs profiles as Record<string, LogsConfig>.",
|
|
39
47
|
"type": "object",
|
|
@@ -192,6 +200,45 @@
|
|
|
192
200
|
},
|
|
193
201
|
"required": ["environments"]
|
|
194
202
|
},
|
|
203
|
+
"GrafanaEnvTarget": {
|
|
204
|
+
"description": "Single Grafana environment target.",
|
|
205
|
+
"type": "object",
|
|
206
|
+
"additionalProperties": false,
|
|
207
|
+
"properties": {
|
|
208
|
+
"url": {
|
|
209
|
+
"description": "Base Grafana URL.",
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
212
|
+
"tokenEnvVar": {
|
|
213
|
+
"description": "Environment variable containing a Grafana API token.",
|
|
214
|
+
"type": "string"
|
|
215
|
+
},
|
|
216
|
+
"prometheusUid": {
|
|
217
|
+
"description": "Grafana datasource UID for Prometheus queries.",
|
|
218
|
+
"type": "string"
|
|
219
|
+
},
|
|
220
|
+
"lokiUid": {
|
|
221
|
+
"description": "Grafana datasource UID for Loki queries.",
|
|
222
|
+
"type": "string"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"required": ["url"]
|
|
226
|
+
},
|
|
227
|
+
"GrafanaConfig": {
|
|
228
|
+
"description": "Grafana profile configuration.",
|
|
229
|
+
"type": "object",
|
|
230
|
+
"additionalProperties": false,
|
|
231
|
+
"properties": {
|
|
232
|
+
"environments": {
|
|
233
|
+
"description": "Named Grafana environments as Record<string, GrafanaEnvTarget>.",
|
|
234
|
+
"type": "object",
|
|
235
|
+
"additionalProperties": {
|
|
236
|
+
"$ref": "#/definitions/GrafanaEnvTarget"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"required": ["environments"]
|
|
241
|
+
},
|
|
195
242
|
"LogsConfig": {
|
|
196
243
|
"description": "Logs profile configuration.",
|
|
197
244
|
"type": "object",
|
package/src/config/index.ts
CHANGED
package/src/config/loader.ts
CHANGED
|
@@ -54,6 +54,17 @@ const LogsConfigSchema = Schema.Struct({
|
|
|
54
54
|
remotePath: Schema.String,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
const GrafanaEnvTargetSchema = Schema.Struct({
|
|
58
|
+
url: Schema.String,
|
|
59
|
+
tokenEnvVar: Schema.optionalKey(Schema.String),
|
|
60
|
+
prometheusUid: Schema.optionalKey(Schema.String),
|
|
61
|
+
lokiUid: Schema.optionalKey(Schema.String),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const GrafanaConfigSchema = Schema.Struct({
|
|
65
|
+
environments: Schema.Record(Schema.String, GrafanaEnvTargetSchema),
|
|
66
|
+
});
|
|
67
|
+
|
|
57
68
|
const AuditConfigSchema = Schema.Struct({
|
|
58
69
|
retentionDays: Schema.optionalKey(Schema.Number),
|
|
59
70
|
dbPath: Schema.optionalKey(Schema.String),
|
|
@@ -69,6 +80,7 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
69
80
|
"azure",
|
|
70
81
|
"kubernetes",
|
|
71
82
|
"database",
|
|
83
|
+
"grafana",
|
|
72
84
|
"logs",
|
|
73
85
|
"session",
|
|
74
86
|
"audit",
|
|
@@ -82,6 +94,7 @@ const AgentToolsConfigSchema = Schema.Struct({
|
|
|
82
94
|
azure: Schema.optionalKey(Schema.Record(Schema.String, AzureConfigSchema)),
|
|
83
95
|
kubernetes: Schema.optionalKey(Schema.Record(Schema.String, K8sConfigSchema)),
|
|
84
96
|
database: Schema.optionalKey(Schema.Record(Schema.String, DatabaseConfigSchema)),
|
|
97
|
+
grafana: Schema.optionalKey(Schema.Record(Schema.String, GrafanaConfigSchema)),
|
|
85
98
|
logs: Schema.optionalKey(Schema.Record(Schema.String, LogsConfigSchema)),
|
|
86
99
|
session: Schema.optionalKey(
|
|
87
100
|
Schema.Struct({
|
|
@@ -175,7 +188,10 @@ export const ConfigServiceLayer = Layer.effect(
|
|
|
175
188
|
}),
|
|
176
189
|
);
|
|
177
190
|
|
|
178
|
-
type ProfiledSection = keyof Pick<
|
|
191
|
+
type ProfiledSection = keyof Pick<
|
|
192
|
+
AgentToolsConfig,
|
|
193
|
+
"azure" | "kubernetes" | "database" | "grafana" | "logs"
|
|
194
|
+
>;
|
|
179
195
|
|
|
180
196
|
export function getToolConfig<T>(
|
|
181
197
|
config: AgentToolsConfig | undefined,
|
package/src/config/types.ts
CHANGED
|
@@ -43,6 +43,19 @@ export type LogsConfig = {
|
|
|
43
43
|
remotePath: string;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
/** Single Grafana environment connection details */
|
|
47
|
+
export type GrafanaEnvTarget = {
|
|
48
|
+
url: string;
|
|
49
|
+
tokenEnvVar?: string;
|
|
50
|
+
prometheusUid?: string;
|
|
51
|
+
lokiUid?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Grafana profile configuration */
|
|
55
|
+
export type GrafanaConfig = {
|
|
56
|
+
environments: Record<string, GrafanaEnvTarget>;
|
|
57
|
+
};
|
|
58
|
+
|
|
46
59
|
export type CliToolOverride = {
|
|
47
60
|
tool: string;
|
|
48
61
|
suggestion: string;
|
|
@@ -86,6 +99,8 @@ export type AgentToolsConfig = {
|
|
|
86
99
|
database?: Record<string, DatabaseConfig>;
|
|
87
100
|
/** Named logs profiles. e.g. { default: { localDir: "...", remotePath: "..." } } */
|
|
88
101
|
logs?: Record<string, LogsConfig>;
|
|
102
|
+
/** Named Grafana profiles. e.g. { default: { environments: { local: {...}, prod: {...} } } } */
|
|
103
|
+
grafana?: Record<string, GrafanaConfig>;
|
|
89
104
|
/** Global session config (not per-profile) */
|
|
90
105
|
session?: {
|
|
91
106
|
storagePath: string;
|
package/src/gh-tool/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
prViewCommand,
|
|
21
21
|
prStatusCommand,
|
|
22
22
|
prCreateCommand,
|
|
23
|
+
prCloseCommand,
|
|
23
24
|
prEditCommand,
|
|
24
25
|
prMergeCommand,
|
|
25
26
|
prThreadsCommand,
|
|
@@ -66,6 +67,7 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
66
67
|
prViewCommand,
|
|
67
68
|
prStatusCommand,
|
|
68
69
|
prCreateCommand,
|
|
70
|
+
prCloseCommand,
|
|
69
71
|
prEditCommand,
|
|
70
72
|
prMergeCommand,
|
|
71
73
|
prThreadsCommand,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "#gh/config";
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
|
+
closePR,
|
|
20
21
|
createPR,
|
|
21
22
|
detectPRStatus,
|
|
22
23
|
editPR,
|
|
@@ -147,6 +148,44 @@ export const prEditCommand = Command.make(
|
|
|
147
148
|
}),
|
|
148
149
|
).pipe(Command.withDescription("Edit an existing PR's title, body, or other metadata"));
|
|
149
150
|
|
|
151
|
+
export const prCloseCommand = Command.make(
|
|
152
|
+
"close",
|
|
153
|
+
{
|
|
154
|
+
comment: Flag.string("comment").pipe(
|
|
155
|
+
Flag.withDescription("Comment to add when closing"),
|
|
156
|
+
Flag.optional,
|
|
157
|
+
),
|
|
158
|
+
commentFile: Flag.string("comment-file").pipe(
|
|
159
|
+
Flag.withDescription("Read close comment from a file path or '-' for stdin"),
|
|
160
|
+
Flag.optional,
|
|
161
|
+
),
|
|
162
|
+
deleteBranch: Flag.boolean("delete-branch").pipe(
|
|
163
|
+
Flag.withDescription("Delete the branch after closing"),
|
|
164
|
+
Flag.withDefault(false),
|
|
165
|
+
),
|
|
166
|
+
format: formatOption,
|
|
167
|
+
pr: Flag.integer("pr").pipe(Flag.withDescription("PR number to close")),
|
|
168
|
+
},
|
|
169
|
+
({ comment, commentFile, deleteBranch, format, pr }) =>
|
|
170
|
+
Effect.gen(function* () {
|
|
171
|
+
const resolvedComment = yield* resolveOptionalTextInput(
|
|
172
|
+
"gh-tool pr close",
|
|
173
|
+
Option.getOrNull(comment),
|
|
174
|
+
Option.getOrNull(commentFile),
|
|
175
|
+
"--comment",
|
|
176
|
+
"--comment-file",
|
|
177
|
+
"comment",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const result = yield* closePR({
|
|
181
|
+
comment: resolvedComment,
|
|
182
|
+
deleteBranch,
|
|
183
|
+
pr,
|
|
184
|
+
});
|
|
185
|
+
yield* logFormatted(result, format);
|
|
186
|
+
}),
|
|
187
|
+
).pipe(Command.withDescription("Close a PR with optional comment and branch deletion"));
|
|
188
|
+
|
|
150
189
|
export const prMergeCommand = Command.make(
|
|
151
190
|
"merge",
|
|
152
191
|
{
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -606,6 +606,28 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
|
606
606
|
return result;
|
|
607
607
|
});
|
|
608
608
|
|
|
609
|
+
export const closePR = Effect.fn("pr.closePR")(function* (opts: {
|
|
610
|
+
pr: number;
|
|
611
|
+
comment: string | null;
|
|
612
|
+
deleteBranch: boolean;
|
|
613
|
+
}) {
|
|
614
|
+
const gh = yield* GitHubService;
|
|
615
|
+
|
|
616
|
+
const args = ["pr", "close", String(opts.pr)];
|
|
617
|
+
|
|
618
|
+
if (opts.comment !== null) {
|
|
619
|
+
args.push("--comment", opts.comment);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (opts.deleteBranch) {
|
|
623
|
+
args.push("--delete-branch");
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
yield* gh.runGh(args);
|
|
627
|
+
|
|
628
|
+
return yield* viewPR(opts.pr);
|
|
629
|
+
});
|
|
630
|
+
|
|
609
631
|
export const editPR = Effect.fn("pr.editPR")(function* (opts: {
|
|
610
632
|
pr: number;
|
|
611
633
|
title: string | null;
|
package/src/gh-tool/pr/index.ts
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Console, Effect } from "effect";
|
|
2
|
+
import { Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
|
|
4
|
+
import { formatOption, formatOutput } from "#shared";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
envOption,
|
|
8
|
+
formatGrafanaError,
|
|
9
|
+
grafanaFetch,
|
|
10
|
+
profileOption,
|
|
11
|
+
resolveConfig,
|
|
12
|
+
} from "./shared";
|
|
13
|
+
|
|
14
|
+
type AlertRulesResponse = Record<
|
|
15
|
+
string,
|
|
16
|
+
Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
rules: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
state: string;
|
|
21
|
+
health: string;
|
|
22
|
+
lastEvaluation?: string;
|
|
23
|
+
evaluationTime?: number;
|
|
24
|
+
}>;
|
|
25
|
+
}>
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
type AlertInstance = {
|
|
29
|
+
labels: Record<string, string>;
|
|
30
|
+
annotations: Record<string, string>;
|
|
31
|
+
state: string;
|
|
32
|
+
activeAt?: string;
|
|
33
|
+
value?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const listCommand = Command.make(
|
|
37
|
+
"list",
|
|
38
|
+
{ format: formatOption, env: envOption, profile: profileOption },
|
|
39
|
+
({ format, env, profile }) => {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
|
|
42
|
+
return Effect.gen(function* () {
|
|
43
|
+
const config = yield* resolveConfig(env, profile);
|
|
44
|
+
const data = yield* grafanaFetch<AlertRulesResponse>(
|
|
45
|
+
config,
|
|
46
|
+
"/api/ruler/grafana/api/v1/rules",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const rules = Object.entries(data).flatMap(([namespace, groups]) =>
|
|
50
|
+
groups.flatMap((group) =>
|
|
51
|
+
group.rules.map((rule) => ({
|
|
52
|
+
name: rule.name,
|
|
53
|
+
state: rule.state,
|
|
54
|
+
health: rule.health,
|
|
55
|
+
namespace,
|
|
56
|
+
group: group.name,
|
|
57
|
+
lastEvaluation: rule.lastEvaluation,
|
|
58
|
+
evaluationTime: rule.evaluationTime,
|
|
59
|
+
})),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const result = {
|
|
64
|
+
success: true,
|
|
65
|
+
message: `Found ${rules.length} alert rule(s)`,
|
|
66
|
+
data: { rules, count: rules.length },
|
|
67
|
+
executionTimeMs: Date.now() - start,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
yield* Console.log(formatOutput(result, format));
|
|
71
|
+
}).pipe(
|
|
72
|
+
Effect.catch((error) =>
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const result = {
|
|
75
|
+
success: false,
|
|
76
|
+
message: "Failed to list alert rules",
|
|
77
|
+
error: formatGrafanaError(error),
|
|
78
|
+
hint: "Check Grafana is running and accessible",
|
|
79
|
+
executionTimeMs: Date.now() - start,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
yield* Console.log(formatOutput(result, format));
|
|
83
|
+
}),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
).pipe(Command.withDescription("List all alert rules"));
|
|
88
|
+
|
|
89
|
+
const statusCommand = Command.make(
|
|
90
|
+
"status",
|
|
91
|
+
{
|
|
92
|
+
format: formatOption,
|
|
93
|
+
env: envOption,
|
|
94
|
+
profile: profileOption,
|
|
95
|
+
all: Flag.boolean("all").pipe(
|
|
96
|
+
Flag.withDescription("Show all alerts including normal state"),
|
|
97
|
+
Flag.withDefault(false),
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
({ format, env, profile, all }) => {
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
|
|
103
|
+
return Effect.gen(function* () {
|
|
104
|
+
const config = yield* resolveConfig(env, profile);
|
|
105
|
+
const alerts = yield* grafanaFetch<AlertInstance[]>(
|
|
106
|
+
config,
|
|
107
|
+
"/api/alertmanager/grafana/api/v2/alerts",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const filtered = all
|
|
111
|
+
? alerts
|
|
112
|
+
: alerts.filter((alert) => alert.state === "firing" || alert.state === "pending");
|
|
113
|
+
const firingCount = filtered.filter((alert) => alert.state === "firing").length;
|
|
114
|
+
const pendingCount = filtered.filter((alert) => alert.state === "pending").length;
|
|
115
|
+
|
|
116
|
+
const result = {
|
|
117
|
+
success: true,
|
|
118
|
+
message:
|
|
119
|
+
firingCount === 0 && pendingCount === 0
|
|
120
|
+
? "No active alerts"
|
|
121
|
+
: `${firingCount} firing, ${pendingCount} pending alert(s)`,
|
|
122
|
+
data: {
|
|
123
|
+
alerts: filtered.map((alert) => ({
|
|
124
|
+
state: alert.state,
|
|
125
|
+
labels: alert.labels,
|
|
126
|
+
annotations: alert.annotations,
|
|
127
|
+
activeAt: alert.activeAt,
|
|
128
|
+
value: alert.value,
|
|
129
|
+
})),
|
|
130
|
+
firingCount,
|
|
131
|
+
pendingCount,
|
|
132
|
+
totalCount: filtered.length,
|
|
133
|
+
},
|
|
134
|
+
executionTimeMs: Date.now() - start,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
yield* Console.log(formatOutput(result, format));
|
|
138
|
+
}).pipe(
|
|
139
|
+
Effect.catch((error) =>
|
|
140
|
+
Effect.gen(function* () {
|
|
141
|
+
const result = {
|
|
142
|
+
success: false,
|
|
143
|
+
message: "Failed to get alert status",
|
|
144
|
+
error: formatGrafanaError(error),
|
|
145
|
+
hint: "Check Grafana is running and accessible",
|
|
146
|
+
executionTimeMs: Date.now() - start,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
yield* Console.log(formatOutput(result, format));
|
|
150
|
+
}),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
).pipe(Command.withDescription("Show firing and pending alerts"));
|
|
155
|
+
|
|
156
|
+
export const alertsCommand = Command.make("alerts", {}).pipe(
|
|
157
|
+
Command.withDescription("Alert operations"),
|
|
158
|
+
Command.withSubcommands([listCommand, statusCommand]),
|
|
159
|
+
);
|