@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,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Keyless weather + geolocation via open-meteo, with ipapi.co IP fallback.
|
|
4
|
+
* All network helpers return null on failure so the receptionist can degrade
|
|
5
|
+
* to a time-only mode without erroring.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.describeWmo = describeWmo;
|
|
9
|
+
exports.geocodeLocation = geocodeLocation;
|
|
10
|
+
exports.autoGeo = autoGeo;
|
|
11
|
+
exports.fetchWeather = fetchWeather;
|
|
12
|
+
exports.toFahrenheit = toFahrenheit;
|
|
13
|
+
exports.formatClock = formatClock;
|
|
14
|
+
// WMO weather-interpretation codes → human text + a glyph keyword the
|
|
15
|
+
// welcome-sign renderer understands (matched by /sun|clear|partly|rain|snow|storm|fog/).
|
|
16
|
+
const WMO = {
|
|
17
|
+
0: { text: 'Clear', icon: 'clear' },
|
|
18
|
+
1: { text: 'Mainly clear', icon: 'partly' },
|
|
19
|
+
2: { text: 'Partly cloudy', icon: 'partly' },
|
|
20
|
+
3: { text: 'Overcast', icon: 'cloud' },
|
|
21
|
+
45: { text: 'Fog', icon: 'fog' },
|
|
22
|
+
48: { text: 'Rime fog', icon: 'fog' },
|
|
23
|
+
51: { text: 'Light drizzle', icon: 'drizzle' },
|
|
24
|
+
53: { text: 'Drizzle', icon: 'drizzle' },
|
|
25
|
+
55: { text: 'Heavy drizzle', icon: 'drizzle' },
|
|
26
|
+
56: { text: 'Freezing drizzle', icon: 'drizzle' },
|
|
27
|
+
57: { text: 'Freezing drizzle', icon: 'drizzle' },
|
|
28
|
+
61: { text: 'Light rain', icon: 'rain' },
|
|
29
|
+
63: { text: 'Rain', icon: 'rain' },
|
|
30
|
+
65: { text: 'Heavy rain', icon: 'rain' },
|
|
31
|
+
66: { text: 'Freezing rain', icon: 'rain' },
|
|
32
|
+
67: { text: 'Freezing rain', icon: 'rain' },
|
|
33
|
+
71: { text: 'Light snow', icon: 'snow' },
|
|
34
|
+
73: { text: 'Snow', icon: 'snow' },
|
|
35
|
+
75: { text: 'Heavy snow', icon: 'snow' },
|
|
36
|
+
77: { text: 'Snow grains', icon: 'snow' },
|
|
37
|
+
80: { text: 'Rain showers', icon: 'shower' },
|
|
38
|
+
81: { text: 'Rain showers', icon: 'shower' },
|
|
39
|
+
82: { text: 'Violent showers', icon: 'shower' },
|
|
40
|
+
85: { text: 'Snow showers', icon: 'snow' },
|
|
41
|
+
86: { text: 'Snow showers', icon: 'snow' },
|
|
42
|
+
95: { text: 'Thunderstorm', icon: 'storm' },
|
|
43
|
+
96: { text: 'Thunderstorm', icon: 'storm' },
|
|
44
|
+
99: { text: 'Thunderstorm', icon: 'storm' },
|
|
45
|
+
};
|
|
46
|
+
/** Map a WMO weather code to display text + a renderer glyph keyword. */
|
|
47
|
+
function describeWmo(code) {
|
|
48
|
+
return WMO[code] ?? { text: 'Unknown', icon: 'cloud' };
|
|
49
|
+
}
|
|
50
|
+
async function fetchJson(url, timeoutMs) {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
headers: { 'User-Agent': 'nocville-npc' },
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
return null;
|
|
60
|
+
return (await res.json());
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Resolve a place name to coordinates via the open-meteo geocoding API. */
|
|
70
|
+
async function geocodeLocation(name, timeoutMs = 5000) {
|
|
71
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(name)}&count=1`;
|
|
72
|
+
const res = await fetchJson(url, timeoutMs);
|
|
73
|
+
const first = res?.['results']?.[0];
|
|
74
|
+
if (!first || typeof first['latitude'] !== 'number')
|
|
75
|
+
return null;
|
|
76
|
+
return {
|
|
77
|
+
lat: first['latitude'],
|
|
78
|
+
lon: first['longitude'],
|
|
79
|
+
label: first['name'] ?? name,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Best-effort IP geolocation via ipapi.co (keyless). */
|
|
83
|
+
async function autoGeo(timeoutMs = 5000) {
|
|
84
|
+
const res = await fetchJson('https://ipapi.co/json/', timeoutMs);
|
|
85
|
+
if (!res || typeof res['latitude'] !== 'number')
|
|
86
|
+
return null;
|
|
87
|
+
return {
|
|
88
|
+
lat: res['latitude'],
|
|
89
|
+
lon: res['longitude'],
|
|
90
|
+
label: res['city'],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Fetch current temperature, weather code, and wind for coordinates. */
|
|
94
|
+
async function fetchWeather(coords, timeoutMs = 5000) {
|
|
95
|
+
const url = `https://api.open-meteo.com/v1/forecast?latitude=${coords.lat}` +
|
|
96
|
+
`&longitude=${coords.lon}¤t=temperature_2m,weather_code,wind_speed_10m`;
|
|
97
|
+
const res = await fetchJson(url, timeoutMs);
|
|
98
|
+
const cur = res?.['current'];
|
|
99
|
+
if (!cur || typeof cur['temperature_2m'] !== 'number')
|
|
100
|
+
return null;
|
|
101
|
+
const code = Number(cur['weather_code'] ?? 0);
|
|
102
|
+
const { text, icon } = describeWmo(code);
|
|
103
|
+
return {
|
|
104
|
+
tempC: Math.round(cur['temperature_2m']),
|
|
105
|
+
code,
|
|
106
|
+
text,
|
|
107
|
+
icon,
|
|
108
|
+
windKph: Math.round(Number(cur['wind_speed_10m'] ?? 0)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Convert Celsius to Fahrenheit, rounded. */
|
|
112
|
+
function toFahrenheit(c) {
|
|
113
|
+
return Math.round((c * 9) / 5 + 32);
|
|
114
|
+
}
|
|
115
|
+
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
116
|
+
/** Format a Date into day/date/time parts for the welcome sign. */
|
|
117
|
+
function formatClock(d) {
|
|
118
|
+
const day = DAYS[d.getDay()];
|
|
119
|
+
let h = d.getHours();
|
|
120
|
+
const m = d.getMinutes();
|
|
121
|
+
const ampm = h >= 12 ? 'PM' : 'AM';
|
|
122
|
+
h = h % 12;
|
|
123
|
+
if (h === 0)
|
|
124
|
+
h = 12;
|
|
125
|
+
const time = `${h}:${String(m).padStart(2, '0')} ${ampm}`;
|
|
126
|
+
const date = `${d.getMonth() + 1}/${d.getDate()}`;
|
|
127
|
+
return { day, date, time };
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=weather.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
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.createCpuSampler = createCpuSampler;
|
|
7
|
+
exports.collectMemory = collectMemory;
|
|
8
|
+
exports.collectDisk = collectDisk;
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
function aggregate(cpus) {
|
|
12
|
+
let idle = 0;
|
|
13
|
+
let total = 0;
|
|
14
|
+
for (const cpu of cpus) {
|
|
15
|
+
const t = cpu.times;
|
|
16
|
+
idle += t.idle;
|
|
17
|
+
total += t.user + t.nice + t.sys + t.irq + t.idle;
|
|
18
|
+
}
|
|
19
|
+
return { idle, total };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Stateful CPU sampler that computes usage from the delta between consecutive
|
|
23
|
+
* `os.cpus()` snapshots — no blocking sleep. The first sample reports usage
|
|
24
|
+
* since system boot; subsequent samples report usage since the previous call.
|
|
25
|
+
*
|
|
26
|
+
* `readCpus`/`readLoad` are injectable for testing.
|
|
27
|
+
*/
|
|
28
|
+
function createCpuSampler(readCpus = () => node_os_1.default.cpus(), readLoad = () => node_os_1.default.loadavg()[0] ?? 0) {
|
|
29
|
+
let prev = null;
|
|
30
|
+
return {
|
|
31
|
+
sample() {
|
|
32
|
+
const cur = aggregate(readCpus());
|
|
33
|
+
const loadAvg1m = Math.round(readLoad() * 100) / 100;
|
|
34
|
+
const base = prev ?? { idle: 0, total: 0 };
|
|
35
|
+
const idleDiff = cur.idle - base.idle;
|
|
36
|
+
const totalDiff = cur.total - base.total;
|
|
37
|
+
prev = cur;
|
|
38
|
+
const percent = totalDiff > 0 ? Math.round((1 - idleDiff / totalDiff) * 100) : 0;
|
|
39
|
+
return { percent, loadAvg1m };
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Collect memory usage from OS.
|
|
45
|
+
*/
|
|
46
|
+
function collectMemory() {
|
|
47
|
+
const totalBytes = node_os_1.default.totalmem();
|
|
48
|
+
const freeBytes = node_os_1.default.freemem();
|
|
49
|
+
const usedBytes = totalBytes - freeBytes;
|
|
50
|
+
const totalGb = Math.round((totalBytes / 1024 ** 3) * 100) / 100;
|
|
51
|
+
const usedGb = Math.round((usedBytes / 1024 ** 3) * 100) / 100;
|
|
52
|
+
const percent = Math.round((usedBytes / totalBytes) * 100);
|
|
53
|
+
return { percent, usedGb, totalGb };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Collect disk usage via fs.statfs (Node 18.15+).
|
|
57
|
+
*/
|
|
58
|
+
async function collectDisk(path) {
|
|
59
|
+
const stats = await node_fs_1.promises.statfs(path);
|
|
60
|
+
const totalBytes = stats.blocks * stats.bsize;
|
|
61
|
+
const freeBytes = stats.bavail * stats.bsize;
|
|
62
|
+
const usedBytes = totalBytes - freeBytes;
|
|
63
|
+
const totalGb = Math.round((totalBytes / 1024 ** 3) * 100) / 100;
|
|
64
|
+
const usedGb = Math.round((usedBytes / 1024 ** 3) * 100) / 100;
|
|
65
|
+
const percent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
|
|
66
|
+
return { percent, usedGb, totalGb };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=collectors.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_THRESHOLDS = exports.DEFAULTS = void 0;
|
|
4
|
+
exports.DEFAULTS = {
|
|
5
|
+
pollInterval: 15000,
|
|
6
|
+
diskPath: '/',
|
|
7
|
+
};
|
|
8
|
+
exports.DEFAULT_THRESHOLDS = {
|
|
9
|
+
cpu: { warning: 75, critical: 90, direction: 'above' },
|
|
10
|
+
mem: { warning: 75, critical: 90, direction: 'above' },
|
|
11
|
+
disk: { warning: 75, critical: 90, direction: 'above' },
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SystemMonitorNPC = void 0;
|
|
4
|
+
const polling_npc_js_1 = require("../../core/polling-npc.js");
|
|
5
|
+
const thresholds_js_1 = require("../../core/thresholds.js");
|
|
6
|
+
const screen_js_1 = require("../../core/screen.js");
|
|
7
|
+
const collectors_js_1 = require("./collectors.js");
|
|
8
|
+
const defaults_js_1 = require("./defaults.js");
|
|
9
|
+
class SystemMonitorNPC extends polling_npc_js_1.PollingNPC {
|
|
10
|
+
diskPath;
|
|
11
|
+
cpuThreshold;
|
|
12
|
+
memThreshold;
|
|
13
|
+
diskThreshold;
|
|
14
|
+
cpuSampler = (0, collectors_js_1.createCpuSampler)();
|
|
15
|
+
cpuHistory = [];
|
|
16
|
+
memHistory = [];
|
|
17
|
+
constructor(config) {
|
|
18
|
+
super({
|
|
19
|
+
...config,
|
|
20
|
+
pollInterval: config.pollInterval ?? defaults_js_1.DEFAULTS.pollInterval,
|
|
21
|
+
});
|
|
22
|
+
this.diskPath = config.diskPath ?? defaults_js_1.DEFAULTS.diskPath;
|
|
23
|
+
this.cpuThreshold = config.cpuThreshold ?? defaults_js_1.DEFAULT_THRESHOLDS['cpu'];
|
|
24
|
+
this.memThreshold = config.memThreshold ?? defaults_js_1.DEFAULT_THRESHOLDS['mem'];
|
|
25
|
+
this.diskThreshold = config.diskThreshold ?? defaults_js_1.DEFAULT_THRESHOLDS['disk'];
|
|
26
|
+
}
|
|
27
|
+
get name() {
|
|
28
|
+
return 'system-monitor';
|
|
29
|
+
}
|
|
30
|
+
get defaultDisplayName() {
|
|
31
|
+
return 'System Monitor';
|
|
32
|
+
}
|
|
33
|
+
get archetype() {
|
|
34
|
+
return 'service';
|
|
35
|
+
}
|
|
36
|
+
get heartbeatSeconds() {
|
|
37
|
+
return 30;
|
|
38
|
+
}
|
|
39
|
+
get behavior() {
|
|
40
|
+
return { idle: 'wander' };
|
|
41
|
+
}
|
|
42
|
+
get companion() {
|
|
43
|
+
return {
|
|
44
|
+
screenType: 'dashboard-wall',
|
|
45
|
+
size: { w: 2, h: 2 },
|
|
46
|
+
placement: 'adjacent-wall',
|
|
47
|
+
label: 'System',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async collect() {
|
|
51
|
+
const cpu = this.cpuSampler.sample();
|
|
52
|
+
const mem = (0, collectors_js_1.collectMemory)();
|
|
53
|
+
const disk = await (0, collectors_js_1.collectDisk)(this.diskPath);
|
|
54
|
+
const cpuStatus = (0, thresholds_js_1.evaluateThreshold)(cpu.percent, this.cpuThreshold);
|
|
55
|
+
const memStatus = (0, thresholds_js_1.evaluateThreshold)(mem.percent, this.memThreshold);
|
|
56
|
+
const diskStatus = (0, thresholds_js_1.evaluateThreshold)(disk.percent, this.diskThreshold);
|
|
57
|
+
const status = (0, thresholds_js_1.worstStatus)([cpuStatus, memStatus, diskStatus]);
|
|
58
|
+
const mood = status === 'critical'
|
|
59
|
+
? 'stressed'
|
|
60
|
+
: status === 'warning'
|
|
61
|
+
? 'concerned'
|
|
62
|
+
: 'happy';
|
|
63
|
+
(0, screen_js_1.pushHistory)(this.cpuHistory, cpu.percent);
|
|
64
|
+
(0, screen_js_1.pushHistory)(this.memHistory, mem.percent);
|
|
65
|
+
const screenData = {
|
|
66
|
+
layout: 'multi-metric',
|
|
67
|
+
values: [
|
|
68
|
+
{
|
|
69
|
+
label: 'CPU',
|
|
70
|
+
value: `${cpu.percent}%`,
|
|
71
|
+
color: (0, screen_js_1.statusColor)(cpuStatus),
|
|
72
|
+
sparkline: [...this.cpuHistory],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: 'MEM',
|
|
76
|
+
value: `${mem.percent}%`,
|
|
77
|
+
color: (0, screen_js_1.statusColor)(memStatus),
|
|
78
|
+
sparkline: [...this.memHistory],
|
|
79
|
+
},
|
|
80
|
+
{ label: 'DISK', value: `${disk.percent}%`, color: (0, screen_js_1.statusColor)(diskStatus) },
|
|
81
|
+
{ label: 'LOAD', value: cpu.loadAvg1m.toFixed(2), color: 'gray' },
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
status,
|
|
86
|
+
mood,
|
|
87
|
+
message: `CPU: ${cpu.percent}% | Mem: ${mem.percent}% | Disk: ${disk.percent}%`,
|
|
88
|
+
screenData,
|
|
89
|
+
// When strained, go find a desk and get to work; otherwise wander.
|
|
90
|
+
behavior: status !== 'healthy' ? { busy: 'seek-desk' } : undefined,
|
|
91
|
+
metrics: {
|
|
92
|
+
cpu_percent: cpu.percent,
|
|
93
|
+
mem_percent: mem.percent,
|
|
94
|
+
mem_used_gb: mem.usedGb,
|
|
95
|
+
mem_total_gb: mem.totalGb,
|
|
96
|
+
disk_percent: disk.percent,
|
|
97
|
+
disk_used_gb: disk.usedGb,
|
|
98
|
+
disk_total_gb: disk.totalGb,
|
|
99
|
+
load_avg_1m: cpu.loadAvg1m,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.SystemMonitorNPC = SystemMonitorNPC;
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractTitle = extractTitle;
|
|
4
|
+
exports.statusFromHttp = statusFromHttp;
|
|
5
|
+
exports.basicCheck = basicCheck;
|
|
6
|
+
exports.hostnameOf = hostnameOf;
|
|
7
|
+
/** Extract the <title> text from an HTML string (empty if none). */
|
|
8
|
+
function extractTitle(html) {
|
|
9
|
+
const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
10
|
+
return m ? m[1].replace(/\s+/g, ' ').trim().slice(0, 80) : '';
|
|
11
|
+
}
|
|
12
|
+
/** Map an HTTP status to NPC status: 2xx/3xx ok, 4xx/timeout warn, 5xx/refused crit. */
|
|
13
|
+
function statusFromHttp(code, timedOut = false) {
|
|
14
|
+
if (code >= 200 && code < 400)
|
|
15
|
+
return 'healthy';
|
|
16
|
+
if (code >= 400 && code < 500)
|
|
17
|
+
return 'warning';
|
|
18
|
+
if (code >= 500)
|
|
19
|
+
return 'critical';
|
|
20
|
+
return timedOut ? 'warning' : 'critical'; // 0 = network error
|
|
21
|
+
}
|
|
22
|
+
/** Fetch a page and extract HTTP status + <title> (basic, no browser). */
|
|
23
|
+
async function basicCheck(url, timeoutMs = 8000) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
headers: { 'User-Agent': 'nocville-npc' },
|
|
30
|
+
redirect: 'follow',
|
|
31
|
+
});
|
|
32
|
+
let title = '';
|
|
33
|
+
try {
|
|
34
|
+
title = extractTitle(await res.text());
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// body unavailable; leave title blank
|
|
38
|
+
}
|
|
39
|
+
return { status: res.status, title, ok: res.ok, timedOut: false };
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const timedOut = err instanceof Error && err.name === 'AbortError';
|
|
43
|
+
return { status: 0, title: '', ok: false, timedOut };
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Best-effort hostname for a URL, falling back to the raw string. */
|
|
50
|
+
function hostnameOf(url) {
|
|
51
|
+
try {
|
|
52
|
+
return new URL(url).hostname;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return url.slice(0, 40);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=basic-mode.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findChrome = findChrome;
|
|
4
|
+
exports.loadPuppeteer = loadPuppeteer;
|
|
5
|
+
exports.browserConnected = browserConnected;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const MAC_PATHS = [
|
|
9
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
10
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
11
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
12
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
13
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
14
|
+
];
|
|
15
|
+
const LINUX_BINARIES = [
|
|
16
|
+
'google-chrome',
|
|
17
|
+
'google-chrome-stable',
|
|
18
|
+
'chromium',
|
|
19
|
+
'chromium-browser',
|
|
20
|
+
'brave-browser',
|
|
21
|
+
];
|
|
22
|
+
function which(bin) {
|
|
23
|
+
try {
|
|
24
|
+
const out = (0, node_child_process_1.execFileSync)('which', [bin], { encoding: 'utf-8' }).trim();
|
|
25
|
+
return out || null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find a usable Chrome/Chromium executable: explicit path → CHROME_PATH env →
|
|
33
|
+
* platform-specific known locations (mac app bundles; `which` on linux).
|
|
34
|
+
*/
|
|
35
|
+
function findChrome(explicit) {
|
|
36
|
+
const envPath = process.env['CHROME_PATH'];
|
|
37
|
+
if (explicit && (0, node_fs_1.existsSync)(explicit))
|
|
38
|
+
return explicit;
|
|
39
|
+
if (envPath && (0, node_fs_1.existsSync)(envPath))
|
|
40
|
+
return envPath;
|
|
41
|
+
if (process.platform === 'darwin') {
|
|
42
|
+
for (const p of MAC_PATHS)
|
|
43
|
+
if ((0, node_fs_1.existsSync)(p))
|
|
44
|
+
return p;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (process.platform === 'linux') {
|
|
48
|
+
for (const bin of LINUX_BINARIES) {
|
|
49
|
+
const resolved = which(bin);
|
|
50
|
+
if (resolved && (0, node_fs_1.existsSync)(resolved))
|
|
51
|
+
return resolved;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Load the optional `puppeteer-core` peer dependency without a static import,
|
|
59
|
+
* so the package builds and runs when it isn't installed. Returns null if the
|
|
60
|
+
* module is absent.
|
|
61
|
+
*/
|
|
62
|
+
async function loadPuppeteer() {
|
|
63
|
+
try {
|
|
64
|
+
const dynImport = new Function('m', 'return import(m)');
|
|
65
|
+
const mod = await dynImport('puppeteer-core');
|
|
66
|
+
return (mod.default ?? mod);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** True if the browser reports a live connection. */
|
|
73
|
+
function browserConnected(browser) {
|
|
74
|
+
if (typeof browser.connected === 'boolean')
|
|
75
|
+
return browser.connected;
|
|
76
|
+
if (typeof browser.isConnected === 'function')
|
|
77
|
+
return browser.isConnected();
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULTS = void 0;
|
|
4
|
+
exports.parseDimensions = parseDimensions;
|
|
5
|
+
exports.DEFAULTS = {
|
|
6
|
+
pollInterval: 60000, // 60s
|
|
7
|
+
minPollInterval: 15000, // floor
|
|
8
|
+
navTimeoutMs: 15000,
|
|
9
|
+
maxColors: 32,
|
|
10
|
+
/** Polls to keep the 'changed' sub-status active after a page change. */
|
|
11
|
+
changedPolls: 2,
|
|
12
|
+
// Default TV resolution (4:3, matches the 4×3 web-view companion). The client
|
|
13
|
+
// reads WxH from the pixel-image `fmt` string, so any size is self-consistent.
|
|
14
|
+
screenW: 160,
|
|
15
|
+
screenH: 120,
|
|
16
|
+
// deviceScaleFactor is 1/8, so the viewport defaults to screen × 8 → Chrome
|
|
17
|
+
// rasterizes the screen size natively (overridable via --viewport).
|
|
18
|
+
scaleDivisor: 8,
|
|
19
|
+
waitUntil: 'networkidle2',
|
|
20
|
+
};
|
|
21
|
+
/** Parse a "WxH" string into a {width, height} pair, or null if malformed. */
|
|
22
|
+
function parseDimensions(s) {
|
|
23
|
+
const m = s
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.match(/^(\d+)x(\d+)$/);
|
|
27
|
+
if (!m)
|
|
28
|
+
return null;
|
|
29
|
+
return { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=defaults.js.map
|