@aidalinfo/aegis-agent 0.1.0 → 0.2.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/dist/bin.js CHANGED
@@ -31,81 +31,245 @@ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
31
31
  });
32
32
  }
33
33
 
34
+ // src/metrics/hostpaths.ts
35
+ import { readFile, statfs } from "fs/promises";
36
+ import { join } from "path";
37
+ var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
38
+ var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
39
+ var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
40
+ function readProc(rel) {
41
+ return readFile(join(HOST_PROC, rel), "utf8");
42
+ }
43
+ function readHostFile(absPath) {
44
+ return readFile(HOST_ROOT ? join(HOST_ROOT, absPath) : absPath, "utf8");
45
+ }
46
+ function statHostFs(mount) {
47
+ return statfs(HOST_ROOT ? join(HOST_ROOT, mount) : mount);
48
+ }
49
+
50
+ // src/metrics/proc-parse.ts
51
+ function parseProcStat(content) {
52
+ const line = content.split("\n").find((l) => l.startsWith("cpu "));
53
+ if (!line) return { idle: 0, total: 0 };
54
+ const nums = line.trim().split(/\s+/).slice(1).map(Number);
55
+ const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
56
+ const total = nums.reduce((a, b) => a + (b || 0), 0);
57
+ return { idle, total };
58
+ }
59
+ function cpuPercent(prev3, cur) {
60
+ const dTotal = cur.total - prev3.total;
61
+ const dIdle = cur.idle - prev3.idle;
62
+ if (dTotal <= 0) return 0;
63
+ return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
64
+ }
65
+ function parseMemInfo(content) {
66
+ const get = (key) => {
67
+ const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
68
+ return m ? Number(m[1]) : 0;
69
+ };
70
+ return {
71
+ memTotalKb: get("MemTotal"),
72
+ memAvailableKb: get("MemAvailable"),
73
+ swapTotalKb: get("SwapTotal"),
74
+ swapFreeKb: get("SwapFree")
75
+ };
76
+ }
77
+ function memUsage(m) {
78
+ const totalMb = Math.round(m.memTotalKb / 1024);
79
+ const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
80
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
81
+ }
82
+ function swapUsage(m) {
83
+ const totalMb = Math.round(m.swapTotalKb / 1024);
84
+ const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
85
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
86
+ }
87
+ function parseLoadAvg(content) {
88
+ const [a, b, c] = content.trim().split(/\s+/).map(Number);
89
+ return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
90
+ }
91
+ function parseUptime(content) {
92
+ return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
93
+ }
94
+ function parseOsRelease(content) {
95
+ const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
96
+ return m ? m[1] : null;
97
+ }
98
+ function parseNetDev(content) {
99
+ const out = [];
100
+ for (const line of content.split("\n")) {
101
+ const m = line.match(/^\s*([^:]+):\s*(.+)$/);
102
+ if (!m) continue;
103
+ const iface = m[1].trim();
104
+ if (iface === "lo") continue;
105
+ const f = m[2].trim().split(/\s+/).map(Number);
106
+ out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
107
+ }
108
+ return out;
109
+ }
110
+ function netRates(prev3, cur, dtSec) {
111
+ if (dtSec <= 0) return [];
112
+ const prevMap = new Map(prev3.map((p) => [p.iface, p]));
113
+ return cur.map((c) => {
114
+ const p = prevMap.get(c.iface);
115
+ const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
116
+ const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
117
+ return {
118
+ interface: c.iface,
119
+ rxBytesPerSec: Math.round(rx / dtSec),
120
+ txBytesPerSec: Math.round(tx / dtSec)
121
+ };
122
+ });
123
+ }
124
+ var PSEUDO_FS = /* @__PURE__ */ new Set([
125
+ "sysfs",
126
+ "proc",
127
+ "tmpfs",
128
+ "devtmpfs",
129
+ "devpts",
130
+ "cgroup",
131
+ "cgroup2",
132
+ "pstore",
133
+ "bpf",
134
+ "mqueue",
135
+ "debugfs",
136
+ "tracefs",
137
+ "securityfs",
138
+ "fusectl",
139
+ "configfs",
140
+ "overlay",
141
+ "autofs",
142
+ "hugetlbfs",
143
+ "squashfs",
144
+ "ramfs",
145
+ "nsfs",
146
+ "binfmt_misc",
147
+ "efivarfs"
148
+ ]);
149
+ function parseMounts(content) {
150
+ const seen = /* @__PURE__ */ new Set();
151
+ const out = [];
152
+ for (const line of content.split("\n")) {
153
+ const parts = line.split(/\s+/);
154
+ if (parts.length < 3) continue;
155
+ const [, mount, fstype] = parts;
156
+ if (PSEUDO_FS.has(fstype)) continue;
157
+ if (seen.has(mount)) continue;
158
+ seen.add(mount);
159
+ out.push(mount);
160
+ }
161
+ return out;
162
+ }
163
+ function parseDiskStats(content) {
164
+ let readSectors = 0;
165
+ let writeSectors = 0;
166
+ for (const line of content.split("\n")) {
167
+ const f = line.trim().split(/\s+/);
168
+ if (f.length < 11) continue;
169
+ const name = f[2];
170
+ if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
171
+ readSectors += Number(f[5]) || 0;
172
+ writeSectors += Number(f[9]) || 0;
173
+ }
174
+ return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
175
+ }
176
+ function diskIoRates(prev3, cur, dtSec) {
177
+ if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
178
+ return {
179
+ readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
180
+ writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
181
+ };
182
+ }
183
+
34
184
  // src/metrics/cpu.ts
