@nocville/npc 0.2.1 → 0.2.3

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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -120
  3. package/dist/cjs/cli.js +55 -34
  4. package/dist/cjs/core/base-npc.js +8 -0
  5. package/dist/cjs/core/config.js +19 -2
  6. package/dist/cjs/core/errors.js +23 -0
  7. package/dist/cjs/core/http.js +121 -0
  8. package/dist/cjs/core/pixel-art.js +110 -0
  9. package/dist/cjs/core/polling-npc.js +99 -14
  10. package/dist/cjs/core/session-filter.js +3 -1
  11. package/dist/cjs/core/text.js +13 -0
  12. package/dist/cjs/core/thresholds.js +29 -0
  13. package/dist/cjs/index.js +10 -2
  14. package/dist/cjs/npcs/agent-common/session.js +1 -0
  15. package/dist/cjs/npcs/github-watcher/defaults.js +7 -1
  16. package/dist/cjs/npcs/github-watcher/github-api.js +82 -45
  17. package/dist/cjs/npcs/github-watcher/index.js +82 -36
  18. package/dist/cjs/npcs/internet-connectivity/checks.js +70 -67
  19. package/dist/cjs/npcs/internet-connectivity/defaults.js +5 -2
  20. package/dist/cjs/npcs/internet-connectivity/index.js +134 -131
  21. package/dist/cjs/npcs/log-watcher/buffer.js +61 -0
  22. package/dist/cjs/npcs/log-watcher/index.js +109 -103
  23. package/dist/cjs/npcs/log-watcher/tail.js +100 -53
  24. package/dist/cjs/npcs/process-watcher/index.js +61 -51
  25. package/dist/cjs/npcs/process-watcher/process-scanner.js +63 -69
  26. package/dist/cjs/npcs/receptionist/defaults.js +4 -0
  27. package/dist/cjs/npcs/receptionist/index.js +45 -19
  28. package/dist/cjs/npcs/receptionist/weather.js +42 -47
  29. package/dist/cjs/npcs/system-monitor/collectors.js +66 -17
  30. package/dist/cjs/npcs/system-monitor/index.js +52 -28
  31. package/dist/cjs/npcs/web-page-watcher/basic-mode.js +30 -14
  32. package/dist/cjs/npcs/web-page-watcher/capture.js +23 -1
  33. package/dist/cjs/npcs/web-page-watcher/defaults.js +44 -2
  34. package/dist/cjs/npcs/web-page-watcher/index.js +119 -68
  35. package/dist/cjs/npcs/web-page-watcher/pixelate.js +2 -1
  36. package/dist/cli.js +56 -35
  37. package/dist/core/base-npc.d.ts +5 -0
  38. package/dist/core/base-npc.js +8 -0
  39. package/dist/core/config.d.ts +5 -0
  40. package/dist/core/config.js +18 -2
  41. package/dist/core/errors.d.ts +11 -0
  42. package/dist/core/errors.js +18 -0
  43. package/dist/core/http.d.ts +52 -0
  44. package/dist/core/http.js +111 -0
  45. package/dist/core/pixel-art.d.ts +38 -0
  46. package/dist/core/pixel-art.js +105 -0
  47. package/dist/core/polling-npc.d.ts +22 -1
  48. package/dist/core/polling-npc.js +98 -13
  49. package/dist/core/session-filter.d.ts +2 -2
  50. package/dist/core/session-filter.js +3 -1
  51. package/dist/core/text.d.ts +6 -0
  52. package/dist/core/text.js +12 -0
  53. package/dist/core/thresholds.d.ts +9 -1
  54. package/dist/core/thresholds.js +27 -0
  55. package/dist/index.d.ts +5 -2
  56. package/dist/index.js +4 -2
  57. package/dist/npcs/agent-common/session.js +1 -0
  58. package/dist/npcs/github-watcher/defaults.d.ts +6 -0
  59. package/dist/npcs/github-watcher/defaults.js +7 -1
  60. package/dist/npcs/github-watcher/github-api.d.ts +21 -7
  61. package/dist/npcs/github-watcher/github-api.js +80 -45
  62. package/dist/npcs/github-watcher/index.d.ts +6 -0
  63. package/dist/npcs/github-watcher/index.js +83 -37
  64. package/dist/npcs/internet-connectivity/checks.d.ts +15 -18
  65. package/dist/npcs/internet-connectivity/checks.js +70 -67
  66. package/dist/npcs/internet-connectivity/defaults.d.ts +3 -0
  67. package/dist/npcs/internet-connectivity/defaults.js +5 -2
  68. package/dist/npcs/internet-connectivity/index.d.ts +19 -6
  69. package/dist/npcs/internet-connectivity/index.js +135 -133
  70. package/dist/npcs/log-watcher/buffer.d.ts +22 -0
  71. package/dist/npcs/log-watcher/buffer.js +58 -0
  72. package/dist/npcs/log-watcher/index.d.ts +22 -21
  73. package/dist/npcs/log-watcher/index.js +112 -106
  74. package/dist/npcs/log-watcher/tail.d.ts +23 -3
  75. package/dist/npcs/log-watcher/tail.js +101 -54
  76. package/dist/npcs/process-watcher/index.d.ts +1 -1
  77. package/dist/npcs/process-watcher/index.js +62 -52
  78. package/dist/npcs/process-watcher/process-scanner.d.ts +29 -1
  79. package/dist/npcs/process-watcher/process-scanner.js +61 -71
  80. package/dist/npcs/receptionist/defaults.d.ts +4 -0
  81. package/dist/npcs/receptionist/defaults.js +4 -0
  82. package/dist/npcs/receptionist/index.d.ts +12 -2
  83. package/dist/npcs/receptionist/index.js +46 -20
  84. package/dist/npcs/receptionist/weather.d.ts +8 -6
  85. package/dist/npcs/receptionist/weather.js +39 -45
  86. package/dist/npcs/system-monitor/collectors.d.ts +15 -3
  87. package/dist/npcs/system-monitor/collectors.js +64 -17
  88. package/dist/npcs/system-monitor/defaults.d.ts +1 -1
  89. package/dist/npcs/system-monitor/index.d.ts +2 -1
  90. package/dist/npcs/system-monitor/index.js +54 -30
  91. package/dist/npcs/web-page-watcher/basic-mode.d.ts +2 -2
  92. package/dist/npcs/web-page-watcher/basic-mode.js +30 -14
  93. package/dist/npcs/web-page-watcher/capture.d.ts +18 -0
  94. package/dist/npcs/web-page-watcher/capture.js +21 -1
  95. package/dist/npcs/web-page-watcher/defaults.d.ts +23 -0
  96. package/dist/npcs/web-page-watcher/defaults.js +41 -1
  97. package/dist/npcs/web-page-watcher/index.d.ts +18 -1
  98. package/dist/npcs/web-page-watcher/index.js +121 -70
  99. package/dist/npcs/web-page-watcher/pixelate.js +2 -1
  100. package/package.json +2 -7
