@glassmkr/crucible 0.7.1 → 0.8.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.
Files changed (103) hide show
  1. package/dist/alerts/__tests__/rules.test.d.ts +1 -0
  2. package/dist/alerts/__tests__/rules.test.js +325 -0
  3. package/dist/alerts/__tests__/rules.test.js.map +1 -0
  4. package/dist/alerts/rules.d.ts +8 -0
  5. package/dist/alerts/rules.js +139 -32
  6. package/dist/alerts/rules.js.map +1 -1
  7. package/dist/api.d.ts +2 -0
  8. package/dist/api.js +7 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/collect/__tests__/dmi.test.d.ts +1 -0
  11. package/dist/collect/__tests__/dmi.test.js +114 -0
  12. package/dist/collect/__tests__/dmi.test.js.map +1 -0
  13. package/dist/collect/__tests__/ipmi.test.js +47 -1
  14. package/dist/collect/__tests__/ipmi.test.js.map +1 -1
  15. package/dist/collect/__tests__/thermal.test.d.ts +1 -0
  16. package/dist/collect/__tests__/thermal.test.js +164 -0
  17. package/dist/collect/__tests__/thermal.test.js.map +1 -0
  18. package/dist/collect/dmi.d.ts +19 -0
  19. package/dist/collect/dmi.js +109 -0
  20. package/dist/collect/dmi.js.map +1 -0
  21. package/dist/collect/ipmi.d.ts +27 -2
  22. package/dist/collect/ipmi.js +90 -2
  23. package/dist/collect/ipmi.js.map +1 -1
  24. package/dist/collect/thermal.d.ts +10 -0
  25. package/dist/collect/thermal.js +187 -0
  26. package/dist/collect/thermal.js.map +1 -0
  27. package/dist/config.d.ts +10 -0
  28. package/dist/config.js +2 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/index.js +51 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/lib/__tests__/capability.test.d.ts +1 -0
  33. package/dist/lib/__tests__/capability.test.js +87 -0
  34. package/dist/lib/__tests__/capability.test.js.map +1 -0
  35. package/dist/lib/__tests__/vendor-sensors.test.d.ts +1 -0
  36. package/dist/lib/__tests__/vendor-sensors.test.js +49 -0
  37. package/dist/lib/__tests__/vendor-sensors.test.js.map +1 -0
  38. package/dist/lib/capability.d.ts +21 -0
  39. package/dist/lib/capability.js +110 -0
  40. package/dist/lib/capability.js.map +1 -0
  41. package/dist/lib/cpu-thermal-chips.d.ts +2 -0
  42. package/dist/lib/cpu-thermal-chips.js +28 -0
  43. package/dist/lib/cpu-thermal-chips.js.map +1 -0
  44. package/dist/lib/types.d.ts +58 -0
  45. package/dist/lib/vendor-sensors.d.ts +27 -0
  46. package/dist/lib/vendor-sensors.js +63 -0
  47. package/dist/lib/vendor-sensors.js.map +1 -0
  48. package/dist/notify/telegram.js +1 -1
  49. package/dist/notify/telegram.js.map +1 -1
  50. package/package.json +16 -1
  51. package/rule-ids.json +29 -0
  52. package/.dockerignore +0 -13
  53. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
  54. package/.github/ISSUE_TEMPLATE/no_data.md +0 -26
  55. package/.github/workflows/docker.yml +0 -53
  56. package/.github/workflows/publish.yml +0 -25
  57. package/Dockerfile +0 -59
  58. package/config/collector.example.yaml +0 -43
  59. package/docker-compose.yml +0 -26
  60. package/scripts/sign-release.sh +0 -29
  61. package/src/__tests__/cli.test.ts +0 -74
  62. package/src/__tests__/reboot-marker.test.ts +0 -122
  63. package/src/alerts/evaluator.ts +0 -15
  64. package/src/alerts/rules.ts +0 -283
  65. package/src/alerts/state.ts +0 -92
  66. package/src/cli.ts +0 -112
  67. package/src/collect/__tests__/ipmi.test.ts +0 -96
  68. package/src/collect/__tests__/smart.test.ts +0 -68
  69. package/src/collect/__tests__/system.test.ts +0 -29
  70. package/src/collect/__tests__/zfs.test.ts +0 -72
  71. package/src/collect/conntrack.ts +0 -27
  72. package/src/collect/cpu.ts +0 -92
  73. package/src/collect/disks.ts +0 -91
  74. package/src/collect/fd.ts +0 -31
  75. package/src/collect/io-errors.ts +0 -23
  76. package/src/collect/io-latency.ts +0 -103
  77. package/src/collect/ipmi.ts +0 -207
  78. package/src/collect/memory.ts +0 -30
  79. package/src/collect/network.ts +0 -193
  80. package/src/collect/ntp.ts +0 -114
  81. package/src/collect/os-alerts.ts +0 -43
  82. package/src/collect/raid.ts +0 -40
  83. package/src/collect/security.ts +0 -268
  84. package/src/collect/smart.ts +0 -72
  85. package/src/collect/system.ts +0 -32
  86. package/src/collect/systemd.ts +0 -33
  87. package/src/collect/zfs.ts +0 -66
  88. package/src/config.ts +0 -65
  89. package/src/index.ts +0 -221
  90. package/src/lib/__tests__/parse.test.ts +0 -28
  91. package/src/lib/exec.ts +0 -16
  92. package/src/lib/parse.ts +0 -29
  93. package/src/lib/reboot-marker.ts +0 -88
  94. package/src/lib/types.ts +0 -226
  95. package/src/lib/version-check.ts +0 -39
  96. package/src/lib/version.ts +0 -33
  97. package/src/metrics-server.ts +0 -123
  98. package/src/notify/email.ts +0 -69
  99. package/src/notify/slack.ts +0 -47
  100. package/src/notify/telegram.ts +0 -65
  101. package/src/push/forge.ts +0 -109
  102. package/tsconfig.json +0 -15
  103. package/vitest.config.ts +0 -12