35
- import si from "systeminformation";
185
+ var prev = null;
36
186
  async function collectCpu() {
37
- const load = await si.currentLoad();
38
- return { usagePercent: Math.round(load.currentLoad * 10) / 10 };
187
+ const cur = parseProcStat(await readProc("stat"));
188
+ const usagePercent = prev ? cpuPercent(prev, cur) : 0;
189
+ prev = cur;
190
+ return { usagePercent };
39
191
  }
40
192
 
41
193
  // src/metrics/memory.ts
42
- import si2 from "systeminformation";
43
194
  async function collectMemory() {
44
- const mem = await si2.mem();
45
- const totalMb = Math.round(mem.total / 1048576);
46
- const usedMb = Math.round(mem.used / 1048576);
47
- return {
48
- totalMb,
49
- usedMb,
50
- usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
51
- };
195
+ return memUsage(parseMemInfo(await readProc("meminfo")));
52
196
  }
53
197
 
54
198
  // src/metrics/disk.ts
55
- import si3 from "systeminformation";
199
+ var prevIo = null;
200
+ function normaliseMountForLabel(rawMount) {
201
+ if (!HOST_ROOT) return rawMount;
202
+ if (rawMount === HOST_ROOT) return "/";
203
+ const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
204
+ if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
205
+ return rawMount;
206
+ }
56
207
  async function collectDisk() {
57
- const [fsSizes, fsStats] = await Promise.all([si3.fsSize(), si3.fsStats()]);
58
- const readBytesPerSec = Math.round(fsStats.rx_sec ?? 0);
59
- const writeBytesPerSec = Math.round(fsStats.wx_sec ?? 0);
60
- return fsSizes.map((fs) => ({
61
- mount: fs.mount,
62
- totalGb: Math.round(fs.size / 1073741824 * 10) / 10,
63
- usedGb: Math.round(fs.used / 1073741824 * 10) / 10,
64
- usagePercent: Math.round(fs.use * 10) / 10,
65
- readBytesPerSec,
66
- writeBytesPerSec
67
- }));
208
+ const rawMounts = parseMounts(await readProc("mounts"));
209
+ const stats = parseDiskStats(await readProc("diskstats"));
210
+ const now = Date.now();
211
+ let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
212
+ if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
213
+ prevIo = { stats, t: now };
214
+ const out = [];
215
+ const seenLabels = /* @__PURE__ */ new Set();
216
+ for (const rawMount of rawMounts) {
217
+ try {
218
+ const label = normaliseMountForLabel(rawMount);
219
+ if (seenLabels.has(label)) continue;
220
+ seenLabels.add(label);
221
+ const fs = await statHostFs(label);
222
+ const bsize = Number(fs.bsize);
223
+ const blocks = Number(fs.blocks);
224
+ const bfree = Number(fs.bfree);
225
+ const bavail = Number(fs.bavail);
226
+ if (!blocks) continue;
227
+ const totalBytes = blocks * bsize;
228
+ const usedBytes = (blocks - bfree) * bsize;
229
+ const denom = blocks - bfree + bavail;
230
+ out.push({
231
+ mount: label,
232
+ totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
233
+ usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
234
+ usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
235
+ readBytesPerSec: io.readBytesPerSec,
236
+ writeBytesPerSec: io.writeBytesPerSec
237
+ });
238
+ } catch {
239
+ }
240
+ }
241
+ return out;
68
242
  }