@@ -2,10 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InternetConnectivityNPC = void 0;
4
4
  exports.evaluateConnectivityStatus = evaluateConnectivityStatus;
5
+ exports.parseLatencyHost = parseLatencyHost;
5
6
  const polling_npc_js_1 = require("../../core/polling-npc.js");
6
7
  const thresholds_js_1 = require("../../core/thresholds.js");
7
8
  const screen_js_1 = require("../../core/screen.js");
8
9
  const config_js_1 = require("../../core/config.js");
10
+ const errors_js_1 = require("../../core/errors.js");
11
+ const http_js_1 = require("../../core/http.js");
9
12
  const checks_js_1 = require("./checks.js");
10
13
  const defaults_js_1 = require("./defaults.js");
11
14
  /**
@@ -16,13 +19,15 @@ const defaults_js_1 = require("./defaults.js");
16
19
  function evaluateConnectivityStatus(sample, thresholds) {
17
20
  if (sample.allFailed)
18
21
  return 'critical';
19
- const timeStatus = (0, thresholds_js_1.worstStatus)([
20
- (0, thresholds_js_1.evaluateThreshold)(sample.dnsAvg, thresholds.dnsTime),
21
- (0, thresholds_js_1.evaluateThreshold)(sample.httpAvg, thresholds.httpTime),
22
- (0, thresholds_js_1.evaluateThreshold)(sample.latencyAvg, thresholds.latencyTime),
23
- ]);
24
- const failStatus = sample.anyFailed ? 'warning' : 'healthy';
25
- return (0, thresholds_js_1.worstStatus)([timeStatus, failStatus]);
22
+ const statuses = [sample.anyFailed ? 'warning' : 'healthy'];
23
+ if (sample.dnsAvg !== null)
24
+ statuses.push((0, thresholds_js_1.evaluateThreshold)(sample.dnsAvg, thresholds.dnsTime));
25
+ if (sample.httpAvg !== null)
26
+ statuses.push((0, thresholds_js_1.evaluateThreshold)(sample.httpAvg, thresholds.httpTime));
27
+ if (sample.latencyAvg !== null) {
28
+ statuses.push((0, thresholds_js_1.evaluateThreshold)(sample.latencyAvg, thresholds.latencyTime));
29
+ }
30
+ return (0, thresholds_js_1.worstStatus)(statuses);
26
31
  }
27
32
  /** Scale a threshold set by a ratio, preserving direction. */