@@ -1,26 +0,0 @@
1
- ---
2
- name: No Data in Forge
3
- about: Crucible is running but Forge shows no data
4
- ---
5
-
6
- **Crucible version:**
7
- **OS and version:**
8
- **How long since install:**
9
-
10
- **Can you reach Forge?**
11
- ```
12
- curl -s -o /dev/null -w "%{http_code}" https://forge.glassmkr.com/api/health
13
- ```
14
-
15
- **Service status:**
16
- ```
17
- systemctl status glassmkr-crucible
18
- ```
19
-
20
- **Last 50 log lines:**
21
- ```
22
- journalctl -u glassmkr-crucible -n 50 --no-pager
23
- ```
24
-
25
- **Is smartctl installed?** `which smartctl`
26
- **Is ipmitool installed?** `which ipmitool`
@@ -1,53 +0,0 @@
1
- name: Build and publish Docker image
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- tags: ['v*']
7
-
8
- permissions:
9
- contents: read
10
- packages: write
11
-
12
- jobs:
13
- docker:
14
- name: Build and push to ghcr.io and docker.io
15
- runs-on: ubuntu-latest
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - uses: docker/setup-buildx-action@v3
20
-
21
- - uses: docker/login-action@v3
22
- with:
23
- registry: ghcr.io
24
- username: ${{ github.actor }}
25
- password: ${{ secrets.GITHUB_TOKEN }}
26
-
27
- - uses: docker/login-action@v3
28
- with:
29
- registry: docker.io
30
- username: ${{ secrets.DOCKERHUB_USERNAME }}
31
- password: ${{ secrets.DOCKERHUB_TOKEN }}
32
-
33
- - uses: docker/metadata-action@v5
34
- id: meta
35
- with:
36
- images: |
37
- ghcr.io/glassmkr/crucible
38
- docker.io/glassmkr/crucible
39
- tags: |
40
- type=semver,pattern={{version}}
41
- type=semver,pattern={{major}}.{{minor}}
42
- type=raw,value=latest,enable={{is_default_branch}}
43
- type=sha,prefix=sha-,format=short
44
-
45
- - uses: docker/build-push-action@v6
46
- with:
47
- context: .
48
- platforms: linux/amd64
49
- push: true
50
- tags: ${{ steps.meta.outputs.tags }}
51
- labels: ${{ steps.meta.outputs.labels }}
52
- cache-from: type=gha
53
- cache-to: type=gha,mode=max
@@ -1,25 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- tags: ['v*']
6
-
7
- permissions:
8
- contents: read
9
- id-token: write
10
-
11
- jobs:
12
- publish:
13
- name: npm publish
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: actions/setup-node@v4
18
- with:
19
- # Node 24 ships with npm 11.x which has working npm Trusted Publishing.
20
- node-version: 24
21
- registry-url: 'https://registry.npmjs.org'
22
- - run: npm --version
23
- - run: npm install
24
- - run: npm run build
25
- - run: npm publish --access public --provenance
package/Dockerfile DELETED
@@ -1,59 +0,0 @@
1
- # syntax=docker/dockerfile:1.7
2
- # Multi-stage build for Glassmkr Crucible monitoring agent.
3
-
4
- # ---------- Stage 1: build TypeScript to dist/ ----------
5
- FROM node:24-slim AS builder
6
- WORKDIR /build
7
- COPY package.json package-lock.json* ./
8
- RUN npm install --include=dev --no-audit --no-fund
9
- COPY tsconfig.json ./
10
- COPY src ./src
11
- RUN npm run build
12
-
13
- # ---------- Stage 2: production runtime ----------
14
- FROM node:24-slim AS runtime
15
-
16
- # Hardware monitoring tools. Crucible shells out to these; they must be on PATH.
17
- RUN apt-get update && apt-get install -y --no-install-recommends \
18
- smartmontools \
19
- ipmitool \
20
- dmidecode \
21
- lm-sensors \
22
- ethtool \
23
- util-linux \
24
- procps \
25
- net-tools \
26
- iproute2 \
27
- ca-certificates \
28
- && rm -rf /var/lib/apt/lists/*
29
-
30
- WORKDIR /app
31
-
32
- # Production node_modules only.
33
- COPY package.json package-lock.json* ./
34
- RUN npm ci --omit=dev --no-audit --no-fund && npm cache clean --force
35
-
36
- # Built code.
37
- COPY --from=builder /build/dist ./dist
38
-
39
- # Create a non-root user for future use. IPMI and SMART typically require root,
40
- # so the container is expected to run with --privileged or cap_add DAC_READ_SEARCH etc.
41
- # Keeping the user available lets operators drop privileges when hardware access is not needed.
42
- RUN useradd --system --no-create-home --shell /usr/sbin/nologin glassmkr
43
-
44
- # Crucible reads /etc/glassmkr/collector.yaml by default.
45
- # Mount the host config directory at this path.
46
- RUN mkdir -p /etc/glassmkr
47
-
48
- # Container health: verify the Node process is actually running and hasn't crashed.
49
- HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \
50
- CMD pgrep -f "node /app/dist/index.js" > /dev/null || exit 1
51
-
52
- LABEL org.opencontainers.image.source="https://github.com/glassmkr/crucible" \
53
- org.opencontainers.image.description="Glassmkr Crucible - bare metal server monitoring agent" \
54
- org.opencontainers.image.licenses="MIT" \
55
- org.opencontainers.image.title="Crucible" \
56
- org.opencontainers.image.vendor="Glassmkr"
57
-
58
- # Crucible does not listen on any port; data flows outbound to Forge.
59
- ENTRYPOINT ["node", "/app/dist/index.js"]
@@ -1,43 +0,0 @@
1
- # Glassmkr Crucible Configuration
2
- # Copy to /etc/glassmkr/crucible.yaml
3
-
4
- # Server identity
5
- server_name: "my-server"
6
-
7
- # Collection settings
8
- collection:
9
- interval_seconds: 300 # How often to collect (default 5 minutes)
10
- ipmi: true # Collect IPMI data (requires ipmitool)
11
- smart: true # Collect SMART data (requires smartmontools)
12
-
13
- # Forge integration (optional dashboard)
14
- forge:
15
- enabled: false
16
- url: "https://forge.glassmkr.com"
17
- api_key: "" # Get this from forge.glassmkr.com after registering a server
18
-
19
- # Alert thresholds (all optional, sensible defaults used if omitted)
20
- thresholds:
21
- ram_percent: 90 # Alert when RAM usage exceeds this
22
- swap_alert: true # Alert on any swap usage
23
- disk_percent: 85 # Alert when any disk exceeds this
24
- iowait_percent: 20 # Alert when CPU iowait exceeds this
25
- nvme_wear_percent: 85 # Alert when NVMe lifetime wear exceeds this
26
- disk_latency_nvme_ms: 50 # p99 latency threshold for NVMe
27
- disk_latency_hdd_ms: 200 # p99 latency threshold for HDD
28
- cpu_temp_warning_c: 80 # CPU temperature warning
29
- cpu_temp_critical_c: 90 # CPU temperature critical
30
- interface_utilization_percent: 90 # Network saturation threshold
31
-
32
- # Notification channels (all optional)
33
- channels:
34
- telegram:
35
- enabled: false
36
- bot_token: ""
37
- chat_id: ""
38
- email:
39
- enabled: false
40
- to: ""
41
- slack:
42
- enabled: false
43
- webhook_url: ""
@@ -1,26 +0,0 @@
1
- # Glassmkr Crucible - docker compose deployment
2
- #
3
- # Before starting, create /etc/glassmkr/collector.yaml on the host with your
4
- # Forge collector key. See https://forge.glassmkr.com/docs/getting-started.
5
- #
6
- # Why privileged + host network:
7
- # - privileged: true gives access to /dev/ipmi0 (IPMI sensors) and raw disk devices (SMART)
8
- # - network_mode: host lets the agent read the real host network interfaces and bond state
9
- # - /proc and /sys are mounted so the agent monitors the host, not the container
10
-
11
- services:
12
- crucible:
13
- image: ghcr.io/glassmkr/crucible:latest
14
- container_name: glassmkr-crucible
15
- restart: unless-stopped
16
- privileged: true
17
- network_mode: host
18
- volumes:
19
- - /etc/glassmkr:/etc/glassmkr:ro
20
- - /proc:/host/proc:ro
21
- - /sys:/host/sys:ro
22
- - /dev:/dev:ro
23
- - /run/dbus:/run/dbus:ro
24
- environment:
25
- - HOST_PROC=/host/proc
26
- - HOST_SYS=/host/sys
@@ -1,29 +0,0 @@
1
- #!/bin/bash
2
- # Sign a Crucible release
3
- # Usage: ./scripts/sign-release.sh <version>
4
-
5
- VERSION=$1
6
- DIST_DIR="dist"
7
-
8
- if [ -z "$VERSION" ]; then
9
- echo "Usage: ./scripts/sign-release.sh <version>"
10
- echo "Example: ./scripts/sign-release.sh v0.2.0"
11
- exit 1
12
- fi
13
-
14
- echo "Signing Crucible $VERSION"
15
-
16
- # Generate checksums
17
- cd "$DIST_DIR" || exit 1
18
- sha256sum *.tar.gz *.deb 2>/dev/null > SHA256SUMS || sha256sum *.js > SHA256SUMS
19
-
20
- # Sign the checksums file
21
- gpg --armor --detach-sign --local-user security@glassmkr.com SHA256SUMS
22
-
23
- echo ""
24
- echo "Release artifacts:"
25
- ls -la SHA256SUMS SHA256SUMS.asc
26
- echo ""
27
- echo "Verify with:"
28
- echo " gpg --verify SHA256SUMS.asc SHA256SUMS"
29
- echo " sha256sum -c SHA256SUMS"
@@ -1,74 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parseCliArgs, helpText, DEFAULT_CONFIG_PATH } from "../cli.js";
3
-
4
- describe("parseCliArgs", () => {
5
- it("--version returns version string and mode=version", () => {
6
- const { result, output } = parseCliArgs(["--version"], "1.2.3");
7
- expect(result.mode).toBe("version");
8
- expect(output).toBe("glassmkr-crucible v1.2.3");
9
- });
10
-
11
- it("-v aliases --version", () => {
12
- const { result, output } = parseCliArgs(["-v"], "1.2.3");
13
- expect(result.mode).toBe("version");
14
- expect(output).toBe("glassmkr-crucible v1.2.3");
15
- });
16
-
17
- it("--help returns help text and mode=help", () => {
18
- const { result, output } = parseCliArgs(["--help"], "1.2.3");
19
- expect(result.mode).toBe("help");
20
- expect(output).toContain("glassmkr-crucible v1.2.3");
21
- expect(output).toContain("Usage:");
22
- expect(output).toContain("--version");
23
- expect(output).toContain("--help");
24
- expect(output).toContain("--config");
25
- });
26
-
27
- it("-h aliases --help", () => {
28
- const { result } = parseCliArgs(["-h"], "1.2.3");
29
- expect(result.mode).toBe("help");
30
- });
31
-
32
- it("no args returns mode=run with the default config path", () => {
33
- const { result, output } = parseCliArgs([], "1.2.3");
34
- expect(result.mode).toBe("run");
35
- expect(result.configPath).toBe(DEFAULT_CONFIG_PATH);
36
- expect(output).toBeNull();
37
- });
38
-
39
- it("-c accepts a path in the next argument", () => {
40
- const { result } = parseCliArgs(["-c", "/tmp/a.yaml"], "1.2.3");
41
- expect(result.configPath).toBe("/tmp/a.yaml");
42
- });
43
-
44
- it("--config accepts a path in the next argument", () => {
45
- const { result } = parseCliArgs(["--config", "/tmp/b.yaml"], "1.2.3");
46
- expect(result.configPath).toBe("/tmp/b.yaml");
47
- });
48
-
49
- it("--config=PATH form works", () => {
50
- const { result } = parseCliArgs(["--config=/tmp/c.yaml"], "1.2.3");
51
- expect(result.configPath).toBe("/tmp/c.yaml");
52
- });
53
-
54
- it("legacy positional argument still sets config path", () => {
55
- const { result } = parseCliArgs(["/tmp/legacy.yaml"], "1.2.3");
56
- expect(result.configPath).toBe("/tmp/legacy.yaml");
57
- });
58
-
59
- it("--version wins over a provided config path (no collector start)", () => {
60
- const { result } = parseCliArgs(["--config", "/tmp/x.yaml", "--version"], "1.2.3");
61
- expect(result.mode).toBe("version");
62
- });
63
- });
64
-
65
- describe("helpText", () => {
66
- it("mentions the binary name, default config path, and both flags", () => {
67
- const txt = helpText("0.6.1");
68
- expect(txt).toContain("glassmkr-crucible v0.6.1");
69
- expect(txt).toContain(DEFAULT_CONFIG_PATH);
70
- expect(txt).toContain("-v, --version");
71
- expect(txt).toContain("-h, --help");
72
- expect(txt).toContain("-c, --config");
73
- });
74
- });
@@ -1,122 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { mkdtempSync, existsSync, writeFileSync, statSync, rmSync, chmodSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import {
6
- consumeRebootMarker,
7
- writeRebootMarker,
8
- parseDuration,
9
- } from "../lib/reboot-marker.js";
10
- import { parseCliArgs } from "../cli.js";
11
-
12
- let tmpDir: string;
13
- let path: string;
14
-
15
- beforeEach(() => {
16
- tmpDir = mkdtempSync(join(tmpdir(), "crucible-test-"));
17
- path = join(tmpDir, "reboot-expected");
18
- });
19
- afterEach(() => {
20
- try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
21
- });
22
-
23
- describe("consumeRebootMarker", () => {
24
- it("7. marker present, not expired: returns flag, deletes file", () => {
25
- const future = new Date(Date.now() + 5 * 60_000).toISOString();
26
- writeFileSync(path, JSON.stringify({ expires_at: future, reason: "kernel update" }));
27
- const out = consumeRebootMarker(path);
28
- expect(out).toEqual({ expected: true, reason: "kernel update" });
29
- expect(existsSync(path)).toBe(false);
30
- });
31
-
32
- it("8. marker present, expired: returns null, deletes file", () => {
33
- const past = new Date(Date.now() - 60_000).toISOString();
34
- writeFileSync(path, JSON.stringify({ expires_at: past, reason: "stale" }));
35
- expect(consumeRebootMarker(path)).toBeNull();
36
- expect(existsSync(path)).toBe(false);
37
- });
38
-
39
- it("9. marker absent: returns null, no throw", () => {
40
- expect(consumeRebootMarker(path)).toBeNull();
41
- });
42
-
43
- it("15. malformed JSON: returns null, file deleted, no crash", () => {
44
- writeFileSync(path, "{not json at all");
45
- expect(consumeRebootMarker(path)).toBeNull();
46
- expect(existsSync(path)).toBe(false);
47
- });
48
-
49
- it("invalid expires_at (missing): returns null, file deleted", () => {
50
- writeFileSync(path, JSON.stringify({ reason: "oops" }));
51
- expect(consumeRebootMarker(path)).toBeNull();
52
- expect(existsSync(path)).toBe(false);
53
- });
54
-
55
- it("consumed marker cannot be re-read (single-use)", () => {
56
- const future = new Date(Date.now() + 60_000).toISOString();
57
- writeFileSync(path, JSON.stringify({ expires_at: future }));
58
- expect(consumeRebootMarker(path)).not.toBeNull();
59
- expect(consumeRebootMarker(path)).toBeNull();
60
- });
61
- });
62
-
63
- describe("writeRebootMarker", () => {
64
- it("13. writes file at given path with correct TTL and reason, 0600 mode", () => {
65
- const now = new Date("2026-04-21T22:00:00Z");
66
- const res = writeRebootMarker({ path, reason: "kernel update", ttlMs: 10 * 60_000, now });
67
- expect(res.path).toBe(path);
68
- expect(res.expires_at).toBe("2026-04-21T22:10:00.000Z");
69
- expect(existsSync(path)).toBe(true);
70
- const mode = statSync(path).mode & 0o777;
71
- expect(mode).toBe(0o600);
72
- const round = consumeRebootMarker(path, new Date("2026-04-21T22:05:00Z"));
73
- expect(round).toEqual({ expected: true, reason: "kernel update" });
74
- });
75
-
76
- it("default TTL is 10 minutes", () => {
77
- const now = new Date("2026-04-21T22:00:00Z");
78
- const res = writeRebootMarker({ path, now });
79
- expect(res.expires_at).toBe("2026-04-21T22:10:00.000Z");
80
- });
81
- });
82
-
83
- describe("parseDuration", () => {
84
- it.each([
85
- ["10m", 600_000],
86
- ["2h", 7_200_000],
87
- ["600s", 600_000],
88
- ["500ms", 500],
89
- ["30", 30_000], // bare number -> seconds
90
- ])("%s -> %d ms", (input, ms) => {
91
- expect(parseDuration(input)).toBe(ms);
92
- });
93
- it("rejects garbage", () => {
94
- expect(parseDuration("forever")).toBeNull();
95
- expect(parseDuration("-5m")).toBeNull();
96
- expect(parseDuration("")).toBeNull();
97
- });
98
- });
99
-
100
- describe("CLI parseCliArgs subcommands", () => {
101
- it("14. `reboot` subcommand captured with flags", () => {
102
- const { result } = parseCliArgs(["reboot", "--reason", "kernel update"], "1.0.0");
103
- expect(result.mode).toBe("reboot");
104
- expect(result.reason).toBe("kernel update");
105
- });
106
- it("`mark-reboot` with --ttl parsed through", () => {
107
- const { result } = parseCliArgs(["mark-reboot", "--ttl=5m", "--reason=test"], "1.0.0");
108
- expect(result.mode).toBe("mark-reboot");
109
- expect(result.ttl).toBe("5m");
110
- expect(result.reason).toBe("test");
111
- });
112
- it("`mark-reboot --help` returns help output without running", () => {
113
- const { result, output } = parseCliArgs(["mark-reboot", "--help"], "1.0.0");
114
- expect(result.mode).toBe("help");
115
- expect(output).toContain("mark-reboot");
116
- });
117
- it("top-level help lists the new subcommands", () => {
118
- const { output } = parseCliArgs(["--help"], "1.0.0");
119
- expect(output).toMatch(/mark-reboot/);
120
- expect(output).toMatch(/reboot/);
121
- });
122
- });
@@ -1,15 +0,0 @@
1
- import { allRules } from "./rules.js";
2
- import type { Snapshot, AlertResult } from "../lib/types.js";
3
- import type { Config } from "../config.js";
4
-
5
- export function evaluateAlerts(snapshot: Snapshot, thresholds: Config["thresholds"]): AlertResult[] {
6
- const results: AlertResult[] = [];
7
- for (const rule of allRules) {
8
- try {
9
- results.push(...rule.evaluate(snapshot, thresholds));
10
- } catch (err) {
11
- console.error(`[alerts] Rule ${rule.type} error:`, err);
12
- }
13
- }
14
- return results;
15
- }