@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.
- package/README.md +243 -2
- package/dist/cjs/cli.js +533 -0
- package/dist/cjs/core/api-key.js +69 -0
- package/dist/cjs/core/base-npc.js +160 -0
- package/dist/cjs/core/config.js +108 -0
- package/dist/cjs/core/event-npc.js +14 -0
- package/dist/cjs/core/logger.js +39 -0
- package/dist/cjs/core/polling-npc.js +65 -0
- package/dist/cjs/core/screen.js +28 -0
- package/dist/cjs/core/text.js +15 -0
- package/dist/cjs/core/thresholds.js +48 -0
- package/dist/cjs/core/types.js +10 -0
- package/dist/cjs/core/user-config.js +92 -0
- package/dist/cjs/doctor.js +130 -0
- package/dist/cjs/index.js +90 -0
- package/dist/cjs/npcs/agent-common/activity.js +291 -0
- package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
- package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
- package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
- package/dist/cjs/npcs/agent-common/liveness.js +112 -0
- package/dist/cjs/npcs/agent-common/lock.js +99 -0
- package/dist/cjs/npcs/agent-common/runtime.js +154 -0
- package/dist/cjs/npcs/agent-common/session.js +390 -0
- package/dist/cjs/npcs/agent-common/shim.js +96 -0
- package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
- package/dist/cjs/npcs/claude-code/defaults.js +20 -0
- package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
- package/dist/cjs/npcs/claude-code/index.js +104 -0
- package/dist/cjs/npcs/claude-code/setup.js +133 -0
- package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
- package/dist/cjs/npcs/codex/defaults.js +32 -0
- package/dist/cjs/npcs/codex/hook-handler.js +116 -0
- package/dist/cjs/npcs/codex/index.js +92 -0
- package/dist/cjs/npcs/codex/rollout.js +246 -0
- package/dist/cjs/npcs/codex/setup.js +104 -0
- package/dist/cjs/npcs/codex/watcher.js +346 -0
- package/dist/cjs/npcs/cursor/defaults.js +15 -0
- package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
- package/dist/cjs/npcs/cursor/index.js +72 -0
- package/dist/cjs/npcs/cursor/normalize.js +54 -0
- package/dist/cjs/npcs/cursor/setup.js +98 -0
- package/dist/cjs/npcs/gemini/defaults.js +15 -0
- package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
- package/dist/cjs/npcs/gemini/index.js +69 -0
- package/dist/cjs/npcs/gemini/setup.js +98 -0
- package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
- package/dist/cjs/npcs/github-watcher/format.js +21 -0
- package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
- package/dist/cjs/npcs/github-watcher/index.js +148 -0
- package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
- package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
- package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
- package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
- package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
- package/dist/cjs/npcs/log-watcher/index.js +161 -0
- package/dist/cjs/npcs/log-watcher/tail.js +81 -0
- package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
- package/dist/cjs/npcs/process-watcher/index.js +140 -0
- package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
- package/dist/cjs/npcs/receptionist/defaults.js +9 -0
- package/dist/cjs/npcs/receptionist/index.js +120 -0
- package/dist/cjs/npcs/receptionist/weather.js +129 -0
- package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
- package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
- package/dist/cjs/npcs/system-monitor/index.js +105 -0
- package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
- package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
- package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
- package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
- package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +498 -0
- package/dist/core/api-key.d.ts +34 -0
- package/dist/core/api-key.js +64 -0
- package/dist/core/base-npc.d.ts +41 -0
- package/dist/core/base-npc.js +156 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +101 -0
- package/dist/core/event-npc.d.ts +8 -0
- package/dist/core/event-npc.js +10 -0
- package/dist/core/logger.d.ts +11 -0
- package/dist/core/logger.js +35 -0
- package/dist/core/polling-npc.d.ts +18 -0
- package/dist/core/polling-npc.js +61 -0
- package/dist/core/screen.d.ts +11 -0
- package/dist/core/screen.js +24 -0
- package/dist/core/text.d.ts +6 -0
- package/dist/core/text.js +12 -0
- package/dist/core/thresholds.d.ts +10 -0
- package/dist/core/thresholds.js +44 -0
- package/dist/core/types.d.ts +60 -0
- package/dist/core/types.js +7 -0
- package/dist/core/user-config.d.ts +43 -0
- package/dist/core/user-config.js +85 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.js +126 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +25 -0
- package/dist/npcs/agent-common/activity.d.ts +42 -0
- package/dist/npcs/agent-common/activity.js +280 -0
- package/dist/npcs/agent-common/git-stats.d.ts +11 -0
- package/dist/npcs/agent-common/git-stats.js +67 -0
- package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
- package/dist/npcs/agent-common/hook-runner.js +245 -0
- package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
- package/dist/npcs/agent-common/hook-settings.js +265 -0
- package/dist/npcs/agent-common/liveness.d.ts +12 -0
- package/dist/npcs/agent-common/liveness.js +105 -0
- package/dist/npcs/agent-common/lock.d.ts +6 -0
- package/dist/npcs/agent-common/lock.js +95 -0
- package/dist/npcs/agent-common/runtime.d.ts +16 -0
- package/dist/npcs/agent-common/runtime.js +149 -0
- package/dist/npcs/agent-common/session.d.ts +184 -0
- package/dist/npcs/agent-common/session.js +368 -0
- package/dist/npcs/agent-common/shim.d.ts +4 -0
- package/dist/npcs/agent-common/shim.js +90 -0
- package/dist/npcs/agent-common/watchdog.d.ts +16 -0
- package/dist/npcs/agent-common/watchdog.js +144 -0
- package/dist/npcs/claude-code/defaults.d.ts +15 -0
- package/dist/npcs/claude-code/defaults.js +17 -0
- package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
- package/dist/npcs/claude-code/hook-handler.js +131 -0
- package/dist/npcs/claude-code/index.d.ts +36 -0
- package/dist/npcs/claude-code/index.js +92 -0
- package/dist/npcs/claude-code/setup.d.ts +58 -0
- package/dist/npcs/claude-code/setup.js +121 -0
- package/dist/npcs/claude-code/state-machine.d.ts +28 -0
- package/dist/npcs/claude-code/state-machine.js +125 -0
- package/dist/npcs/codex/defaults.d.ts +24 -0
- package/dist/npcs/codex/defaults.js +28 -0
- package/dist/npcs/codex/hook-handler.d.ts +24 -0
- package/dist/npcs/codex/hook-handler.js +110 -0
- package/dist/npcs/codex/index.d.ts +21 -0
- package/dist/npcs/codex/index.js +76 -0
- package/dist/npcs/codex/rollout.d.ts +39 -0
- package/dist/npcs/codex/rollout.js +242 -0
- package/dist/npcs/codex/setup.d.ts +37 -0
- package/dist/npcs/codex/setup.js +96 -0
- package/dist/npcs/codex/watcher.d.ts +62 -0
- package/dist/npcs/codex/watcher.js +341 -0
- package/dist/npcs/cursor/defaults.d.ts +9 -0
- package/dist/npcs/cursor/defaults.js +11 -0
- package/dist/npcs/cursor/hook-handler.d.ts +8 -0
- package/dist/npcs/cursor/hook-handler.js +60 -0
- package/dist/npcs/cursor/index.d.ts +18 -0
- package/dist/npcs/cursor/index.js +57 -0
- package/dist/npcs/cursor/normalize.d.ts +6 -0
- package/dist/npcs/cursor/normalize.js +49 -0
- package/dist/npcs/cursor/setup.d.ts +29 -0
- package/dist/npcs/cursor/setup.js +90 -0
- package/dist/npcs/gemini/defaults.d.ts +9 -0
- package/dist/npcs/gemini/defaults.js +11 -0
- package/dist/npcs/gemini/hook-handler.d.ts +10 -0
- package/dist/npcs/gemini/hook-handler.js +87 -0
- package/dist/npcs/gemini/index.d.ts +17 -0
- package/dist/npcs/gemini/index.js +56 -0
- package/dist/npcs/gemini/setup.d.ts +29 -0
- package/dist/npcs/gemini/setup.js +90 -0
- package/dist/npcs/github-watcher/defaults.d.ts +8 -0
- package/dist/npcs/github-watcher/defaults.js +8 -0
- package/dist/npcs/github-watcher/format.d.ts +3 -0
- package/dist/npcs/github-watcher/format.js +18 -0
- package/dist/npcs/github-watcher/github-api.d.ts +55 -0
- package/dist/npcs/github-watcher/github-api.js +120 -0
- package/dist/npcs/github-watcher/index.d.ts +35 -0
- package/dist/npcs/github-watcher/index.js +144 -0
- package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
- package/dist/npcs/internet-connectivity/checks.js +84 -0
- package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
- package/dist/npcs/internet-connectivity/defaults.js +19 -0
- package/dist/npcs/internet-connectivity/index.d.ts +55 -0
- package/dist/npcs/internet-connectivity/index.js +246 -0
- package/dist/npcs/log-watcher/buffer.d.ts +18 -0
- package/dist/npcs/log-watcher/buffer.js +34 -0
- package/dist/npcs/log-watcher/defaults.d.ts +9 -0
- package/dist/npcs/log-watcher/defaults.js +9 -0
- package/dist/npcs/log-watcher/index.d.ts +45 -0
- package/dist/npcs/log-watcher/index.js +157 -0
- package/dist/npcs/log-watcher/tail.d.ts +9 -0
- package/dist/npcs/log-watcher/tail.js +78 -0
- package/dist/npcs/process-watcher/defaults.d.ts +6 -0
- package/dist/npcs/process-watcher/defaults.js +6 -0
- package/dist/npcs/process-watcher/index.d.ts +26 -0
- package/dist/npcs/process-watcher/index.js +136 -0
- package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
- package/dist/npcs/process-watcher/process-scanner.js +110 -0
- package/dist/npcs/receptionist/defaults.d.ts +6 -0
- package/dist/npcs/receptionist/defaults.js +6 -0
- package/dist/npcs/receptionist/index.d.ts +29 -0
- package/dist/npcs/receptionist/index.js +116 -0
- package/dist/npcs/receptionist/weather.d.ts +37 -0
- package/dist/npcs/receptionist/weather.js +121 -0
- package/dist/npcs/system-monitor/collectors.d.ts +45 -0
- package/dist/npcs/system-monitor/collectors.js +60 -0
- package/dist/npcs/system-monitor/defaults.d.ts +7 -0
- package/dist/npcs/system-monitor/defaults.js +10 -0
- package/dist/npcs/system-monitor/index.d.ts +27 -0
- package/dist/npcs/system-monitor/index.js +101 -0
- package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
- package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
- package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
- package/dist/npcs/web-page-watcher/capture.js +75 -0
- package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
- package/dist/npcs/web-page-watcher/defaults.js +27 -0
- package/dist/npcs/web-page-watcher/index.d.ts +42 -0
- package/dist/npcs/web-page-watcher/index.js +230 -0
- package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
- package/dist/npcs/web-page-watcher/pixelate.js +172 -0
- package/package.json +73 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { PollingNPC } from '../../core/polling-npc.js';
|
|
2
|
+
import { truncate } from '../../core/text.js';
|
|
3
|
+
import { DEFAULTS } from './defaults.js';
|
|
4
|
+
import { fetchCommits, fetchDefaultBranch, newCommitsSince, commitsWithin } from './github-api.js';
|
|
5
|
+
import { relativeTime } from './format.js';
|
|
6
|
+
export class GitHubWatcherNPC extends PollingNPC {
|
|
7
|
+
repo;
|
|
8
|
+
token;
|
|
9
|
+
branch;
|
|
10
|
+
fetchFn;
|
|
11
|
+
etag = null;
|
|
12
|
+
lastSeenSha = null;
|
|
13
|
+
commits = [];
|
|
14
|
+
rateRemaining = null;
|
|
15
|
+
newCommitPollsLeft = 0;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super({ ...config, pollInterval: config.pollInterval ?? DEFAULTS.pollInterval });
|
|
18
|
+
this.repo = config.repo;
|
|
19
|
+
this.token = config.token;
|
|
20
|
+
this.branch = config.branch;
|
|
21
|
+
this.fetchFn = config.fetchFn;
|
|
22
|
+
}
|
|
23
|
+
get name() {
|
|
24
|
+
return 'github-watcher';
|
|
25
|
+
}
|
|
26
|
+
get defaultDisplayName() {
|
|
27
|
+
const repoName = this.repo.split('/').pop() || this.repo;
|
|
28
|
+
return `GitHub: ${repoName}`;
|
|
29
|
+
}
|
|
30
|
+
get archetype() {
|
|
31
|
+
return 'pipeline';
|
|
32
|
+
}
|
|
33
|
+
get companion() {
|
|
34
|
+
return {
|
|
35
|
+
screenType: 'commit-board',
|
|
36
|
+
size: { w: 3, h: 2 },
|
|
37
|
+
placement: 'adjacent-wall',
|
|
38
|
+
label: this.repo,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async onStart() {
|
|
42
|
+
// Resolve the default branch once if the user didn't pin one.
|
|
43
|
+
if (!this.branch) {
|
|
44
|
+
const def = await fetchDefaultBranch(this.repo, {
|
|
45
|
+
token: this.token,
|
|
46
|
+
timeoutMs: DEFAULTS.fetchTimeoutMs,
|
|
47
|
+
fetchFn: this.fetchFn,
|
|
48
|
+
});
|
|
49
|
+
if (def) {
|
|
50
|
+
this.branch = def;
|
|
51
|
+
this.logger.info(`Watching default branch "${def}"`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await super.onStart();
|
|
55
|
+
}
|
|
56
|
+
async collect() {
|
|
57
|
+
const result = await fetchCommits(this.repo, {
|
|
58
|
+
token: this.token,
|
|
59
|
+
etag: this.etag,
|
|
60
|
+
branch: this.branch,
|
|
61
|
+
timeoutMs: DEFAULTS.fetchTimeoutMs,
|
|
62
|
+
perPage: DEFAULTS.perPage,
|
|
63
|
+
fetchFn: this.fetchFn,
|
|
64
|
+
});
|
|
65
|
+
if (result.rateRemaining !== null)
|
|
66
|
+
this.rateRemaining = result.rateRemaining;
|
|
67
|
+
if (result.status === 403) {
|
|
68
|
+
return this.report('warning', 'concerned', 'GitHub API rate-limited (403)', 'rate-limited');
|
|
69
|
+
}
|
|
70
|
+
if (result.status === 404) {
|
|
71
|
+
return this.report('warning', 'concerned', `Repo not found: ${this.repo}`, 'not-found');
|
|
72
|
+
}
|
|
73
|
+
if (result.status === 0 && this.commits.length === 0) {
|
|
74
|
+
return this.report('warning', 'concerned', 'GitHub API unreachable', 'unreachable');
|
|
75
|
+
}
|
|
76
|
+
if (result.notModified) {
|
|
77
|
+
return this.reportIdle();
|
|
78
|
+
}
|
|
79
|
+
if (result.commits.length > 0) {
|
|
80
|
+
const fresh = newCommitsSince(result.commits, this.lastSeenSha);
|
|
81
|
+
this.commits = result.commits;
|
|
82
|
+
this.etag = result.etag ?? this.etag;
|
|
83
|
+
if (this.lastSeenSha === null) {
|
|
84
|
+
this.lastSeenSha = result.commits[0].sha;
|
|
85
|
+
return this.reportIdle();
|
|
86
|
+
}
|
|
87
|
+
if (fresh.length > 0) {
|
|
88
|
+
this.lastSeenSha = result.commits[0].sha;
|
|
89
|
+
this.newCommitPollsLeft = DEFAULTS.newCommitPolls;
|
|
90
|
+
return this.reportNewCommits(fresh);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return this.reportIdle();
|
|
94
|
+
}
|
|
95
|
+
reportNewCommits(fresh) {
|
|
96
|
+
const newest = fresh[0];
|
|
97
|
+
const latest = `${newest.author}: ${truncate(newest.message, 120)} (${newest.sha.slice(0, 7)})`;
|
|
98
|
+
const message = fresh.length > 1 ? `${fresh.length} new commits — latest: ${latest}` : latest;
|
|
99
|
+
return this.report('healthy', 'happy', message, 'new-commit');
|
|
100
|
+
}
|
|
101
|
+
reportIdle() {
|
|
102
|
+
if (this.newCommitPollsLeft > 0)
|
|
103
|
+
this.newCommitPollsLeft--;
|
|
104
|
+
const subStatus = this.newCommitPollsLeft > 0 ? 'new-commit' : undefined;
|
|
105
|
+
const newest = this.commits[0];
|
|
106
|
+
const message = newest
|
|
107
|
+
? `Last: ${truncate(newest.message, 80)} (${relativeTime(newest.date)})`
|
|
108
|
+
: `Watching ${this.repo}`;
|
|
109
|
+
return this.report('healthy', 'happy', message, subStatus);
|
|
110
|
+
}
|
|
111
|
+
report(status, mood, message, subStatus) {
|
|
112
|
+
const newest = this.commits[0];
|
|
113
|
+
const secondsSinceLast = newest && newest.date
|
|
114
|
+
? Math.max(0, Math.round((Date.now() - Date.parse(newest.date)) / 1000))
|
|
115
|
+
: 0;
|
|
116
|
+
return {
|
|
117
|
+
status,
|
|
118
|
+
mood,
|
|
119
|
+
message,
|
|
120
|
+
subStatus,
|
|
121
|
+
screenData: this.buildBoard(),
|
|
122
|
+
metrics: {
|
|
123
|
+
seconds_since_last_commit: secondsSinceLast,
|
|
124
|
+
commits_last_24h: commitsWithin(this.commits, 24),
|
|
125
|
+
api_rate_remaining: this.rateRemaining ?? -1,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
buildBoard() {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const header = this.branch ? `${this.repo}@${this.branch}` : this.repo;
|
|
132
|
+
const values = [{ label: 'repo', value: header }];
|
|
133
|
+
for (const c of this.commits.slice(0, 8)) {
|
|
134
|
+
const recent = c.date && now - Date.parse(c.date) < 3600_000;
|
|
135
|
+
values.push({
|
|
136
|
+
label: c.sha.slice(0, 7),
|
|
137
|
+
value: truncate(`${c.author}: ${c.message}`, 60),
|
|
138
|
+
color: recent ? 'green' : 'gray',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return { layout: 'commit-board', values };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LatencyHost } from './defaults.js';
|
|
2
|
+
export interface DnsCheckResult {
|
|
3
|
+
host: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
timeMs: number;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function checkDns(hosts: string[]): Promise<DnsCheckResult[]>;
|
|
9
|
+
export interface HttpCheckResult {
|
|
10
|
+
url: string;
|
|
11
|
+
ok: boolean;
|
|
12
|
+
timeMs: number;
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function checkHttp(urls: string[], timeout: number): Promise<HttpCheckResult[]>;
|
|
17
|
+
export interface LatencyCheckResult {
|
|
18
|
+
host: string;
|
|
19
|
+
ok: boolean;
|
|
20
|
+
timeMs: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function checkLatency(hosts: LatencyHost[]): Promise<LatencyCheckResult[]>;
|
|
24
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { promises as dns } from 'node:dns';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
export async function checkDns(hosts) {
|
|
4
|
+
return Promise.all(hosts.map(resolveSingleDns));
|
|
5
|
+
}
|
|
6
|
+
async function resolveSingleDns(host) {
|
|
7
|
+
const start = performance.now();
|
|
8
|
+
try {
|
|
9
|
+
await dns.resolve(host);
|
|
10
|
+
return { host, ok: true, timeMs: performance.now() - start };
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return {
|
|
14
|
+
host,
|
|
15
|
+
ok: false,
|
|
16
|
+
timeMs: performance.now() - start,
|
|
17
|
+
error: err instanceof Error ? err.message : String(err),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function checkHttp(urls, timeout) {
|
|
22
|
+
return Promise.all(urls.map((url) => fetchSingleUrl(url, timeout)));
|
|
23
|
+
}
|
|
24
|
+
async function fetchSingleUrl(url, timeout) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
30
|
+
// Consume body to ensure connection is completed
|
|
31
|
+
await response.text();
|
|
32
|
+
return {
|
|
33
|
+
url,
|
|
34
|
+
ok: response.ok,
|
|
35
|
+
timeMs: performance.now() - start,
|
|
36
|
+
statusCode: response.status,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
url,
|
|
42
|
+
ok: false,
|
|
43
|
+
timeMs: performance.now() - start,
|
|
44
|
+
error: err instanceof Error ? err.message : String(err),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function checkLatency(hosts) {
|
|
52
|
+
return Promise.all(hosts.map(measureTcpConnect));
|
|
53
|
+
}
|
|
54
|
+
function measureTcpConnect(target) {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
const socket = net.connect({ host: target.host, port: target.port });
|
|
58
|
+
const timeout = setTimeout(() => {
|
|
59
|
+
socket.destroy();
|
|
60
|
+
resolve({
|
|
61
|
+
host: target.host,
|
|
62
|
+
ok: false,
|
|
63
|
+
timeMs: performance.now() - start,
|
|
64
|
+
error: 'Connection timed out',
|
|
65
|
+
});
|
|
66
|
+
}, 5000);
|
|
67
|
+
socket.on('connect', () => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
const timeMs = performance.now() - start;
|
|
70
|
+
socket.destroy();
|
|
71
|
+
resolve({ host: target.host, ok: true, timeMs });
|
|
72
|
+
});
|
|
73
|
+
socket.on('error', (err) => {
|
|
74
|
+
clearTimeout(timeout);
|
|
75
|
+
resolve({
|
|
76
|
+
host: target.host,
|
|
77
|
+
ok: false,
|
|
78
|
+
timeMs: performance.now() - start,
|
|
79
|
+
error: err.message,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ThresholdConfig } from '../../core/types.js';
|
|
2
|
+
export interface LatencyHost {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
}
|
|
6
|
+
export interface InternetConnectivityDefaults {
|
|
7
|
+
pollInterval: number;
|
|
8
|
+
dnsHosts: string[];
|
|
9
|
+
httpUrls: string[];
|
|
10
|
+
latencyHosts: LatencyHost[];
|
|
11
|
+
slowThreshold: number;
|
|
12
|
+
httpTimeout: number;
|
|
13
|
+
thresholds: {
|
|
14
|
+
dnsTime: ThresholdConfig;
|
|
15
|
+
httpTime: ThresholdConfig;
|
|
16
|
+
latencyTime: ThresholdConfig;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const DEFAULTS: InternetConnectivityDefaults;
|
|
20
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
pollInterval: 30000, // 30 seconds
|
|
3
|
+
dnsHosts: ['google.com', 'cloudflare.com'],
|
|
4
|
+
// gstatic generate_204 is a stable, low-latency 204 endpoint used by many
|
|
5
|
+
// OSes for connectivity checks (replaces the flaky httpbin.org default).
|
|
6
|
+
httpUrls: ['https://1.1.1.1/cdn-cgi/trace', 'https://www.gstatic.com/generate_204'],
|
|
7
|
+
latencyHosts: [
|
|
8
|
+
{ host: '8.8.8.8', port: 80 },
|
|
9
|
+
{ host: '1.1.1.1', port: 80 },
|
|
10
|
+
],
|
|
11
|
+
slowThreshold: 1000, // ms
|
|
12
|
+
httpTimeout: 5000, // ms
|
|
13
|
+
thresholds: {
|
|
14
|
+
dnsTime: { warning: 500, critical: 2000, direction: 'above' },
|
|
15
|
+
httpTime: { warning: 1000, critical: 3000, direction: 'above' },
|
|
16
|
+
latencyTime: { warning: 300, critical: 1000, direction: 'above' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PollingNPC } from '../../core/polling-npc.js';
|
|
2
|
+
import type { PollingNpcConfig } from '../../core/polling-npc.js';
|
|
3
|
+
import type { CollectedMetrics, Status, ThresholdConfig, Archetype, BehaviorHints, CompanionDecl } from '../../core/types.js';
|
|
4
|
+
import type { LatencyHost } from './defaults.js';
|
|
5
|
+
export interface ConnectivityThresholds {
|
|
6
|
+
dnsTime: ThresholdConfig;
|
|
7
|
+
httpTime: ThresholdConfig;
|
|
8
|
+
latencyTime: ThresholdConfig;
|
|
9
|
+
}
|
|
10
|
+
export interface InternetConnectivityConfig extends PollingNpcConfig {
|
|
11
|
+
dnsHosts?: string[];
|
|
12
|
+
httpUrls?: string[];
|
|
13
|
+
latencyHosts?: LatencyHost[] | string[];
|
|
14
|
+
slowThreshold?: number;
|
|
15
|
+
httpTimeout?: number;
|
|
16
|
+
thresholds?: ConnectivityThresholds;
|
|
17
|
+
}
|
|
18
|
+
interface ConnectivitySample {
|
|
19
|
+
allFailed: boolean;
|
|
20
|
+
anyFailed: boolean;
|
|
21
|
+
dnsAvg: number;
|
|
22
|
+
httpAvg: number;
|
|
23
|
+
latencyAvg: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Derive overall status from live per-check thresholds. All checks failing is
|
|
27
|
+
* critical; any partial failure is at least a warning; slow-but-working checks
|
|
28
|
+
* escalate per their own thresholds.
|
|
29
|
+
*/
|
|
30
|
+
export declare function evaluateConnectivityStatus(sample: ConnectivitySample, thresholds: ConnectivityThresholds): Status;
|
|
31
|
+
export declare class InternetConnectivityNPC extends PollingNPC {
|
|
32
|
+
private dnsHosts;
|
|
33
|
+
private httpUrls;
|
|
34
|
+
private latencyHosts;
|
|
35
|
+
private httpTimeout;
|
|
36
|
+
private thresholds;
|
|
37
|
+
private dnsHistory;
|
|
38
|
+
private httpHistory;
|
|
39
|
+
private latencyHistory;
|
|
40
|
+
constructor(config: InternetConnectivityConfig);
|
|
41
|
+
get name(): string;
|
|
42
|
+
protected get defaultDisplayName(): string;
|
|
43
|
+
get archetype(): Archetype;
|
|
44
|
+
get behavior(): BehaviorHints;
|
|
45
|
+
get companion(): CompanionDecl;
|
|
46
|
+
protected collect(): Promise<CollectedMetrics>;
|
|
47
|
+
private buildOnlineMessage;
|
|
48
|
+
private buildOfflineMessage;
|
|
49
|
+
/**
|
|
50
|
+
* Build config from CLI flags. Merges with any programmatic overrides.
|
|
51
|
+
*/
|
|
52
|
+
static configFromFlags(flags: Record<string, string | boolean>): Partial<InternetConnectivityConfig>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { PollingNPC } from '../../core/polling-npc.js';
|
|
2
|
+
import { evaluateThreshold, worstStatus } from '../../core/thresholds.js';
|
|
3
|
+
import { statusColor, pushHistory } from '../../core/screen.js';
|
|
4
|
+
import { numberFlag, stringFlag } from '../../core/config.js';
|
|
5
|
+
import { checkDns, checkHttp, checkLatency } from './checks.js';
|
|
6
|
+
import { DEFAULTS } from './defaults.js';
|
|
7
|
+
/**
|
|
8
|
+
* Derive overall status from live per-check thresholds. All checks failing is
|
|
9
|
+
* critical; any partial failure is at least a warning; slow-but-working checks
|
|
10
|
+
* escalate per their own thresholds.
|
|
11
|
+
*/
|
|
12
|
+
export function evaluateConnectivityStatus(sample, thresholds) {
|
|
13
|
+
if (sample.allFailed)
|
|
14
|
+
return 'critical';
|
|
15
|
+
const timeStatus = worstStatus([
|
|
16
|
+
evaluateThreshold(sample.dnsAvg, thresholds.dnsTime),
|
|
17
|
+
evaluateThreshold(sample.httpAvg, thresholds.httpTime),
|
|
18
|
+
evaluateThreshold(sample.latencyAvg, thresholds.latencyTime),
|
|
19
|
+
]);
|
|
20
|
+
const failStatus = sample.anyFailed ? 'warning' : 'healthy';
|
|
21
|
+
return worstStatus([timeStatus, failStatus]);
|
|
22
|
+
}
|
|
23
|
+
/** Scale a threshold set by a ratio, preserving direction. */
|
|
24
|
+
function scaleThresholds(base, ratio) {
|
|
25
|
+
const scale = (t) => ({
|
|
26
|
+
warning: Math.round(t.warning * ratio),
|
|
27
|
+
critical: Math.round(t.critical * ratio),
|
|
28
|
+
direction: t.direction,
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
dnsTime: scale(base.dnsTime),
|
|
32
|
+
httpTime: scale(base.httpTime),
|
|
33
|
+
latencyTime: scale(base.latencyTime),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export class InternetConnectivityNPC extends PollingNPC {
|
|
37
|
+
dnsHosts;
|
|
38
|
+
httpUrls;
|
|
39
|
+
latencyHosts;
|
|
40
|
+
httpTimeout;
|
|
41
|
+
thresholds;
|
|
42
|
+
dnsHistory = [];
|
|
43
|
+
httpHistory = [];
|
|
44
|
+
latencyHistory = [];
|
|
45
|
+
constructor(config) {
|
|
46
|
+
super({
|
|
47
|
+
...config,
|
|
48
|
+
pollInterval: config.pollInterval ?? DEFAULTS.pollInterval,
|
|
49
|
+
});
|
|
50
|
+
this.dnsHosts = config.dnsHosts ?? DEFAULTS.dnsHosts;
|
|
51
|
+
this.httpUrls = config.httpUrls ?? DEFAULTS.httpUrls;
|
|
52
|
+
this.latencyHosts = config.latencyHosts
|
|
53
|
+
? parseLatencyHosts(config.latencyHosts)
|
|
54
|
+
: DEFAULTS.latencyHosts;
|
|
55
|
+
this.httpTimeout = config.httpTimeout ?? DEFAULTS.httpTimeout;
|
|
56
|
+
// Threshold resolution: explicit thresholds win; else if the legacy
|
|
57
|
+
// --slow-threshold differs from the default, scale the live thresholds by
|
|
58
|
+
// that ratio for back-compat; else use the defaults.
|
|
59
|
+
if (config.thresholds) {
|
|
60
|
+
this.thresholds = config.thresholds;
|
|
61
|
+
}
|
|
62
|
+
else if (config.slowThreshold && config.slowThreshold !== DEFAULTS.slowThreshold) {
|
|
63
|
+
this.thresholds = scaleThresholds(DEFAULTS.thresholds, config.slowThreshold / DEFAULTS.slowThreshold);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.thresholds = DEFAULTS.thresholds;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
get name() {
|
|
70
|
+
return 'internet-connectivity';
|
|
71
|
+
}
|
|
72
|
+
get defaultDisplayName() {
|
|
73
|
+
return 'Internet';
|
|
74
|
+
}
|
|
75
|
+
get archetype() {
|
|
76
|
+
return 'gateway';
|
|
77
|
+
}
|
|
78
|
+
get behavior() {
|
|
79
|
+
return { idle: 'wander' };
|
|
80
|
+
}
|
|
81
|
+
get companion() {
|
|
82
|
+
return {
|
|
83
|
+
screenType: 'dashboard-wall',
|
|
84
|
+
size: { w: 2, h: 2 },
|
|
85
|
+
placement: 'adjacent-wall',
|
|
86
|
+
label: 'Internet',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async collect() {
|
|
90
|
+
const [dnsResults, httpResults, latencyResults] = await Promise.all([
|
|
91
|
+
checkDns(this.dnsHosts),
|
|
92
|
+
checkHttp(this.httpUrls, this.httpTimeout),
|
|
93
|
+
checkLatency(this.latencyHosts),
|
|
94
|
+
]);
|
|
95
|
+
const dnsOk = dnsResults.filter((r) => r.ok).length;
|
|
96
|
+
const dnsFail = dnsResults.length - dnsOk;
|
|
97
|
+
const httpOk = httpResults.filter((r) => r.ok).length;
|
|
98
|
+
const httpFail = httpResults.length - httpOk;
|
|
99
|
+
const dnsAvg = avgTime(dnsResults);
|
|
100
|
+
const httpAvg = avgTime(httpResults);
|
|
101
|
+
const latencyAvg = avgTime(latencyResults);
|
|
102
|
+
const isOnline = dnsOk > 0 || httpOk > 0;
|
|
103
|
+
const allFailed = dnsOk === 0 && httpOk === 0;
|
|
104
|
+
const anyFailed = dnsResults.some((r) => !r.ok) ||
|
|
105
|
+
httpResults.some((r) => !r.ok) ||
|
|
106
|
+
latencyResults.some((r) => !r.ok);
|
|
107
|
+
const status = evaluateConnectivityStatus({ allFailed, anyFailed, dnsAvg, httpAvg, latencyAvg }, this.thresholds);
|
|
108
|
+
const mood = statusToMood(status);
|
|
109
|
+
const message = allFailed
|
|
110
|
+
? this.buildOfflineMessage(dnsResults, httpResults)
|
|
111
|
+
: this.buildOnlineMessage(dnsAvg, httpAvg, latencyAvg);
|
|
112
|
+
pushHistory(this.dnsHistory, round(dnsAvg));
|
|
113
|
+
pushHistory(this.httpHistory, round(httpAvg));
|
|
114
|
+
pushHistory(this.latencyHistory, round(latencyAvg));
|
|
115
|
+
const screenData = {
|
|
116
|
+
layout: 'multi-metric',
|
|
117
|
+
values: [
|
|
118
|
+
{
|
|
119
|
+
label: 'DNS',
|
|
120
|
+
value: `${round(dnsAvg)}ms`,
|
|
121
|
+
color: statusColor(evaluateThreshold(dnsAvg, this.thresholds.dnsTime)),
|
|
122
|
+
sparkline: [...this.dnsHistory],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: 'HTTP',
|
|
126
|
+
value: `${round(httpAvg)}ms`,
|
|
127
|
+
color: statusColor(evaluateThreshold(httpAvg, this.thresholds.httpTime)),
|
|
128
|
+
sparkline: [...this.httpHistory],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: 'TCP',
|
|
132
|
+
value: `${round(latencyAvg)}ms`,
|
|
133
|
+
color: statusColor(evaluateThreshold(latencyAvg, this.thresholds.latencyTime)),
|
|
134
|
+
sparkline: [...this.latencyHistory],
|
|
135
|
+
},
|
|
136
|
+
{ label: 'NET', value: isOnline ? 'ONLINE' : 'OFFLINE', color: isOnline ? 'green' : 'red' },
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
status,
|
|
141
|
+
mood,
|
|
142
|
+
message,
|
|
143
|
+
subStatus: isOnline ? undefined : 'offline',
|
|
144
|
+
screenData,
|
|
145
|
+
metrics: {
|
|
146
|
+
dns_avg_ms: round(dnsAvg),
|
|
147
|
+
http_avg_ms: round(httpAvg),
|
|
148
|
+
latency_avg_ms: round(latencyAvg),
|
|
149
|
+
dns_ok_count: dnsOk,
|
|
150
|
+
dns_fail_count: dnsFail,
|
|
151
|
+
http_ok_count: httpOk,
|
|
152
|
+
http_fail_count: httpFail,
|
|
153
|
+
is_online: isOnline ? 1 : 0,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
buildOnlineMessage(dnsAvg, httpAvg, latencyAvg) {
|
|
158
|
+
return `Online | DNS: ${round(dnsAvg)}ms | HTTP: ${round(httpAvg)}ms | Latency: ${round(latencyAvg)}ms`;
|
|
159
|
+
}
|
|
160
|
+
buildOfflineMessage(dnsResults, httpResults) {
|
|
161
|
+
const problems = [];
|
|
162
|
+
if (dnsResults.every((r) => !r.ok)) {
|
|
163
|
+
problems.push('DNS resolution failed');
|
|
164
|
+
}
|
|
165
|
+
if (httpResults.every((r) => !r.ok)) {
|
|
166
|
+
problems.push('no HTTP connectivity');
|
|
167
|
+
}
|
|
168
|
+
return `OFFLINE: ${problems.join(', ') || 'all checks failed'}`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build config from CLI flags. Merges with any programmatic overrides.
|
|
172
|
+
*/
|
|
173
|
+
static configFromFlags(flags) {
|
|
174
|
+
const config = {};
|
|
175
|
+
const dnsHostsRaw = stringFlag(flags, 'dns-hosts');
|
|
176
|
+
if (dnsHostsRaw) {
|
|
177
|
+
config.dnsHosts = dnsHostsRaw.split(',').map((s) => s.trim());
|
|
178
|
+
}
|
|
179
|
+
const httpUrlsRaw = stringFlag(flags, 'http-urls');
|
|
180
|
+
if (httpUrlsRaw) {
|
|
181
|
+
config.httpUrls = httpUrlsRaw.split(',').map((s) => s.trim());
|
|
182
|
+
}
|
|
183
|
+
const latencyHostsRaw = stringFlag(flags, 'latency-hosts');
|
|
184
|
+
if (latencyHostsRaw) {
|
|
185
|
+
config.latencyHosts = latencyHostsRaw.split(',').map((s) => {
|
|
186
|
+
const trimmed = s.trim();
|
|
187
|
+
const colonIndex = trimmed.lastIndexOf(':');
|
|
188
|
+
if (colonIndex !== -1) {
|
|
189
|
+
const host = trimmed.slice(0, colonIndex);
|
|
190
|
+
const port = Number(trimmed.slice(colonIndex + 1));
|
|
191
|
+
if (!Number.isNaN(port))
|
|
192
|
+
return { host, port };
|
|
193
|
+
}
|
|
194
|
+
return { host: trimmed, port: 80 };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const slowThreshold = numberFlag(flags, 'slow-threshold');
|
|
198
|
+
if (slowThreshold !== undefined)
|
|
199
|
+
config.slowThreshold = slowThreshold;
|
|
200
|
+
const pollInterval = numberFlag(flags, 'poll-interval');
|
|
201
|
+
if (pollInterval !== undefined)
|
|
202
|
+
config.pollInterval = pollInterval;
|
|
203
|
+
return config;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ---------- Helpers ----------
|
|
207
|
+
function avgTime(results) {
|
|
208
|
+
if (results.length === 0)
|
|
209
|
+
return 0;
|
|
210
|
+
const sum = results.reduce((acc, r) => acc + r.timeMs, 0);
|
|
211
|
+
return sum / results.length;
|
|
212
|
+
}
|
|
213
|
+
function round(n) {
|
|
214
|
+
return Math.round(n * 100) / 100;
|
|
215
|
+
}
|
|
216
|
+
function statusToMood(status) {
|
|
217
|
+
switch (status) {
|
|
218
|
+
case 'healthy':
|
|
219
|
+
return 'happy';
|
|
220
|
+
case 'warning':
|
|
221
|
+
return 'concerned';
|
|
222
|
+
case 'critical':
|
|
223
|
+
return 'stressed';
|
|
224
|
+
default:
|
|
225
|
+
return 'idle';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Normalise latency hosts from either string[] (CLI) or LatencyHost[].
|
|
230
|
+
* String entries can be "host" or "host:port".
|
|
231
|
+
*/
|
|
232
|
+
function parseLatencyHosts(input) {
|
|
233
|
+
return input.map((item) => {
|
|
234
|
+
if (typeof item === 'object')
|
|
235
|
+
return item;
|
|
236
|
+
const colonIndex = item.lastIndexOf(':');
|
|
237
|
+
if (colonIndex !== -1) {
|
|
238
|
+
const host = item.slice(0, colonIndex);
|
|
239
|
+
const port = Number(item.slice(colonIndex + 1));
|
|
240
|
+
if (!Number.isNaN(port))
|
|
241
|
+
return { host, port };
|
|
242
|
+
}
|
|
243
|
+
return { host: item, port: 80 };
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ScreenData } from '../../core/types.js';
|
|
2
|
+
export type LogLineLevel = 'error' | 'warning' | 'info';
|
|
3
|
+
export interface RecentLine {
|
|
4
|
+
text: string;
|
|
5
|
+
level: LogLineLevel;
|
|
6
|
+
}
|
|
7
|
+
/** Classify a raw log line against the error/warning patterns. */
|
|
8
|
+
export declare function classifyLine(line: string, errorRe: RegExp, warningRe: RegExp): LogLineLevel;
|
|
9
|
+
/**
|
|
10
|
+
* Push a line into the bounded ring buffer: each entry is capped at `cap`
|
|
11
|
+
* characters and the buffer keeps only the last `max` entries.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pushRecent(buffer: RecentLine[], line: string, level: LogLineLevel, max?: number, cap?: number): RecentLine[];
|
|
14
|
+
/** Build a log-terminal screen payload from the ring buffer, colored per level. */
|
|
15
|
+
export declare function buildLogScreen(buffer: RecentLine[]): ScreenData;
|
|
16
|
+
/** True when enough time has elapsed since the last burst push to emit again. */
|
|
17
|
+
export declare function shouldEmitBurst(lastEmit: number, now: number, windowMs?: number): boolean;
|
|
18
|
+
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Classify a raw log line against the error/warning patterns. */
|
|
2
|
+
export function classifyLine(line, errorRe, warningRe) {
|
|
3
|
+
if (errorRe.test(line))
|
|
4
|
+
return 'error';
|
|
5
|
+
if (warningRe.test(line))
|
|
6
|
+
return 'warning';
|
|
7
|
+
return 'info';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Push a line into the bounded ring buffer: each entry is capped at `cap`
|
|
11
|
+
* characters and the buffer keeps only the last `max` entries.
|
|
12
|
+
*/
|
|
13
|
+
export function pushRecent(buffer, line, level, max = 12, cap = 80) {
|
|
14
|
+
buffer.push({ text: line.slice(0, cap), level });
|
|
15
|
+
if (buffer.length > max)
|
|
16
|
+
buffer.splice(0, buffer.length - max);
|
|
17
|
+
return buffer;
|
|
18
|
+
}
|
|
19
|
+
/** Build a log-terminal screen payload from the ring buffer, colored per level. */
|
|
20
|
+
export function buildLogScreen(buffer) {
|
|
21
|
+
return {
|
|
22
|
+
layout: 'log-terminal',
|
|
23
|
+
values: buffer.map((l) => ({
|
|
24
|
+
label: '',
|
|
25
|
+
value: l.text,
|
|
26
|
+
color: l.level === 'error' ? 'red' : l.level === 'warning' ? 'amber' : 'green',
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** True when enough time has elapsed since the last burst push to emit again. */
|
|
31
|
+
export function shouldEmitBurst(lastEmit, now, windowMs = 5000) {
|
|
32
|
+
return now - lastEmit >= windowMs;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=buffer.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
reportInterval: 15000, // 15 seconds
|
|
3
|
+
windowSeconds: 300, // 5 minutes
|
|
4
|
+
errorPattern: 'ERROR|FATAL',
|
|
5
|
+
warningPattern: 'WARN',
|
|
6
|
+
errorWarn: 5, // errors in window before warning status
|
|
7
|
+
errorCrit: 20, // errors in window before critical status
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=defaults.js.map
|