28
33
  function scaleThresholds(base, ratio) {
@@ -37,6 +42,30 @@ function scaleThresholds(base, ratio) {
37
42
  latencyTime: scale(base.latencyTime),
38
43
  };
39
44
  }
45
+ /**
46
+ * Parse a latency target: "host", "host:port", "[ipv6]:port" or a bare IPv6
47
+ * address. Throws NpcConfigError on an out-of-range port.
48
+ */
49
+ function parseLatencyHost(input, defaultPort = defaults_js_1.DEFAULTS.latencyPort) {
50
+ const s = input.trim();
51
+ const toPort = (raw) => {
52
+ const port = Number(raw);
53
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
54
+ throw new errors_js_1.NpcConfigError(`Invalid port in latency host "${s}"`);
55
+ }
56
+ return port;
57
+ };
58
+ const bracketed = s.match(/^\[([^\]]+)\](?::(\d*))?$/);
59
+ if (bracketed) {
60
+ return { host: bracketed[1], port: bracketed[2] ? toPort(bracketed[2]) : defaultPort };
61
+ }
62
+ const colons = s.split(':').length - 1;
63
+ if (colons === 1) {
64
+ const [host, port] = s.split(':');
65
+ return { host, port: toPort(port) };
66
+ }
67
+ return { host: s, port: defaultPort }; // plain host, or a bare IPv6 address
68
+ }
40
69
  class InternetConnectivityNPC extends polling_npc_js_1.PollingNPC {
41
70
  dnsHosts;
42
71
  httpUrls;
@@ -53,10 +82,16 @@ class InternetConnectivityNPC extends polling_npc_js_1.PollingNPC {
53
82
  });
54
83
  this.dnsHosts = config.dnsHosts ?? defaults_js_1.DEFAULTS.dnsHosts;
55
84
  this.httpUrls = config.httpUrls ?? defaults_js_1.DEFAULTS.httpUrls;
56
- this.latencyHosts = config.latencyHosts
57
- ? parseLatencyHosts(config.latencyHosts)
58
- : defaults_js_1.DEFAULTS.latencyHosts;
59
- this.httpTimeout = config.httpTimeout ?? defaults_js_1.DEFAULTS.httpTimeout;
85
+ this.latencyHosts = (config.latencyHosts ?? defaults_js_1.DEFAULTS.latencyHosts).map((h) => typeof h === 'string' ? parseLatencyHost(h) : h);
86
+ // Every check must finish well inside the poll interval.
87
+ this.httpTimeout = Math.min(Math.max(500, config.httpTimeout ?? defaults_js_1.DEFAULTS.httpTimeout), this.pollInterval / 2);
88
+ const badUrl = this.httpUrls.find((u) => !(0, http_js_1.isHttpUrl)(u));
89
+ if (badUrl !== undefined) {
90
+ throw new errors_js_1.NpcConfigError(`--http-urls: "${badUrl}" is not an http(s) URL`);
91
+ }
92
+ if (this.dnsHosts.length === 0 && this.httpUrls.length === 0) {
93
+ throw new errors_js_1.NpcConfigError('internet-connectivity needs at least one DNS host or HTTP URL');
94
+ }
60
95
  // Threshold resolution: explicit thresholds win; else if the legacy
61
96
  // --slow-threshold differs from the default, scale the live thresholds by
62
97
  // that ratio for back-compat; else use the defaults.
@@ -92,115 +127,91 @@ class InternetConnectivityNPC extends polling_npc_js_1.PollingNPC {
92
127
  }