69
243
 
70
244
  // src/metrics/network.ts
71
- import si4 from "systeminformation";
245
+ var prev2 = null;
72
246
  async function collectNetwork() {
73
- const stats = await si4.networkStats();
74
- return stats.filter((s) => s.iface && s.rx_sec !== null).map((s) => ({
75
- interface: s.iface,
76
- rxBytesPerSec: Math.round(s.rx_sec ?? 0),
77
- txBytesPerSec: Math.round(s.tx_sec ?? 0)
78
- }));
247
+ const counters = parseNetDev(await readProc("net/dev"));
248
+ const now = Date.now();
249
+ if (!prev2) {
250
+ prev2 = { counters, t: now };
251
+ return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
252
+ }
253
+ const dtSec = (now - prev2.t) / 1e3;
254
+ const rates = netRates(prev2.counters, counters, dtSec);
255
+ prev2 = { counters, t: now };
256
+ return rates;
79
257
  }
80
258
 
81
259
  // src/metrics/load.ts
82
- import { loadavg } from "os";
83
- function collectLoad() {
84
- const [l1, l5, l15] = loadavg();
85
- return {
86
- "1m": Math.round(l1 * 100) / 100,
87
- "5m": Math.round(l5 * 100) / 100,
88
- "15m": Math.round(l15 * 100) / 100
89
- };
260
+ async function collectLoad() {
261
+ return parseLoadAvg(await readProc("loadavg"));
90
262
  }
91
263
 
92
264
  // src/metrics/swap.ts
93
- import si5 from "systeminformation";
94
265
  async function collectSwap() {
95
- const mem = await si5.mem();
96
- const totalMb = Math.round(mem.swaptotal / 1048576);
97
- const usedMb = Math.round(mem.swapused / 1048576);
98
- return {
99
- totalMb,
100
- usedMb,
101
- usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
102
- };
266
+ return swapUsage(parseMemInfo(await readProc("meminfo")));
103
267
  }
104
268
 
105
269
  // src/metrics/processes.ts
106
- import si6 from "systeminformation";
270
+ import si from "systeminformation";
107
271
  async function collectProcesses() {
108
- const { list } = await si6.processes();
272
+ const { list } = await si.processes();
109
273
  return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
110
274
  pid: p.pid,
111
275
  name: p.name,
@@ -115,38 +279,51 @@ async function collectProcesses() {
115
279
  }
116
280
 
117
281
  // src/metrics/temperature.ts
118
- import si7 from "systeminformation";
282
+ import si2 from "systeminformation";
119
283
  async function collectTemperature() {
120
284
  try {
121
- const temp = await si7.cpuTemperature();
285
+ const temp = await si2.cpuTemperature();
122
286
  return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
123
287
  } catch {
124
288
  return { cpuCelsius: null };
125
289
  }
126
290
  }
127
291
 
292
+ // src/metrics/system.ts
293
+ async function collectUptime() {
294
+ try {
295
+ return parseUptime(await readProc("uptime"));
296
+ } catch {
297
+ return 0;
298
+ }
299
+ }
300
+ async function collectOs() {
301
+ try {
302
+ return parseOsRelease(await readHostFile("/etc/os-release"));
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+
128
308
  // src/collector.ts
129
309
  async function collectMetrics(config) {
130
- const [cpu, memory, disks] = await Promise.all([
310
+ const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
131
311
  collectCpu(),
132
312
  collectMemory(),
133
- collectDisk()
313
+ collectDisk(),
314
+ collectUptime(),
315
+ collectOs()
134
316
  ]);
135
- const metrics = { cpu, memory, disks };
317
+ const metrics = { cpu, memory, disks, os, uptimeSeconds };
136
318
  if (config.mode === "full") {
137
- const [network, swap, topProcesses, temperature] = await Promise.all([
319
+ const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
138
320
  collectNetwork(),
139
321
  collectSwap(),
140
322
  collectProcesses(),
141
- collectTemperature()
323
+ collectTemperature(),
324
+ collectLoad()
142
325
  ]);
143
- Object.assign(metrics, {
144
- network,
145
- loadAverage: collectLoad(),
146
- swap,
147
- topProcesses,
148
- temperature
149
- });
326
+ Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
150
327
  }
151
328
  return {
152
329
  hostname: config.hostname,
@@ -264,9 +441,18 @@ var install = defineCommand({
264
441
  async run({ args }) {
265
442
  let binaryPath;
266
443
  try {
267
- binaryPath = execSync2("which aegis-agent").toString().trim();
444
+ binaryPath = execSync2("which aegis-agent", { stdio: "pipe" }).toString().trim();
268
445
  } catch {
269
- binaryPath = process.argv[1];
446
+ console.log("[aegis-agent] Not found in PATH \u2014 installing globally via npm...");
447
+ try {
448
+ execSync2("npm install -g @aidalinfo/aegis-agent", { stdio: "inherit" });
449
+ binaryPath = execSync2("which aegis-agent", { stdio: "pipe" }).toString().trim();
450
+ } catch {
451
+ console.error(
452
+ "[aegis-agent] Global install failed.\nRun manually: npm install -g @aidalinfo/aegis-agent\nThen: aegis-agent install ..."
453
+ );
454
+ process.exit(1);
455
+ }
270
456
  }
271
457
  const config = ConfigSchema.parse({
272
458
  endpoint: args.endpoint,
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ declare const ConfigSchema: z.ZodObject<{
20
20
  hostname?: string | undefined;
21
21
  }>;
22
22
  type Config = z.infer<typeof ConfigSchema>;
23
+ declare function loadConfig(configPath?: string): Config;
23
24
 
24
25
  interface MetricsPayload {
25
26
  hostname: string;
@@ -27,5 +28,6 @@ interface MetricsPayload {
27
28
  mode: 'core' | 'full';
28
29
  metrics: Record<string, unknown>;
29
30
  }
31
+ declare function collectMetrics(config: Config): Promise<MetricsPayload>;
30
32
 
31
- export type { Config, MetricsPayload };
33
+ export { type Config, ConfigSchema, type MetricsPayload, collectMetrics, loadConfig };
package/dist/index.js CHANGED
@@ -0,0 +1,333 @@
1
+ // src/metrics/hostpaths.ts
2
+ import { readFile, statfs } from "fs/promises";
3
+ import { join } from "path";
4
+ var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
5
+ var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
6
+ var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
7
+ function readProc(rel) {
8
+ return readFile(join(HOST_PROC, rel), "utf8");
9
+ }
10
+ function readHostFile(absPath) {
11
+ return readFile(HOST_ROOT ? join(HOST_ROOT, absPath) : absPath, "utf8");
12
+ }
13
+ function statHostFs(mount) {
14
+ return statfs(HOST_ROOT ? join(HOST_ROOT, mount) : mount);
15
+ }
16
+
17
+ // src/metrics/proc-parse.ts
18
+ function parseProcStat(content) {
19
+ const line = content.split("\n").find((l) => l.startsWith("cpu "));
20
+ if (!line) return { idle: 0, total: 0 };
21
+ const nums = line.trim().split(/\s+/).slice(1).map(Number);
22
+ const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
23
+ const total = nums.reduce((a, b) => a + (b || 0), 0);
24
+ return { idle, total };
25
+ }
26
+ function cpuPercent(prev3, cur) {
27
+ const dTotal = cur.total - prev3.total;
28
+ const dIdle = cur.idle - prev3.idle;
29
+ if (dTotal <= 0) return 0;
30
+ return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
31
+ }
32
+ function parseMemInfo(content) {
33
+ const get = (key) => {
34
+ const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
35
+ return m ? Number(m[1]) : 0;
36
+ };
37
+ return {
38
+ memTotalKb: get("MemTotal"),
39
+ memAvailableKb: get("MemAvailable"),
40
+ swapTotalKb: get("SwapTotal"),
41
+ swapFreeKb: get("SwapFree")
42
+ };
43
+ }
44
+ function memUsage(m) {
45
+ const totalMb = Math.round(m.memTotalKb / 1024);
46
+ const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
47
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
48
+ }
49
+ function swapUsage(m) {
50
+ const totalMb = Math.round(m.swapTotalKb / 1024);
51
+ const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
52
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
53
+ }
54
+ function parseLoadAvg(content) {
55
+ const [a, b, c] = content.trim().split(/\s+/).map(Number);
56
+ return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
57
+ }
58
+ function parseUptime(content) {
59
+ return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
60
+ }
61
+ function parseOsRelease(content) {
62
+ const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
63
+ return m ? m[1] : null;
64
+ }
65
+ function parseNetDev(content) {
66
+ const out = [];
67
+ for (const line of content.split("\n")) {
68
+ const m = line.match(/^\s*([^:]+):\s*(.+)$/);
69
+ if (!m) continue;
70
+ const iface = m[1].trim();
71
+ if (iface === "lo") continue;
72
+ const f = m[2].trim().split(/\s+/).map(Number);
73
+ out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
74
+ }
75
+ return out;
76
+ }
77
+ function netRates(prev3, cur, dtSec) {
78
+ if (dtSec <= 0) return [];
79
+ const prevMap = new Map(prev3.map((p) => [p.iface, p]));
80
+ return cur.map((c) => {
81
+ const p = prevMap.get(c.iface);
82
+ const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
83
+ const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
84
+ return {
85
+ interface: c.iface,
86
+ rxBytesPerSec: Math.round(rx / dtSec),
87
+ txBytesPerSec: Math.round(tx / dtSec)
88
+ };
89
+ });
90
+ }
91
+ var PSEUDO_FS = /* @__PURE__ */ new Set([
92
+ "sysfs",
93
+ "proc",
94
+ "tmpfs",
95
+ "devtmpfs",
96
+ "devpts",
97
+ "cgroup",
98
+ "cgroup2",
99
+ "pstore",
100
+ "bpf",
101
+ "mqueue",
102
+ "debugfs",
103
+ "tracefs",
104
+ "securityfs",
105
+ "fusectl",
106
+ "configfs",
107
+ "overlay",
108
+ "autofs",
109
+ "hugetlbfs",
110
+ "squashfs",
111
+ "ramfs",
112
+ "nsfs",
113
+ "binfmt_misc",
114
+ "efivarfs"
115
+ ]);
116
+ function parseMounts(content) {
117
+ const seen = /* @__PURE__ */ new Set();
118
+ const out = [];
119
+ for (const line of content.split("\n")) {
120
+ const parts = line.split(/\s+/);
121
+ if (parts.length < 3) continue;
122
+ const [, mount, fstype] = parts;
123
+ if (PSEUDO_FS.has(fstype)) continue;
124
+ if (seen.has(mount)) continue;
125
+ seen.add(mount);
126
+ out.push(mount);
127
+ }
128
+ return out;
129
+ }
130
+ function parseDiskStats(content) {
131
+ let readSectors = 0;
132
+ let writeSectors = 0;
133
+ for (const line of content.split("\n")) {
134
+ const f = line.trim().split(/\s+/);
135
+ if (f.length < 11) continue;
136
+ const name = f[2];
137
+ if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
138
+ readSectors += Number(f[5]) || 0;
139
+ writeSectors += Number(f[9]) || 0;
140
+ }
141
+ return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
142
+ }
143
+ function diskIoRates(prev3, cur, dtSec) {
144
+ if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
145
+ return {
146
+ readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
147
+ writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
148
+ };
149
+ }
150
+
151
+ // src/metrics/cpu.ts
152
+ var prev = null;
153
+ async function collectCpu() {
154
+ const cur = parseProcStat(await readProc("stat"));
155
+ const usagePercent = prev ? cpuPercent(prev, cur) : 0;
156
+ prev = cur;
157
+ return { usagePercent };
158
+ }
159
+
160
+ // src/metrics/memory.ts
161
+ async function collectMemory() {
162
+ return memUsage(parseMemInfo(await readProc("meminfo")));
163
+ }
164
+
165
+ // src/metrics/disk.ts
166
+ var prevIo = null;
167
+ function normaliseMountForLabel(rawMount) {
168
+ if (!HOST_ROOT) return rawMount;
169
+ if (rawMount === HOST_ROOT) return "/";
170
+ const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
171
+ if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
172
+ return rawMount;
173
+ }
174
+ async function collectDisk() {
175
+ const rawMounts = parseMounts(await readProc("mounts"));
176
+ const stats = parseDiskStats(await readProc("diskstats"));
177
+ const now = Date.now();
178
+ let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
179
+ if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
180
+ prevIo = { stats, t: now };
181
+ const out = [];
182
+ const seenLabels = /* @__PURE__ */ new Set();
183
+ for (const rawMount of rawMounts) {
184
+ try {
185
+ const label = normaliseMountForLabel(rawMount);
186
+ if (seenLabels.has(label)) continue;
187
+ seenLabels.add(label);
188
+ const fs = await statHostFs(label);
189
+ const bsize = Number(fs.bsize);
190
+ const blocks = Number(fs.blocks);
191
+ const bfree = Number(fs.bfree);
192
+ const bavail = Number(fs.bavail);
193
+ if (!blocks) continue;
194
+ const totalBytes = blocks * bsize;
195
+ const usedBytes = (blocks - bfree) * bsize;
196
+ const denom = blocks - bfree + bavail;
197
+ out.push({
198
+ mount: label,
199
+ totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
200
+ usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
201
+ usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
202
+ readBytesPerSec: io.readBytesPerSec,
203
+ writeBytesPerSec: io.writeBytesPerSec
204
+ });
205
+ } catch {
206
+ }
207
+ }
208
+ return out;
209
+ }
210
+
211
+ // src/metrics/network.ts
212
+ var prev2 = null;
213
+ async function collectNetwork() {
214
+ const counters = parseNetDev(await readProc("net/dev"));
215
+ const now = Date.now();
216
+ if (!prev2) {
217
+ prev2 = { counters, t: now };
218
+ return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
219
+ }
220
+ const dtSec = (now - prev2.t) / 1e3;
221
+ const rates = netRates(prev2.counters, counters, dtSec);
222
+ prev2 = { counters, t: now };
223
+ return rates;
224
+ }
225
+
226
+ // src/metrics/load.ts
227
+ async function collectLoad() {
228
+ return parseLoadAvg(await readProc("loadavg"));
229
+ }
230
+
231
+ // src/metrics/swap.ts
232
+ async function collectSwap() {
233
+ return swapUsage(parseMemInfo(await readProc("meminfo")));
234
+ }
235
+
236
+ // src/metrics/processes.ts
237
+ import si from "systeminformation";
238
+ async function collectProcesses() {
239
+ const { list } = await si.processes();
240
+ return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
241
+ pid: p.pid,
242
+ name: p.name,
243
+ cpuPercent: Math.round(p.cpu * 10) / 10,
244
+ memPercent: Math.round(p.mem * 10) / 10
245
+ }));
246
+ }
247
+
248
+ // src/metrics/temperature.ts
249
+ import si2 from "systeminformation";
250
+ async function collectTemperature() {
251
+ try {
252
+ const temp = await si2.cpuTemperature();
253
+ return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
254
+ } catch {
255
+ return { cpuCelsius: null };
256
+ }
257
+ }
258
+
259
+ // src/metrics/system.ts
260
+ async function collectUptime() {
261
+ try {
262
+ return parseUptime(await readProc("uptime"));
263
+ } catch {
264
+ return 0;
265
+ }
266
+ }
267
+ async function collectOs() {
268
+ try {
269
+ return parseOsRelease(await readHostFile("/etc/os-release"));
270
+ } catch {
271
+ return null;
272
+ }
273
+ }
274
+
275
+ // src/collector.ts
276
+ async function collectMetrics(config) {
277
+ const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
278
+ collectCpu(),
279
+ collectMemory(),
280
+ collectDisk(),
281
+ collectUptime(),
282
+ collectOs()
283
+ ]);
284
+ const metrics = { cpu, memory, disks, os, uptimeSeconds };
285
+ if (config.mode === "full") {
286
+ const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
287
+ collectNetwork(),
288
+ collectSwap(),
289
+ collectProcesses(),
290
+ collectTemperature(),
291
+ collectLoad()
292
+ ]);
293
+ Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
294
+ }
295
+ return {
296
+ hostname: config.hostname,
297
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
298
+ mode: config.mode,
299
+ metrics
300
+ };
301
+ }
302
+
303
+ // src/config.ts
304
+ import { z } from "zod";
305
+ import { readFileSync } from "fs";
306
+ import { hostname } from "os";
307
+ var ConfigSchema = z.object({
308
+ interval: z.coerce.number().int().min(5).default(30),
309
+ endpoint: z.string().url(),
310
+ apiKey: z.string().min(1),
311
+ mode: z.enum(["core", "full"]).default("core"),
312
+ hostname: z.string().default(hostname())
313
+ });
314
+ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
315
+ let fileConfig = {};
316
+ try {
317
+ fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
318
+ } catch {
319
+ }
320
+ return ConfigSchema.parse({
321
+ ...fileConfig,
322
+ ...process.env.AEGIS_INTERVAL !== void 0 && { interval: process.env.AEGIS_INTERVAL },
323
+ ...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
324
+ ...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
325
+ ...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
326
+ ...process.env.AEGIS_HOSTNAME !== void 0 && { hostname: process.env.AEGIS_HOSTNAME }
327
+ });
328
+ }
329
+ export {
330
+ ConfigSchema,
331
+ collectMetrics,
332
+ loadConfig
333
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aidalinfo/aegis-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight monitoring agent for Linux machines — pushes system metrics to Aegis",
5
5
  "license": "MIT",
6
6
  "type": "module",