@nocville/npc 0.0.0-stage → 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.
Files changed (210) hide show
  1. package/README.md +243 -2
  2. package/dist/cjs/cli.js +533 -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/text.js +15 -0
  11. package/dist/cjs/core/thresholds.js +48 -0
  12. package/dist/cjs/core/types.js +10 -0
  13. package/dist/cjs/core/user-config.js +92 -0
  14. package/dist/cjs/doctor.js +130 -0
  15. package/dist/cjs/index.js +90 -0
  16. package/dist/cjs/npcs/agent-common/activity.js +291 -0
  17. package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
  18. package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
  19. package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
  20. package/dist/cjs/npcs/agent-common/liveness.js +112 -0
  21. package/dist/cjs/npcs/agent-common/lock.js +99 -0
  22. package/dist/cjs/npcs/agent-common/runtime.js +154 -0
  23. package/dist/cjs/npcs/agent-common/session.js +390 -0
  24. package/dist/cjs/npcs/agent-common/shim.js +96 -0
  25. package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
  26. package/dist/cjs/npcs/claude-code/defaults.js +20 -0
  27. package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
  28. package/dist/cjs/npcs/claude-code/index.js +104 -0
  29. package/dist/cjs/npcs/claude-code/setup.js +133 -0
  30. package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
  31. package/dist/cjs/npcs/codex/defaults.js +32 -0
  32. package/dist/cjs/npcs/codex/hook-handler.js +116 -0
  33. package/dist/cjs/npcs/codex/index.js +92 -0
  34. package/dist/cjs/npcs/codex/rollout.js +246 -0
  35. package/dist/cjs/npcs/codex/setup.js +104 -0
  36. package/dist/cjs/npcs/codex/watcher.js +346 -0
  37. package/dist/cjs/npcs/cursor/defaults.js +15 -0
  38. package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
  39. package/dist/cjs/npcs/cursor/index.js +72 -0
  40. package/dist/cjs/npcs/cursor/normalize.js +54 -0
  41. package/dist/cjs/npcs/cursor/setup.js +98 -0
  42. package/dist/cjs/npcs/gemini/defaults.js +15 -0
  43. package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
  44. package/dist/cjs/npcs/gemini/index.js +69 -0
  45. package/dist/cjs/npcs/gemini/setup.js +98 -0
  46. package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
  47. package/dist/cjs/npcs/github-watcher/format.js +21 -0
  48. package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
  49. package/dist/cjs/npcs/github-watcher/index.js +148 -0
  50. package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
  51. package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
  52. package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
  53. package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
  54. package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
  55. package/dist/cjs/npcs/log-watcher/index.js +161 -0
  56. package/dist/cjs/npcs/log-watcher/tail.js +81 -0
  57. package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
  58. package/dist/cjs/npcs/process-watcher/index.js +140 -0
  59. package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
  60. package/dist/cjs/npcs/receptionist/defaults.js +9 -0
  61. package/dist/cjs/npcs/receptionist/index.js +120 -0
  62. package/dist/cjs/npcs/receptionist/weather.js +129 -0
  63. package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
  64. package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
  65. package/dist/cjs/npcs/system-monitor/index.js +105 -0
  66. package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
  67. package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
  68. package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
  69. package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
  70. package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
  71. package/dist/cjs/package.json +1 -0
  72. package/dist/cli.d.ts +3 -0
  73. package/dist/cli.js +498 -0
  74. package/dist/core/api-key.d.ts +34 -0
  75. package/dist/core/api-key.js +64 -0
  76. package/dist/core/base-npc.d.ts +41 -0
  77. package/dist/core/base-npc.js +156 -0
  78. package/dist/core/config.d.ts +22 -0
  79. package/dist/core/config.js +101 -0
  80. package/dist/core/event-npc.d.ts +8 -0
  81. package/dist/core/event-npc.js +10 -0
  82. package/dist/core/logger.d.ts +11 -0
  83. package/dist/core/logger.js +35 -0
  84. package/dist/core/polling-npc.d.ts +18 -0
  85. package/dist/core/polling-npc.js +61 -0
  86. package/dist/core/screen.d.ts +11 -0
  87. package/dist/core/screen.js +24 -0
  88. package/dist/core/text.d.ts +6 -0
  89. package/dist/core/text.js +12 -0
  90. package/dist/core/thresholds.d.ts +10 -0
  91. package/dist/core/thresholds.js +44 -0
  92. package/dist/core/types.d.ts +60 -0
  93. package/dist/core/types.js +7 -0
  94. package/dist/core/user-config.d.ts +43 -0
  95. package/dist/core/user-config.js +85 -0
  96. package/dist/doctor.d.ts +8 -0
  97. package/dist/doctor.js +126 -0
  98. package/dist/index.d.ts +39 -0
  99. package/dist/index.js +25 -0
  100. package/dist/npcs/agent-common/activity.d.ts +42 -0
  101. package/dist/npcs/agent-common/activity.js +280 -0
  102. package/dist/npcs/agent-common/git-stats.d.ts +11 -0
  103. package/dist/npcs/agent-common/git-stats.js +67 -0
  104. package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
  105. package/dist/npcs/agent-common/hook-runner.js +245 -0
  106. package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
  107. package/dist/npcs/agent-common/hook-settings.js +265 -0
  108. package/dist/npcs/agent-common/liveness.d.ts +12 -0
  109. package/dist/npcs/agent-common/liveness.js +105 -0
  110. package/dist/npcs/agent-common/lock.d.ts +6 -0
  111. package/dist/npcs/agent-common/lock.js +95 -0
  112. package/dist/npcs/agent-common/runtime.d.ts +16 -0
  113. package/dist/npcs/agent-common/runtime.js +149 -0
  114. package/dist/npcs/agent-common/session.d.ts +184 -0
  115. package/dist/npcs/agent-common/session.js +368 -0
  116. package/dist/npcs/agent-common/shim.d.ts +4 -0
  117. package/dist/npcs/agent-common/shim.js +90 -0
  118. package/dist/npcs/agent-common/watchdog.d.ts +16 -0
  119. package/dist/npcs/agent-common/watchdog.js +144 -0
  120. package/dist/npcs/claude-code/defaults.d.ts +15 -0
  121. package/dist/npcs/claude-code/defaults.js +17 -0
  122. package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
  123. package/dist/npcs/claude-code/hook-handler.js +131 -0
  124. package/dist/npcs/claude-code/index.d.ts +36 -0
  125. package/dist/npcs/claude-code/index.js +92 -0
  126. package/dist/npcs/claude-code/setup.d.ts +58 -0
  127. package/dist/npcs/claude-code/setup.js +121 -0
  128. package/dist/npcs/claude-code/state-machine.d.ts +28 -0
  129. package/dist/npcs/claude-code/state-machine.js +125 -0
  130. package/dist/npcs/codex/defaults.d.ts +24 -0
  131. package/dist/npcs/codex/defaults.js +28 -0
  132. package/dist/npcs/codex/hook-handler.d.ts +24 -0
  133. package/dist/npcs/codex/hook-handler.js +110 -0
  134. package/dist/npcs/codex/index.d.ts +21 -0
  135. package/dist/npcs/codex/index.js +76 -0
  136. package/dist/npcs/codex/rollout.d.ts +39 -0
  137. package/dist/npcs/codex/rollout.js +242 -0
  138. package/dist/npcs/codex/setup.d.ts +37 -0
  139. package/dist/npcs/codex/setup.js +96 -0
  140. package/dist/npcs/codex/watcher.d.ts +62 -0
  141. package/dist/npcs/codex/watcher.js +341 -0
  142. package/dist/npcs/cursor/defaults.d.ts +9 -0
  143. package/dist/npcs/cursor/defaults.js +11 -0
  144. package/dist/npcs/cursor/hook-handler.d.ts +8 -0
  145. package/dist/npcs/cursor/hook-handler.js +60 -0
  146. package/dist/npcs/cursor/index.d.ts +18 -0
  147. package/dist/npcs/cursor/index.js +57 -0
  148. package/dist/npcs/cursor/normalize.d.ts +6 -0
  149. package/dist/npcs/cursor/normalize.js +49 -0
  150. package/dist/npcs/cursor/setup.d.ts +29 -0
  151. package/dist/npcs/cursor/setup.js +90 -0
  152. package/dist/npcs/gemini/defaults.d.ts +9 -0
  153. package/dist/npcs/gemini/defaults.js +11 -0
  154. package/dist/npcs/gemini/hook-handler.d.ts +10 -0
  155. package/dist/npcs/gemini/hook-handler.js +87 -0
  156. package/dist/npcs/gemini/index.d.ts +17 -0
  157. package/dist/npcs/gemini/index.js +56 -0
  158. package/dist/npcs/gemini/setup.d.ts +29 -0
  159. package/dist/npcs/gemini/setup.js +90 -0
  160. package/dist/npcs/github-watcher/defaults.d.ts +8 -0
  161. package/dist/npcs/github-watcher/defaults.js +8 -0
  162. package/dist/npcs/github-watcher/format.d.ts +3 -0
  163. package/dist/npcs/github-watcher/format.js +18 -0
  164. package/dist/npcs/github-watcher/github-api.d.ts +55 -0
  165. package/dist/npcs/github-watcher/github-api.js +120 -0
  166. package/dist/npcs/github-watcher/index.d.ts +35 -0
  167. package/dist/npcs/github-watcher/index.js +144 -0
  168. package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
  169. package/dist/npcs/internet-connectivity/checks.js +84 -0
  170. package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
  171. package/dist/npcs/internet-connectivity/defaults.js +19 -0
  172. package/dist/npcs/internet-connectivity/index.d.ts +55 -0
  173. package/dist/npcs/internet-connectivity/index.js +246 -0
  174. package/dist/npcs/log-watcher/buffer.d.ts +18 -0
  175. package/dist/npcs/log-watcher/buffer.js +34 -0
  176. package/dist/npcs/log-watcher/defaults.d.ts +9 -0
  177. package/dist/npcs/log-watcher/defaults.js +9 -0
  178. package/dist/npcs/log-watcher/index.d.ts +45 -0
  179. package/dist/npcs/log-watcher/index.js +157 -0
  180. package/dist/npcs/log-watcher/tail.d.ts +9 -0
  181. package/dist/npcs/log-watcher/tail.js +78 -0
  182. package/dist/npcs/process-watcher/defaults.d.ts +6 -0
  183. package/dist/npcs/process-watcher/defaults.js +6 -0
  184. package/dist/npcs/process-watcher/index.d.ts +26 -0
  185. package/dist/npcs/process-watcher/index.js +136 -0
  186. package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
  187. package/dist/npcs/process-watcher/process-scanner.js +110 -0
  188. package/dist/npcs/receptionist/defaults.d.ts +6 -0
  189. package/dist/npcs/receptionist/defaults.js +6 -0
  190. package/dist/npcs/receptionist/index.d.ts +29 -0
  191. package/dist/npcs/receptionist/index.js +116 -0
  192. package/dist/npcs/receptionist/weather.d.ts +37 -0
  193. package/dist/npcs/receptionist/weather.js +121 -0
  194. package/dist/npcs/system-monitor/collectors.d.ts +45 -0
  195. package/dist/npcs/system-monitor/collectors.js +60 -0
  196. package/dist/npcs/system-monitor/defaults.d.ts +7 -0
  197. package/dist/npcs/system-monitor/defaults.js +10 -0
  198. package/dist/npcs/system-monitor/index.d.ts +27 -0
  199. package/dist/npcs/system-monitor/index.js +101 -0
  200. package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
  201. package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
  202. package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
  203. package/dist/npcs/web-page-watcher/capture.js +75 -0
  204. package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
  205. package/dist/npcs/web-page-watcher/defaults.js +27 -0
  206. package/dist/npcs/web-page-watcher/index.d.ts +42 -0
  207. package/dist/npcs/web-page-watcher/index.js +230 -0
  208. package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
  209. package/dist/npcs/web-page-watcher/pixelate.js +172 -0
  210. package/package.json +73 -3
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubWatcherNPC = void 0;
4
+ const polling_npc_js_1 = require("../../core/polling-npc.js");
5
+ const text_js_1 = require("../../core/text.js");
6
+ const defaults_js_1 = require("./defaults.js");
7
+ const github_api_js_1 = require("./github-api.js");
8
+ const format_js_1 = require("./format.js");
9
+ class GitHubWatcherNPC extends polling_npc_js_1.PollingNPC {
10
+ repo;
11
+ token;
12
+ branch;
13
+ fetchFn;
14
+ etag = null;
15
+ lastSeenSha = null;
16
+ commits = [];
17
+ rateRemaining = null;
18
+ newCommitPollsLeft = 0;
19
+ constructor(config) {
20
+ super({ ...config, pollInterval: config.pollInterval ?? defaults_js_1.DEFAULTS.pollInterval });
21
+ this.repo = config.repo;
22
+ this.token = config.token;
23
+ this.branch = config.branch;
24
+ this.fetchFn = config.fetchFn;
25
+ }
26
+ get name() {
27
+ return 'github-watcher';
28
+ }
29
+ get defaultDisplayName() {
30
+ const repoName = this.repo.split('/').pop() || this.repo;
31
+ return `GitHub: ${repoName}`;
32
+ }
33
+ get archetype() {
34
+ return 'pipeline';
35
+ }
36
+ get companion() {
37
+ return {
38
+ screenType: 'commit-board',
39
+ size: { w: 3, h: 2 },
40
+ placement: 'adjacent-wall',
41
+ label: this.repo,
42
+ };
43
+ }
44
+ async onStart() {
45
+ // Resolve the default branch once if the user didn't pin one.
46
+ if (!this.branch) {
47
+ const def = await (0, github_api_js_1.fetchDefaultBranch)(this.repo, {
48
+ token: this.token,
49
+ timeoutMs: defaults_js_1.DEFAULTS.fetchTimeoutMs,
50
+ fetchFn: this.fetchFn,
51
+ });
52
+ if (def) {
53
+ this.branch = def;
54
+ this.logger.info(`Watching default branch "${def}"`);
55
+ }
56
+ }
57
+ await super.onStart();
58
+ }
59
+ async collect() {
60
+ const result = await (0, github_api_js_1.fetchCommits)(this.repo, {
61
+ token: this.token,
62
+ etag: this.etag,
63
+ branch: this.branch,
64
+ timeoutMs: defaults_js_1.DEFAULTS.fetchTimeoutMs,
65
+ perPage: defaults_js_1.DEFAULTS.perPage,
66
+ fetchFn: this.fetchFn,
67
+ });
68
+ if (result.rateRemaining !== null)
69
+ this.rateRemaining = result.rateRemaining;
70
+ if (result.status === 403) {
71
+ return this.report('warning', 'concerned', 'GitHub API rate-limited (403)', 'rate-limited');
72
+ }
73
+ if (result.status === 404) {
74
+ return this.report('warning', 'concerned', `Repo not found: ${this.repo}`, 'not-found');
75
+ }
76
+ if (result.status === 0 && this.commits.length === 0) {
77
+ return this.report('warning', 'concerned', 'GitHub API unreachable', 'unreachable');
78
+ }
79
+ if (result.notModified) {
80
+ return this.reportIdle();
81
+ }
82
+ if (result.commits.length > 0) {
83
+ const fresh = (0, github_api_js_1.newCommitsSince)(result.commits, this.lastSeenSha);
84
+ this.commits = result.commits;
85
+ this.etag = result.etag ?? this.etag;
86
+ if (this.lastSeenSha === null) {
87
+ this.lastSeenSha = result.commits[0].sha;
88
+ return this.reportIdle();
89
+ }
90
+ if (fresh.length > 0) {
91
+ this.lastSeenSha = result.commits[0].sha;
92
+ this.newCommitPollsLeft = defaults_js_1.DEFAULTS.newCommitPolls;
93
+ return this.reportNewCommits(fresh);
94
+ }
95
+ }
96
+ return this.reportIdle();
97
+ }
98
+ reportNewCommits(fresh) {
99
+ const newest = fresh[0];
100
+ const latest = `${newest.author}: ${(0, text_js_1.truncate)(newest.message, 120)} (${newest.sha.slice(0, 7)})`;
101
+ const message = fresh.length > 1 ? `${fresh.length} new commits — latest: ${latest}` : latest;
102
+ return this.report('healthy', 'happy', message, 'new-commit');
103
+ }
104
+ reportIdle() {
105
+ if (this.newCommitPollsLeft > 0)
106
+ this.newCommitPollsLeft--;
107
+ const subStatus = this.newCommitPollsLeft > 0 ? 'new-commit' : undefined;
108
+ const newest = this.commits[0];
109
+ const message = newest
110
+ ? `Last: ${(0, text_js_1.truncate)(newest.message, 80)} (${(0, format_js_1.relativeTime)(newest.date)})`
111
+ : `Watching ${this.repo}`;
112
+ return this.report('healthy', 'happy', message, subStatus);
113
+ }
114
+ report(status, mood, message, subStatus) {
115
+ const newest = this.commits[0];
116
+ const secondsSinceLast = newest && newest.date
117
+ ? Math.max(0, Math.round((Date.now() - Date.parse(newest.date)) / 1000))
118
+ : 0;
119
+ return {
120
+ status,
121
+ mood,
122
+ message,
123
+ subStatus,
124
+ screenData: this.buildBoard(),
125
+ metrics: {
126
+ seconds_since_last_commit: secondsSinceLast,
127
+ commits_last_24h: (0, github_api_js_1.commitsWithin)(this.commits, 24),
128
+ api_rate_remaining: this.rateRemaining ?? -1,
129
+ },
130
+ };
131
+ }
132
+ buildBoard() {
133
+ const now = Date.now();
134
+ const header = this.branch ? `${this.repo}@${this.branch}` : this.repo;
135
+ const values = [{ label: 'repo', value: header }];
136
+ for (const c of this.commits.slice(0, 8)) {
137
+ const recent = c.date && now - Date.parse(c.date) < 3600_000;
138
+ values.push({
139
+ label: c.sha.slice(0, 7),
140
+ value: (0, text_js_1.truncate)(`${c.author}: ${c.message}`, 60),
141
+ color: recent ? 'green' : 'gray',
142
+ });
143
+ }
144
+ return { layout: 'commit-board', values };
145
+ }
146
+ }
147
+ exports.GitHubWatcherNPC = GitHubWatcherNPC;
148
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkDns = checkDns;
7
+ exports.checkHttp = checkHttp;
8
+ exports.checkLatency = checkLatency;
9
+ const node_dns_1 = require("node:dns");
10
+ const node_net_1 = __importDefault(require("node:net"));
11
+ async function checkDns(hosts) {
12
+ return Promise.all(hosts.map(resolveSingleDns));
13
+ }
14
+ async function resolveSingleDns(host) {
15
+ const start = performance.now();
16
+ try {
17
+ await node_dns_1.promises.resolve(host);
18
+ return { host, ok: true, timeMs: performance.now() - start };
19
+ }
20
+ catch (err) {
21
+ return {
22
+ host,
23
+ ok: false,
24
+ timeMs: performance.now() - start,
25
+ error: err instanceof Error ? err.message : String(err),
26
+ };
27
+ }
28
+ }
29
+ async function checkHttp(urls, timeout) {
30
+ return Promise.all(urls.map((url) => fetchSingleUrl(url, timeout)));
31
+ }
32
+ async function fetchSingleUrl(url, timeout) {
33
+ const controller = new AbortController();
34
+ const timer = setTimeout(() => controller.abort(), timeout);
35
+ const start = performance.now();
36
+ try {
37
+ const response = await fetch(url, { signal: controller.signal });
38
+ // Consume body to ensure connection is completed
39
+ await response.text();
40
+ return {
41
+ url,
42
+ ok: response.ok,
43
+ timeMs: performance.now() - start,
44
+ statusCode: response.status,
45
+ };
46
+ }
47
+ catch (err) {
48
+ return {
49
+ url,
50
+ ok: false,
51
+ timeMs: performance.now() - start,
52
+ error: err instanceof Error ? err.message : String(err),
53
+ };
54
+ }
55
+ finally {
56
+ clearTimeout(timer);
57
+ }
58
+ }
59
+ async function checkLatency(hosts) {
60
+ return Promise.all(hosts.map(measureTcpConnect));
61
+ }
62
+ function measureTcpConnect(target) {
63
+ return new Promise((resolve) => {
64
+ const start = performance.now();
65
+ const socket = node_net_1.default.connect({ host: target.host, port: target.port });
66
+ const timeout = setTimeout(() => {
67
+ socket.destroy();
68
+ resolve({
69
+ host: target.host,
70
+ ok: false,
71
+ timeMs: performance.now() - start,
72
+ error: 'Connection timed out',
73
+ });
74
+ }, 5000);
75
+ socket.on('connect', () => {
76
+ clearTimeout(timeout);
77
+ const timeMs = performance.now() - start;
78
+ socket.destroy();
79
+ resolve({ host: target.host, ok: true, timeMs });
80
+ });
81
+ socket.on('error', (err) => {
82
+ clearTimeout(timeout);
83
+ resolve({
84
+ host: target.host,
85
+ ok: false,
86
+ timeMs: performance.now() - start,
87
+ error: err.message,
88
+ });
89
+ });
90
+ });
91
+ }
92
+ //# sourceMappingURL=checks.js.map
@@ -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