93
128
  async collect() {
94
129
  const [dnsResults, httpResults, latencyResults] = await Promise.all([
95
- (0, checks_js_1.checkDns)(this.dnsHosts),
130
+ (0, checks_js_1.checkDns)(this.dnsHosts, this.httpTimeout),
96
131
  (0, checks_js_1.checkHttp)(this.httpUrls, this.httpTimeout),
97
- (0, checks_js_1.checkLatency)(this.latencyHosts),
132
+ (0, checks_js_1.checkLatency)(this.latencyHosts, this.httpTimeout),
98
133
  ]);
99
- const dnsOk = dnsResults.filter((r) => r.ok).length;
100
- const dnsFail = dnsResults.length - dnsOk;
101
- const httpOk = httpResults.filter((r) => r.ok).length;
102
- const httpFail = httpResults.length - httpOk;
103
- const dnsAvg = avgTime(dnsResults);
104
- const httpAvg = avgTime(httpResults);
105
- const latencyAvg = avgTime(latencyResults);
134
+ const dnsOk = countOk(dnsResults);
135
+ const httpOk = countOk(httpResults);
136
+ const latencyOk = countOk(latencyResults);
137
+ const dnsAvg = avgOkTime(dnsResults);
138
+ const httpAvg = avgOkTime(httpResults);
139
+ const latencyAvg = avgOkTime(latencyResults);
106
140
  const isOnline = dnsOk > 0 || httpOk > 0;
107
- const allFailed = dnsOk === 0 && httpOk === 0;
108
- const anyFailed = dnsResults.some((r) => !r.ok) ||
109
- httpResults.some((r) => !r.ok) ||
110
- latencyResults.some((r) => !r.ok);
111
- const status = evaluateConnectivityStatus({ allFailed, anyFailed, dnsAvg, httpAvg, latencyAvg }, this.thresholds);
112
- const mood = statusToMood(status);
113
- const message = allFailed
114
- ? this.buildOfflineMessage(dnsResults, httpResults)
115
- : this.buildOnlineMessage(dnsAvg, httpAvg, latencyAvg);
116
- (0, screen_js_1.pushHistory)(this.dnsHistory, round(dnsAvg));
117
- (0, screen_js_1.pushHistory)(this.httpHistory, round(httpAvg));
118
- (0, screen_js_1.pushHistory)(this.latencyHistory, round(latencyAvg));
119
- const screenData = {
120
- layout: 'multi-metric',
121
- values: [
122
- {
123
- label: 'DNS',
124
- value: `${round(dnsAvg)}ms`,
125
- color: (0, screen_js_1.statusColor)((0, thresholds_js_1.evaluateThreshold)(dnsAvg, this.thresholds.dnsTime)),
126
- sparkline: [...this.dnsHistory],
127
- },
128
- {
129
- label: 'HTTP',
130
- value: `${round(httpAvg)}ms`,
131
- color: (0, screen_js_1.statusColor)((0, thresholds_js_1.evaluateThreshold)(httpAvg, this.thresholds.httpTime)),
132
- sparkline: [...this.httpHistory],
133
- },
134
- {
135
- label: 'TCP',
136
- value: `${round(latencyAvg)}ms`,
137
- color: (0, screen_js_1.statusColor)((0, thresholds_js_1.evaluateThreshold)(latencyAvg, this.thresholds.latencyTime)),
138
- sparkline: [...this.latencyHistory],
139
- },
140
- { label: 'NET', value: isOnline ? 'ONLINE' : 'OFFLINE', color: isOnline ? 'green' : 'red' },
141
- ],
142
- };
141
+ const anyFailed = dnsOk < dnsResults.length || httpOk < httpResults.length || latencyOk < latencyResults.length;
142
+ const status = evaluateConnectivityStatus({ allFailed: !isOnline, anyFailed, dnsAvg, httpAvg, latencyAvg }, this.thresholds);
143
+ const message = isOnline
144
+ ? `Online | DNS: ${fmtMs(dnsAvg)} | HTTP: ${fmtMs(httpAvg)} | Latency: ${fmtMs(latencyAvg)}` +
145
+ (anyFailed ? ` | ${failureSummary(dnsResults, httpResults, latencyResults)}` : '')
146
+ : offlineMessage(dnsResults, httpResults);
143
147
  return {
144
148
  status,
145
- mood,
149
+ mood: (0, thresholds_js_1.moodForStatus)(status),
146
150
  message,
147
151
  subStatus: isOnline ? undefined : 'offline',
148
- screenData,
152
+ screenData: {
153
+ layout: 'multi-metric',
154
+ values: [
155
+ this.panel('DNS', dnsResults, dnsAvg, this.dnsHistory, this.thresholds.dnsTime),
156
+ this.panel('HTTP', httpResults, httpAvg, this.httpHistory, this.thresholds.httpTime),
157
+ this.panel('TCP', latencyResults, latencyAvg, this.latencyHistory, this.thresholds.latencyTime),
158
+ {
159
+ label: 'NET',
160
+ value: isOnline ? 'ONLINE' : 'OFFLINE',
161
+ color: isOnline ? 'green' : 'red',
162
+ },
163
+ ],
164
+ },
149
165
  metrics: {
150
- dns_avg_ms: round(dnsAvg),
151
- http_avg_ms: round(httpAvg),
152
- latency_avg_ms: round(latencyAvg),
166
+ dns_avg_ms: round(dnsAvg ?? 0),
167
+ http_avg_ms: round(httpAvg ?? 0),
168
+ latency_avg_ms: round(latencyAvg ?? 0),
153
169
  dns_ok_count: dnsOk,
154
- dns_fail_count: dnsFail,
170
+ dns_fail_count: dnsResults.length - dnsOk,
155
171
  http_ok_count: httpOk,
156
- http_fail_count: httpFail,
172
+ http_fail_count: httpResults.length - httpOk,
173
+ latency_ok_count: latencyOk,
174
+ latency_fail_count: latencyResults.length - latencyOk,
157
175
  is_online: isOnline ? 1 : 0,
158
176
  },
159
177
  };
160
178
  }
