@nocville/npc 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +268 -2
  2. package/dist/cjs/cli.js +584 -0
  3. package/dist/cjs/core/api-key.js +69 -0
  4. package/dist/cjs/core/base-npc.js +160 -0
  5. package/dist/cjs/core/config.js +108 -0
  6. package/dist/cjs/core/event-npc.js +14 -0
  7. package/dist/cjs/core/logger.js +39 -0
  8. package/dist/cjs/core/polling-npc.js +65 -0
  9. package/dist/cjs/core/screen.js +28 -0
  10. package/dist/cjs/core/session-filter.js +150 -0
  11. package/dist/cjs/core/text.js +15 -0
  12. package/dist/cjs/core/thresholds.js +48 -0
  13. package/dist/cjs/core/types.js +10 -0
  14. package/dist/cjs/core/user-config.js +92 -0
  15. package/dist/cjs/doctor.js +130 -0
  16. package/dist/cjs/index.js +95 -0
  17. package/dist/cjs/npcs/agent-common/activity.js +291 -0
  18. package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
  19. package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
  20. package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
  21. package/dist/cjs/npcs/agent-common/liveness.js +112 -0
  22. package/dist/cjs/npcs/agent-common/lock.js +99 -0
  23. package/dist/cjs/npcs/agent-common/runtime.js +154 -0
  24. package/dist/cjs/npcs/agent-common/session.js +412 -0
  25. package/dist/cjs/npcs/agent-common/shim.js +96 -0
  26. package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
  27. package/dist/cjs/npcs/claude-code/defaults.js +20 -0
  28. package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
  29. package/dist/cjs/npcs/claude-code/index.js +104 -0
  30. package/dist/cjs/npcs/claude-code/setup.js +133 -0
  31. package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
  32. package/dist/cjs/npcs/codex/defaults.js +32 -0
  33. package/dist/cjs/npcs/codex/hook-handler.js +116 -0
  34. package/dist/cjs/npcs/codex/index.js +92 -0
  35. package/dist/cjs/npcs/codex/rollout.js +246 -0
  36. package/dist/cjs/npcs/codex/setup.js +104 -0
  37. package/dist/cjs/npcs/codex/watcher.js +361 -0
  38. package/dist/cjs/npcs/cursor/defaults.js +15 -0
  39. package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
  40. package/dist/cjs/npcs/cursor/index.js +72 -0
  41. package/dist/cjs/npcs/cursor/normalize.js +54 -0
  42. package/dist/cjs/npcs/cursor/setup.js +98 -0
  43. package/dist/cjs/npcs/gemini/defaults.js +15 -0
  44. package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
  45. package/dist/cjs/npcs/gemini/index.js +69 -0
  46. package/dist/cjs/npcs/gemini/setup.js +98 -0
  47. package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
  48. package/dist/cjs/npcs/github-watcher/format.js +21 -0
  49. package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
  50. package/dist/cjs/npcs/github-watcher/index.js +148 -0
  51. package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
  52. package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
  53. package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
  54. package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
  55. package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
  56. package/dist/cjs/npcs/log-watcher/index.js +161 -0
  57. package/dist/cjs/npcs/log-watcher/tail.js +81 -0
  58. package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
  59. package/dist/cjs/npcs/process-watcher/index.js +140 -0
  60. package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
  61. package/dist/cjs/npcs/receptionist/defaults.js +9 -0
  62. package/dist/cjs/npcs/receptionist/index.js +120 -0
  63. package/dist/cjs/npcs/receptionist/weather.js +129 -0
  64. package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
  65. package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
  66. package/dist/cjs/npcs/system-monitor/index.js +105 -0
  67. package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
  68. package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
  69. package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
  70. package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
  71. package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
  72. package/dist/cjs/package.json +1 -0
  73. package/dist/cli.d.ts +3 -0
  74. package/dist/cli.js +549 -0
  75. package/dist/core/api-key.d.ts +34 -0
  76. package/dist/core/api-key.js +64 -0
  77. package/dist/core/base-npc.d.ts +41 -0
  78. package/dist/core/base-npc.js +156 -0
  79. package/dist/core/config.d.ts +22 -0
  80. package/dist/core/config.js +101 -0
  81. package/dist/core/event-npc.d.ts +8 -0
  82. package/dist/core/event-npc.js +10 -0
  83. package/dist/core/logger.d.ts +11 -0
  84. package/dist/core/logger.js +35 -0
  85. package/dist/core/polling-npc.d.ts +18 -0
  86. package/dist/core/polling-npc.js +61 -0
  87. package/dist/core/screen.d.ts +11 -0
  88. package/dist/core/screen.js +24 -0
  89. package/dist/core/session-filter.d.ts +59 -0
  90. package/dist/core/session-filter.js +143 -0
  91. package/dist/core/text.d.ts +6 -0
  92. package/dist/core/text.js +12 -0
  93. package/dist/core/thresholds.d.ts +10 -0
  94. package/dist/core/thresholds.js +44 -0
  95. package/dist/core/types.d.ts +60 -0
  96. package/dist/core/types.js +7 -0
  97. package/dist/core/user-config.d.ts +43 -0
  98. package/dist/core/user-config.js +85 -0
  99. package/dist/doctor.d.ts +8 -0
  100. package/dist/doctor.js +126 -0
  101. package/dist/index.d.ts +41 -0
  102. package/dist/index.js +26 -0
  103. package/dist/npcs/agent-common/activity.d.ts +42 -0
  104. package/dist/npcs/agent-common/activity.js +280 -0
  105. package/dist/npcs/agent-common/git-stats.d.ts +11 -0
  106. package/dist/npcs/agent-common/git-stats.js +67 -0
  107. package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
  108. package/dist/npcs/agent-common/hook-runner.js +245 -0
  109. package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
  110. package/dist/npcs/agent-common/hook-settings.js +265 -0
  111. package/dist/npcs/agent-common/liveness.d.ts +12 -0
  112. package/dist/npcs/agent-common/liveness.js +105 -0
  113. package/dist/npcs/agent-common/lock.d.ts +6 -0
  114. package/dist/npcs/agent-common/lock.js +95 -0
  115. package/dist/npcs/agent-common/runtime.d.ts +16 -0
  116. package/dist/npcs/agent-common/runtime.js +149 -0
  117. package/dist/npcs/agent-common/session.d.ts +192 -0
  118. package/dist/npcs/agent-common/session.js +390 -0
  119. package/dist/npcs/agent-common/shim.d.ts +4 -0
  120. package/dist/npcs/agent-common/shim.js +90 -0
  121. package/dist/npcs/agent-common/watchdog.d.ts +16 -0
  122. package/dist/npcs/agent-common/watchdog.js +144 -0
  123. package/dist/npcs/claude-code/defaults.d.ts +15 -0
  124. package/dist/npcs/claude-code/defaults.js +17 -0
  125. package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
  126. package/dist/npcs/claude-code/hook-handler.js +131 -0
  127. package/dist/npcs/claude-code/index.d.ts +36 -0
  128. package/dist/npcs/claude-code/index.js +92 -0
  129. package/dist/npcs/claude-code/setup.d.ts +58 -0
  130. package/dist/npcs/claude-code/setup.js +121 -0
  131. package/dist/npcs/claude-code/state-machine.d.ts +28 -0
  132. package/dist/npcs/claude-code/state-machine.js +125 -0
  133. package/dist/npcs/codex/defaults.d.ts +24 -0
  134. package/dist/npcs/codex/defaults.js +28 -0
  135. package/dist/npcs/codex/hook-handler.d.ts +24 -0
  136. package/dist/npcs/codex/hook-handler.js +110 -0
  137. package/dist/npcs/codex/index.d.ts +21 -0
  138. package/dist/npcs/codex/index.js +76 -0
  139. package/dist/npcs/codex/rollout.d.ts +39 -0
  140. package/dist/npcs/codex/rollout.js +242 -0
  141. package/dist/npcs/codex/setup.d.ts +37 -0
  142. package/dist/npcs/codex/setup.js +96 -0
  143. package/dist/npcs/codex/watcher.d.ts +65 -0
  144. package/dist/npcs/codex/watcher.js +356 -0
  145. package/dist/npcs/cursor/defaults.d.ts +9 -0
  146. package/dist/npcs/cursor/defaults.js +11 -0
  147. package/dist/npcs/cursor/hook-handler.d.ts +8 -0
  148. package/dist/npcs/cursor/hook-handler.js +60 -0
  149. package/dist/npcs/cursor/index.d.ts +18 -0
  150. package/dist/npcs/cursor/index.js +57 -0
  151. package/dist/npcs/cursor/normalize.d.ts +6 -0
  152. package/dist/npcs/cursor/normalize.js +49 -0
  153. package/dist/npcs/cursor/setup.d.ts +29 -0
  154. package/dist/npcs/cursor/setup.js +90 -0
  155. package/dist/npcs/gemini/defaults.d.ts +9 -0
  156. package/dist/npcs/gemini/defaults.js +11 -0
  157. package/dist/npcs/gemini/hook-handler.d.ts +10 -0
  158. package/dist/npcs/gemini/hook-handler.js +87 -0
  159. package/dist/npcs/gemini/index.d.ts +17 -0
  160. package/dist/npcs/gemini/index.js +56 -0
  161. package/dist/npcs/gemini/setup.d.ts +29 -0
  162. package/dist/npcs/gemini/setup.js +90 -0
  163. package/dist/npcs/github-watcher/defaults.d.ts +8 -0
  164. package/dist/npcs/github-watcher/defaults.js +8 -0
  165. package/dist/npcs/github-watcher/format.d.ts +3 -0
  166. package/dist/npcs/github-watcher/format.js +18 -0
  167. package/dist/npcs/github-watcher/github-api.d.ts +55 -0
  168. package/dist/npcs/github-watcher/github-api.js +120 -0
  169. package/dist/npcs/github-watcher/index.d.ts +35 -0
  170. package/dist/npcs/github-watcher/index.js +144 -0
  171. package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
  172. package/dist/npcs/internet-connectivity/checks.js +84 -0
  173. package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
  174. package/dist/npcs/internet-connectivity/defaults.js +19 -0
  175. package/dist/npcs/internet-connectivity/index.d.ts +55 -0
  176. package/dist/npcs/internet-connectivity/index.js +246 -0
  177. package/dist/npcs/log-watcher/buffer.d.ts +18 -0
  178. package/dist/npcs/log-watcher/buffer.js +34 -0
  179. package/dist/npcs/log-watcher/defaults.d.ts +9 -0
  180. package/dist/npcs/log-watcher/defaults.js +9 -0
  181. package/dist/npcs/log-watcher/index.d.ts +45 -0
  182. package/dist/npcs/log-watcher/index.js +157 -0
  183. package/dist/npcs/log-watcher/tail.d.ts +9 -0
  184. package/dist/npcs/log-watcher/tail.js +78 -0
  185. package/dist/npcs/process-watcher/defaults.d.ts +6 -0
  186. package/dist/npcs/process-watcher/defaults.js +6 -0
  187. package/dist/npcs/process-watcher/index.d.ts +26 -0
  188. package/dist/npcs/process-watcher/index.js +136 -0
  189. package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
  190. package/dist/npcs/process-watcher/process-scanner.js +110 -0
  191. package/dist/npcs/receptionist/defaults.d.ts +6 -0
  192. package/dist/npcs/receptionist/defaults.js +6 -0
  193. package/dist/npcs/receptionist/index.d.ts +29 -0
  194. package/dist/npcs/receptionist/index.js +116 -0
  195. package/dist/npcs/receptionist/weather.d.ts +37 -0
  196. package/dist/npcs/receptionist/weather.js +121 -0
  197. package/dist/npcs/system-monitor/collectors.d.ts +45 -0
  198. package/dist/npcs/system-monitor/collectors.js +60 -0
  199. package/dist/npcs/system-monitor/defaults.d.ts +7 -0
  200. package/dist/npcs/system-monitor/defaults.js +10 -0
  201. package/dist/npcs/system-monitor/index.d.ts +27 -0
  202. package/dist/npcs/system-monitor/index.js +101 -0
  203. package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
  204. package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
  205. package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
  206. package/dist/npcs/web-page-watcher/capture.js +75 -0
  207. package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
  208. package/dist/npcs/web-page-watcher/defaults.js +27 -0
  209. package/dist/npcs/web-page-watcher/index.d.ts +42 -0
  210. package/dist/npcs/web-page-watcher/index.js +230 -0
  211. package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
  212. package/dist/npcs/web-page-watcher/pixelate.js +172 -0
  213. package/package.json +73 -3
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULTS = void 0;
4
+ exports.DEFAULTS = {
5
+ pollInterval: 30000, // 30 seconds
6
+ dnsHosts: ['google.com', 'cloudflare.com'],
7
+ // gstatic generate_204 is a stable, low-latency 204 endpoint used by many
8
+ // OSes for connectivity checks (replaces the flaky httpbin.org default).
9
+ httpUrls: ['https://1.1.1.1/cdn-cgi/trace', 'https://www.gstatic.com/generate_204'],
10
+ latencyHosts: [
11
+ { host: '8.8.8.8', port: 80 },
12
+ { host: '1.1.1.1', port: 80 },
13
+ ],
14
+ slowThreshold: 1000, // ms
15
+ httpTimeout: 5000, // ms
16
+ thresholds: {
17
+ dnsTime: { warning: 500, critical: 2000, direction: 'above' },
18
+ httpTime: { warning: 1000, critical: 3000, direction: 'above' },
19
+ latencyTime: { warning: 300, critical: 1000, direction: 'above' },
20
+ },
21
+ };
22
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InternetConnectivityNPC = void 0;
4
+ exports.evaluateConnectivityStatus = evaluateConnectivityStatus;
5
+ const polling_npc_js_1 = require("../../core/polling-npc.js");
6
+ const thresholds_js_1 = require("../../core/thresholds.js");
7
+ const screen_js_1 = require("../../core/screen.js");
8
+ const config_js_1 = require("../../core/config.js");
9
+ const checks_js_1 = require("./checks.js");
10
+ const defaults_js_1 = require("./defaults.js");
11
+ /**
12
+ * Derive overall status from live per-check thresholds. All checks failing is
13
+ * critical; any partial failure is at least a warning; slow-but-working checks
14
+ * escalate per their own thresholds.
15
+ */
16
+ function evaluateConnectivityStatus(sample, thresholds) {
17
+ if (sample.allFailed)
18
+ 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]);
26
+ }
27
+ /** Scale a threshold set by a ratio, preserving direction. */
28
+ function scaleThresholds(base, ratio) {
29
+ const scale = (t) => ({
30
+ warning: Math.round(t.warning * ratio),
31
+ critical: Math.round(t.critical * ratio),
32
+ direction: t.direction,
33
+ });
34
+ return {
35
+ dnsTime: scale(base.dnsTime),
36
+ httpTime: scale(base.httpTime),
37
+ latencyTime: scale(base.latencyTime),
38
+ };
39
+ }
40
+ class InternetConnectivityNPC extends polling_npc_js_1.PollingNPC {
41
+ dnsHosts;
42
+ httpUrls;
43
+ latencyHosts;
44
+ httpTimeout;
45
+ thresholds;
46
+ dnsHistory = [];
47
+ httpHistory = [];
48
+ latencyHistory = [];
49
+ constructor(config) {
50
+ super({
51
+ ...config,
52
+ pollInterval: config.pollInterval ?? defaults_js_1.DEFAULTS.pollInterval,
53
+ });
54
+ this.dnsHosts = config.dnsHosts ?? defaults_js_1.DEFAULTS.dnsHosts;
55
+ 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;
60
+ // Threshold resolution: explicit thresholds win; else if the legacy
61
+ // --slow-threshold differs from the default, scale the live thresholds by
62
+ // that ratio for back-compat; else use the defaults.
63
+ if (config.thresholds) {
64
+ this.thresholds = config.thresholds;
65
+ }
66
+ else if (config.slowThreshold && config.slowThreshold !== defaults_js_1.DEFAULTS.slowThreshold) {
67
+ this.thresholds = scaleThresholds(defaults_js_1.DEFAULTS.thresholds, config.slowThreshold / defaults_js_1.DEFAULTS.slowThreshold);
68
+ }
69
+ else {
70
+ this.thresholds = defaults_js_1.DEFAULTS.thresholds;
71
+ }
72
+ }
73
+ get name() {
74
+ return 'internet-connectivity';
75
+ }
76
+ get defaultDisplayName() {
77
+ return 'Internet';
78
+ }
79
+ get archetype() {
80
+ return 'gateway';
81
+ }
82
+ get behavior() {
83
+ return { idle: 'wander' };
84
+ }
85
+ get companion() {
86
+ return {
87
+ screenType: 'dashboard-wall',
88
+ size: { w: 2, h: 2 },
89
+ placement: 'adjacent-wall',
90
+ label: 'Internet',
91
+ };
92
+ }
93
+ async collect() {
94
+ const [dnsResults, httpResults, latencyResults] = await Promise.all([
95
+ (0, checks_js_1.checkDns)(this.dnsHosts),
96
+ (0, checks_js_1.checkHttp)(this.httpUrls, this.httpTimeout),
97
+ (0, checks_js_1.checkLatency)(this.latencyHosts),
98
+ ]);
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);
106
+ 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
+ };
143
+ return {
144
+ status,
145
+ mood,
146
+ message,
147
+ subStatus: isOnline ? undefined : 'offline',
148
+ screenData,
149
+ metrics: {
150
+ dns_avg_ms: round(dnsAvg),
151
+ http_avg_ms: round(httpAvg),
152
+ latency_avg_ms: round(latencyAvg),
153
+ dns_ok_count: dnsOk,
154
+ dns_fail_count: dnsFail,
155
+ http_ok_count: httpOk,
156
+ http_fail_count: httpFail,
157
+ is_online: isOnline ? 1 : 0,
158
+ },
159
+ };
160
+ }
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'}`;
173
+ }
174
+ /**
175
+ * Build config from CLI flags. Merges with any programmatic overrides.
176
+ */
177
+ static configFromFlags(flags) {
178
+ 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
+ }
201
+ const slowThreshold = (0, config_js_1.numberFlag)(flags, 'slow-threshold');
202
+ if (slowThreshold !== undefined)
203
+ config.slowThreshold = slowThreshold;
204
+ const pollInterval = (0, config_js_1.numberFlag)(flags, 'poll-interval');
205
+ if (pollInterval !== undefined)
206
+ config.pollInterval = pollInterval;
207
+ return config;
208
+ }
209
+ }
210
+ exports.InternetConnectivityNPC = InternetConnectivityNPC;
211
+ // ---------- 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;
217
+ }
218
+ function round(n) {
219
+ return Math.round(n * 100) / 100;
220
+ }
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
+ }
232
+ }
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
+ });
250
+ }
251
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyLine = classifyLine;
4
+ exports.pushRecent = pushRecent;
5
+ exports.buildLogScreen = buildLogScreen;
6
+ exports.shouldEmitBurst = shouldEmitBurst;
7
+ /** Classify a raw log line against the error/warning patterns. */
8
+ function classifyLine(line, errorRe, warningRe) {
9
+ if (errorRe.test(line))
10
+ return 'error';
11
+ if (warningRe.test(line))
12
+ return 'warning';
13
+ return 'info';
14
+ }
15
+ /**
16
+ * Push a line into the bounded ring buffer: each entry is capped at `cap`
17
+ * characters and the buffer keeps only the last `max` entries.
18
+ */
19
+ function pushRecent(buffer, line, level, max = 12, cap = 80) {
20
+ buffer.push({ text: line.slice(0, cap), level });
21
+ if (buffer.length > max)
22
+ buffer.splice(0, buffer.length - max);
23
+ return buffer;
24
+ }
25
+ /** Build a log-terminal screen payload from the ring buffer, colored per level. */
26
+ function buildLogScreen(buffer) {
27
+ return {
28
+ layout: 'log-terminal',
29
+ values: buffer.map((l) => ({
30
+ label: '',
31
+ value: l.text,
32
+ color: l.level === 'error' ? 'red' : l.level === 'warning' ? 'amber' : 'green',
33
+ })),
34
+ };
35
+ }
36
+ /** True when enough time has elapsed since the last burst push to emit again. */
37
+ function shouldEmitBurst(lastEmit, now, windowMs = 5000) {
38
+ return now - lastEmit >= windowMs;
39
+ }
40
+ //# sourceMappingURL=buffer.js.map
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULTS = void 0;
4
+ exports.DEFAULTS = {
5
+ reportInterval: 15000, // 15 seconds
6
+ windowSeconds: 300, // 5 minutes
7
+ errorPattern: 'ERROR|FATAL',
8
+ warningPattern: 'WARN',
9
+ errorWarn: 5, // errors in window before warning status
10
+ errorCrit: 20, // errors in window before critical status
11
+ };
12
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogWatcherNPC = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const base_npc_js_1 = require("../../core/base-npc.js");
6
+ const thresholds_js_1 = require("../../core/thresholds.js");
7
+ const text_js_1 = require("../../core/text.js");
8
+ const tail_js_1 = require("./tail.js");
9
+ const buffer_js_1 = require("./buffer.js");
10
+ const defaults_js_1 = require("./defaults.js");
11
+ class LogWatcherNPC extends base_npc_js_1.BaseNPC {
12
+ tailers = [];
13
+ timer = null;
14
+ entries = [];
15
+ recentLines = [];
16
+ lastBurstPush = 0;
17
+ errorRegex;
18
+ warningRegex;
19
+ windowMs;
20
+ errorWarn;
21
+ errorCrit;
22
+ reportInterval;
23
+ files;
24
+ constructor(config) {
25
+ super(config);
26
+ this.files = config.files;
27
+ this.errorRegex = new RegExp(config.errorPattern ?? defaults_js_1.DEFAULTS.errorPattern);
28
+ this.warningRegex = new RegExp(config.warningPattern ?? defaults_js_1.DEFAULTS.warningPattern);
29
+ this.windowMs = (config.windowSeconds ?? defaults_js_1.DEFAULTS.windowSeconds) * 1000;
30
+ this.errorWarn = config.errorWarn ?? defaults_js_1.DEFAULTS.errorWarn;
31
+ this.errorCrit = config.errorCrit ?? defaults_js_1.DEFAULTS.errorCrit;
32
+ this.reportInterval = config.reportInterval ?? defaults_js_1.DEFAULTS.reportInterval;
33
+ }
34
+ get name() {
35
+ return 'log-watcher';
36
+ }
37
+ get defaultDisplayName() {
38
+ const first = this.files[0];
39
+ return first ? `Logs: ${(0, node_path_1.basename)(first)}` : 'Logs';
40
+ }
41
+ get archetype() {
42
+ return 'service';
43
+ }
44
+ get heartbeatSeconds() {
45
+ return 30;
46
+ }
47
+ get behavior() {
48
+ return { idle: 'wander' };
49
+ }
50
+ get companion() {
51
+ return {
52
+ screenType: 'log-terminal',
53
+ size: { w: 2, h: 2 },
54
+ placement: 'adjacent-wall',
55
+ label: this.files[0] ? (0, node_path_1.basename)(this.files[0]) : 'log',
56
+ };
57
+ }
58
+ async onStart() {
59
+ for (const filePath of this.files) {
60
+ this.logger.info(`Tailing ${filePath}`);
61
+ const tailer = (0, tail_js_1.createTailer)(filePath, (line) => {
62
+ this.handleLine(line);
63
+ });
64
+ this.tailers.push(tailer);
65
+ }
66
+ // Report immediately, then on interval
67
+ await this.report();
68
+ this.timer = setInterval(() => {
69
+ void this.report();
70
+ }, this.reportInterval);
71
+ }
72
+ async onStop() {
73
+ if (this.timer) {
74
+ clearInterval(this.timer);
75
+ this.timer = null;
76
+ }
77
+ for (const tailer of this.tailers) {
78
+ tailer.close();
79
+ }
80
+ this.tailers = [];
81
+ }
82
+ handleLine(line) {
83
+ const now = Date.now();
84
+ const level = (0, buffer_js_1.classifyLine)(line, this.errorRegex, this.warningRegex);
85
+ // Ring buffer holds ALL lines (matched ones flagged) for the log TV.
86
+ (0, buffer_js_1.pushRecent)(this.recentLines, line, level);
87
+ if (level === 'error') {
88
+ this.entries.push({ timestamp: now, type: 'error', message: line });
89
+ this.onErrorBurst(line);
90
+ }
91
+ else if (level === 'warning') {
92
+ this.entries.push({ timestamp: now, type: 'warning', message: line });
93
+ }
94
+ }
95
+ /** A fresh error triggers an immediate push, debounced to at most once / 5s. */
96
+ onErrorBurst(line) {
97
+ const now = Date.now();
98
+ if (!(0, buffer_js_1.shouldEmitBurst)(this.lastBurstPush, now))
99
+ return;
100
+ this.lastBurstPush = now;
101
+ void this.pushReport((0, text_js_1.truncate)(line, 200), 'error');
102
+ }
103
+ pruneWindow() {
104
+ const cutoff = Date.now() - this.windowMs;
105
+ this.entries = this.entries.filter((e) => e.timestamp >= cutoff);
106
+ }
107
+ async report() {
108
+ if (this.state !== 'running')
109
+ return;
110
+ await this.pushReport();
111
+ }
112
+ /**
113
+ * Build and push the current status. If `overrideMessage` is set (burst push),
114
+ * it drives the bubble and the status floors at warning.
115
+ */
116
+ async pushReport(overrideMessage, subStatus) {
117
+ if (this.state !== 'running')
118
+ return;
119
+ this.pruneWindow();
120
+ const errorCount = this.entries.filter((e) => e.type === 'error').length;
121
+ const warningCount = this.entries.filter((e) => e.type === 'warning').length;
122
+ const windowSeconds = this.windowMs / 1000;
123
+ let status = (0, thresholds_js_1.evaluateThreshold)(errorCount, {
124
+ warning: this.errorWarn,
125
+ critical: this.errorCrit,
126
+ direction: 'above',
127
+ });
128
+ if (overrideMessage)
129
+ status = (0, thresholds_js_1.worstStatus)([status, 'warning']);
130
+ const mood = status === 'critical' ? 'stressed' : status === 'warning' ? 'concerned' : 'happy';
131
+ const lastError = this.entries.filter((e) => e.type === 'error').at(-1);
132
+ const message = overrideMessage ??
133
+ (errorCount > 0
134
+ ? `${errorCount} error${errorCount !== 1 ? 's' : ''} in last ${windowSeconds / 60}min across ${this.files.length} file${this.files.length !== 1 ? 's' : ''}`
135
+ : `No errors in last ${windowSeconds / 60}min`);
136
+ const metrics = {
137
+ status,
138
+ mood,
139
+ message,
140
+ subStatus,
141
+ screenData: (0, buffer_js_1.buildLogScreen)(this.recentLines),
142
+ metrics: {
143
+ error_count: errorCount,
144
+ warning_count: warningCount,
145
+ window_seconds: windowSeconds,
146
+ files_watched: this.files.length,
147
+ last_error_message: lastError?.message ?? '',
148
+ },
149
+ };
150
+ try {
151
+ await this.pushMetrics(metrics);
152
+ }
153
+ catch (err) {
154
+ this.logger.error('Failed to push metrics', {
155
+ error: err instanceof Error ? err.message : String(err),
156
+ });
157
+ }
158
+ }
159
+ }
160
+ exports.LogWatcherNPC = LogWatcherNPC;
161
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTailer = createTailer;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_readline_1 = require("node:readline");
6
+ /**
7
+ * Tail a file by watching for changes and reading new lines from the last
8
+ * known byte offset. Handles file rotation (size shrinks -> reset to 0).
9
+ */
10
+ function createTailer(filePath, onLine) {
11
+ let offset = 0;
12
+ let reading = false;
13
+ let watcher = null;
14
+ let closed = false;
15
+ async function init() {
16
+ // Start at the end of the file so we only read new lines
17
+ try {
18
+ const stats = await node_fs_1.promises.stat(filePath);
19
+ offset = stats.size;
20
+ }
21
+ catch {
22
+ // File may not exist yet; start at 0
23
+ offset = 0;
24
+ }
25
+ watcher = (0, node_fs_1.watch)(filePath, () => {
26
+ if (!closed) {
27
+ void readNewLines();
28
+ }
29
+ });
30
+ // Ignore watcher errors (e.g. file deleted then recreated)
31
+ watcher.on('error', () => { });
32
+ }
33
+ async function readNewLines() {
34
+ if (reading || closed)
35
+ return;
36
+ reading = true;
37
+ try {
38
+ const stats = await node_fs_1.promises.stat(filePath);
39
+ // File rotation detected: size smaller than our offset
40
+ if (stats.size < offset) {
41
+ offset = 0;
42
+ }
43
+ // Nothing new to read
44
+ if (stats.size === offset) {
45
+ reading = false;
46
+ return;
47
+ }
48
+ const stream = (0, node_fs_1.createReadStream)(filePath, {
49
+ start: offset,
50
+ encoding: 'utf-8',
51
+ });
52
+ const rl = (0, node_readline_1.createInterface)({ input: stream, crlfDelay: Infinity });
53
+ let bytesRead = 0;
54
+ for await (const line of rl) {
55
+ if (closed)
56
+ break;
57
+ // Account for line content + newline character
58
+ bytesRead += Buffer.byteLength(line, 'utf-8') + 1;
59
+ onLine(line);
60
+ }
61
+ offset += bytesRead;
62
+ }
63
+ catch {
64
+ // File may have been removed; ignore and wait for next change
65
+ }
66
+ finally {
67
+ reading = false;
68
+ }
69
+ }
70
+ void init();
71
+ return {
72
+ close() {
73
+ closed = true;
74
+ if (watcher) {
75
+ watcher.close();
76
+ watcher = null;
77
+ }
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=tail.js.map
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULTS = void 0;
4
+ exports.DEFAULTS = {
5
+ pollInterval: 15000, // 15 seconds
6
+ cpuWarn: 80, // percent
7
+ memWarn: 500, // MB
8
+ };
9
+ //# sourceMappingURL=defaults.js.map