161
- buildOnlineMessage(dnsAvg, httpAvg, latencyAvg) {
162
- return `Online | DNS: ${round(dnsAvg)}ms | HTTP: ${round(httpAvg)}ms | Latency: ${round(latencyAvg)}ms`;
163
- }
164
- buildOfflineMessage(dnsResults, httpResults) {
165
- const problems = [];
166
- if (dnsResults.every((r) => !r.ok)) {
167
- problems.push('DNS resolution failed');
168
- }
169
- if (httpResults.every((r) => !r.ok)) {
170
- problems.push('no HTTP connectivity');
171
- }
172
- return `OFFLINE: ${problems.join(', ') || 'all checks failed'}`;
179
+ /**
180
+ * One dashboard panel. A category whose checks all failed shows FAIL in red
181
+ * (and a 0 sparkline sample) rather than a misleading latency; a partial
182
+ * failure is amber.
183
+ */
184
+ panel(label, results, avg, history, threshold) {
185
+ if (results.length === 0)
186
+ return { label, value: 'off', color: 'gray' };
187
+ (0, screen_js_1.pushHistory)(history, round(avg ?? 0));
188
+ const sparkline = [...history];
189
+ if (avg === null)
190
+ return { label, value: 'FAIL', color: 'red', sparkline };
191
+ const color = countOk(results) < results.length ? 'amber' : (0, screen_js_1.statusColor)((0, thresholds_js_1.evaluateThreshold)(avg, threshold));
192
+ return { label, value: fmtMs(avg), color, sparkline };
173
193
  }
174
194
  /**
175
- * Build config from CLI flags. Merges with any programmatic overrides.
195
+ * Build config from CLI flags. List flags are comma-separated; latency
196
+ * hosts accept "host", "host:port" or "[ipv6]:port".
176
197
  */
177
198
  static configFromFlags(flags) {
178
199
  const config = {};
179
- const dnsHostsRaw = (0, config_js_1.stringFlag)(flags, 'dns-hosts');
180
- if (dnsHostsRaw) {
181
- config.dnsHosts = dnsHostsRaw.split(',').map((s) => s.trim());
182
- }
183
- const httpUrlsRaw = (0, config_js_1.stringFlag)(flags, 'http-urls');
184
- if (httpUrlsRaw) {
185
- config.httpUrls = httpUrlsRaw.split(',').map((s) => s.trim());
186
- }
187
- const latencyHostsRaw = (0, config_js_1.stringFlag)(flags, 'latency-hosts');
188
- if (latencyHostsRaw) {
189
- config.latencyHosts = latencyHostsRaw.split(',').map((s) => {
190
- const trimmed = s.trim();
191
- const colonIndex = trimmed.lastIndexOf(':');
192
- if (colonIndex !== -1) {
193
- const host = trimmed.slice(0, colonIndex);
194
- const port = Number(trimmed.slice(colonIndex + 1));
195
- if (!Number.isNaN(port))
196
- return { host, port };
197
- }
198
- return { host: trimmed, port: 80 };
199
- });
200
- }
200
+ const dnsHosts = (0, config_js_1.listFlag)(flags, 'dns-hosts');
201
+ if (dnsHosts)
202
+ config.dnsHosts = dnsHosts;
203
+ const httpUrls = (0, config_js_1.listFlag)(flags, 'http-urls');
204
+ if (httpUrls)
205
+ config.httpUrls = httpUrls;
206
+ const latencyHosts = (0, config_js_1.listFlag)(flags, 'latency-hosts');
207
+ if (latencyHosts)
208
+ config.latencyHosts = latencyHosts.map((h) => parseLatencyHost(h));
201
209
  const slowThreshold = (0, config_js_1.numberFlag)(flags, 'slow-threshold');
202
210
  if (slowThreshold !== undefined)
203
211
  config.slowThreshold = slowThreshold;
212
+ const timeout = (0, config_js_1.numberFlag)(flags, 'timeout');
213
+ if (timeout !== undefined)
214
+ config.httpTimeout = timeout;
204
215
  const pollInterval = (0, config_js_1.numberFlag)(flags, 'poll-interval');
205
216
  if (pollInterval !== undefined)
206
217
  config.pollInterval = pollInterval;
@@ -209,43 +220,35 @@ class InternetConnectivityNPC extends polling_npc_js_1.PollingNPC {
209
220
  }
210
221
  exports.InternetConnectivityNPC = InternetConnectivityNPC;
211
222
  // ---------- Helpers ----------
212
- function avgTime(results) {
213
- if (results.length === 0)
214
- return 0;
215
- const sum = results.reduce((acc, r) => acc + r.timeMs, 0);
216
- return sum / results.length;
223
+ function countOk(results) {
224
+ return results.filter((r) => r.ok).length;
225
+ }
226
+ /** Mean time of the successful checks only — a 5s timeout is a failure, not a latency. */
227
+ function avgOkTime(results) {
228
+ const ok = results.filter((r) => r.ok);
229
+ if (ok.length === 0)
230
+ return null;
231
+ return ok.reduce((acc, r) => acc + r.timeMs, 0) / ok.length;
217
232
  }
218
233
  function round(n) {
219
234
  return Math.round(n * 100) / 100;
220
235
  }
221
- function statusToMood(status) {
222
- switch (status) {
223
- case 'healthy':
224
- return 'happy';
225
- case 'warning':
226
- return 'concerned';
227
- case 'critical':
228
- return 'stressed';
229
- default:
230
- return 'idle';
231
- }
236
+ function fmtMs(n) {
237
+ return n === null ? 'FAIL' : `${Math.round(n)}ms`;
232
238
  }
233
- /**
234
- * Normalise latency hosts from either string[] (CLI) or LatencyHost[].
235
- * String entries can be "host" or "host:port".
236
- */
237
- function parseLatencyHosts(input) {
238
- return input.map((item) => {
239
- if (typeof item === 'object')
240
- return item;
241
- const colonIndex = item.lastIndexOf(':');
242
- if (colonIndex !== -1) {
243
- const host = item.slice(0, colonIndex);
244
- const port = Number(item.slice(colonIndex + 1));
245
- if (!Number.isNaN(port))
246
- return { host, port };
247
- }
248
- return { host: item, port: 80 };
249
- });
239
+ /** "failing: 8.8.8.8:443, dns.example" — names the targets, never error text. */
240
+ function failureSummary(...groups) {
241
+ const failed = groups.flat().filter((r) => !r.ok);
242
+ return `failing: ${failed.map((r) => r.target).join(', ')}`;
243
+ }
244
+ function offlineMessage(dnsResults, httpResults) {
245
+ const problems = [];
246
+ if (dnsResults.length > 0 && dnsResults.every((r) => !r.ok)) {
247
+ problems.push('DNS resolution failed');
248
+ }
249
+ if (httpResults.length > 0 && httpResults.every((r) => !r.ok)) {
250
+ problems.push('no HTTP connectivity');
251
+ }
252
+ return `OFFLINE: ${problems.join(', ') || 'all checks failed'}`;
250
253
  }
251
254
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WindowCounter = void 0;
3
4
  exports.classifyLine = classifyLine;
5
+ exports.cleanLogLine = cleanLogLine;
4
6
  exports.pushRecent = pushRecent;
5
7
  exports.buildLogScreen = buildLogScreen;
6
8
  exports.shouldEmitBurst = shouldEmitBurst;
9
+ const text_js_1 = require("../../core/text.js");
10
+ const activity_js_1 = require("../agent-common/activity.js");
7
11
  /** Classify a raw log line against the error/warning patterns. */
8
12
  function classifyLine(line, errorRe, warningRe) {
9
13
  if (errorRe.test(line))
@@ -12,6 +16,23 @@ function classifyLine(line, errorRe, warningRe) {
12
16
  return 'warning';
13
17
  return 'info';
14
18
  }
19
+ // ANSI CSI/OSC escape sequences (colours, cursor moves, hyperlinks).
20
+ // eslint-disable-next-line no-control-regex
21
+ const ANSI = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001b]*(?:\u0007|\u001b\\))/g;
22
+ // `password=…`, `"token": "…"`, `Authorization: …` style pairs common in logs
23
+ // (redactSecrets covers CLI flags, bearer tokens, URL credentials and keys).
24
+ const SECRET_PAIR = /\b([\w-]*(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|authorization|cookie|session)[\w-]*)(["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi;
25
+ /**
26
+ * Prepare a log line for display to the whole team: strip terminal escapes,
27
+ * redact anything secret-looking, drop control characters and truncate.
28
+ * Log lines routinely contain tokens and passwords; only redacted text ever
29
+ * leaves the machine.
30
+ */
31
+ function cleanLogLine(line, max = 200) {
32
+ const plain = line.replace(ANSI, '');
33
+ const redacted = (0, activity_js_1.redactSecrets)(plain).replace(SECRET_PAIR, (_m, key, sep) => `${key}${sep}***`);
34
+ return (0, text_js_1.sanitizeText)(redacted, max);
35
+ }
15
36
  /**
16
37
  * Push a line into the bounded ring buffer: each entry is capped at `cap`
17
38
  * characters and the buffer keeps only the last `max` entries.
@@ -37,4 +58,44 @@ function buildLogScreen(buffer) {
37
58
  function shouldEmitBurst(lastEmit, now, windowMs = 5000) {
38
59
  return now - lastEmit >= windowMs;
39
60
  }
61
+ /**
62
+ * Sliding-window error/warning counter in one-second buckets: memory is
63
+ * bounded by the window length, not by how many lines a runaway logger writes.
64
+ */
65
+ class WindowCounter {
66
+ windowMs;
67
+ buckets = new Map();
68
+ constructor(windowMs) {
69
+ this.windowMs = windowMs;
70
+ }
71
+ add(level, now = Date.now()) {
72
+ const key = Math.floor(now / 1000);
73
+ const bucket = this.buckets.get(key) ?? { errors: 0, warnings: 0 };
74
+ if (level === 'error')
75
+ bucket.errors++;
76
+ else
77
+ bucket.warnings++;
78
+ this.buckets.set(key, bucket);
79
+ if (this.buckets.size > this.windowMs / 1000 + 2)
80
+ this.prune(now);
81
+ }
82
+ counts(now = Date.now()) {
83
+ this.prune(now);
84
+ let errors = 0;
85
+ let warnings = 0;
86
+ for (const b of this.buckets.values()) {
87
+ errors += b.errors;
88
+ warnings += b.warnings;
89
+ }
90
+ return { errors, warnings };
91
+ }
92
+ prune(now) {
93
+ const oldest = Math.floor((now - this.windowMs) / 1000);
94
+ for (const key of this.buckets.keys()) {
95
+ if (key < oldest)
96
+ this.buckets.delete(key);
97
+ }
98
+ }
99
+ }
100
+ exports.WindowCounter = WindowCounter;
40
101
  //# sourceMappingURL=buffer.js.map