process-watchdog 1.0.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 (117) hide show
  1. package/config/default.json +16 -0
  2. package/dashboard/watchdog.html +856 -0
  3. package/dist/api/routes.d.ts +6 -0
  4. package/dist/api/routes.d.ts.map +1 -0
  5. package/dist/api/routes.js +105 -0
  6. package/dist/api/routes.js.map +1 -0
  7. package/dist/api/server.d.ts +10 -0
  8. package/dist/api/server.d.ts.map +1 -0
  9. package/dist/api/server.js +16 -0
  10. package/dist/api/server.js.map +1 -0
  11. package/dist/config.d.ts +27 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +45 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/processwatchdog.err.log +973 -0
  16. package/dist/daemon/processwatchdog.exe +0 -0
  17. package/dist/daemon/processwatchdog.exe.config +6 -0
  18. package/dist/daemon/processwatchdog.out.log +2 -0
  19. package/dist/daemon/processwatchdog.wrapper.log +18 -0
  20. package/dist/daemon/processwatchdog.xml +30 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +203 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/installer/service.d.ts +3 -0
  26. package/dist/installer/service.d.ts.map +1 -0
  27. package/dist/installer/service.js +41 -0
  28. package/dist/installer/service.js.map +1 -0
  29. package/dist/integrations/mah.d.ts +3 -0
  30. package/dist/integrations/mah.d.ts.map +1 -0
  31. package/dist/integrations/mah.js +22 -0
  32. package/dist/integrations/mah.js.map +1 -0
  33. package/dist/integrations/total-recall.d.ts +3 -0
  34. package/dist/integrations/total-recall.d.ts.map +1 -0
  35. package/dist/integrations/total-recall.js +22 -0
  36. package/dist/integrations/total-recall.js.map +1 -0
  37. package/dist/platform/index.d.ts +4 -0
  38. package/dist/platform/index.d.ts.map +1 -0
  39. package/dist/platform/index.js +10 -0
  40. package/dist/platform/index.js.map +1 -0
  41. package/dist/platform/platform.interface.d.ts +42 -0
  42. package/dist/platform/platform.interface.d.ts.map +1 -0
  43. package/dist/platform/platform.interface.js +2 -0
  44. package/dist/platform/platform.interface.js.map +1 -0
  45. package/dist/platform/windows.d.ts +14 -0
  46. package/dist/platform/windows.d.ts.map +1 -0
  47. package/dist/platform/windows.js +162 -0
  48. package/dist/platform/windows.js.map +1 -0
  49. package/dist/plugins/cpu-monitor.d.ts +11 -0
  50. package/dist/plugins/cpu-monitor.d.ts.map +1 -0
  51. package/dist/plugins/cpu-monitor.js +57 -0
  52. package/dist/plugins/cpu-monitor.js.map +1 -0
  53. package/dist/plugins/disk-health.d.ts +11 -0
  54. package/dist/plugins/disk-health.d.ts.map +1 -0
  55. package/dist/plugins/disk-health.js +111 -0
  56. package/dist/plugins/disk-health.js.map +1 -0
  57. package/dist/plugins/memory-monitor.d.ts +11 -0
  58. package/dist/plugins/memory-monitor.d.ts.map +1 -0
  59. package/dist/plugins/memory-monitor.js +61 -0
  60. package/dist/plugins/memory-monitor.js.map +1 -0
  61. package/dist/plugins/plugin-loader.d.ts +4 -0
  62. package/dist/plugins/plugin-loader.d.ts.map +1 -0
  63. package/dist/plugins/plugin-loader.js +25 -0
  64. package/dist/plugins/plugin-loader.js.map +1 -0
  65. package/dist/plugins/plugin.interface.d.ts +28 -0
  66. package/dist/plugins/plugin.interface.d.ts.map +1 -0
  67. package/dist/plugins/plugin.interface.js +2 -0
  68. package/dist/plugins/plugin.interface.js.map +1 -0
  69. package/dist/plugins/process-guard.d.ts +11 -0
  70. package/dist/plugins/process-guard.d.ts.map +1 -0
  71. package/dist/plugins/process-guard.js +139 -0
  72. package/dist/plugins/process-guard.js.map +1 -0
  73. package/dist/plugins/startup-optimizer.d.ts +11 -0
  74. package/dist/plugins/startup-optimizer.d.ts.map +1 -0
  75. package/dist/plugins/startup-optimizer.js +78 -0
  76. package/dist/plugins/startup-optimizer.js.map +1 -0
  77. package/dist/scheduler.d.ts +16 -0
  78. package/dist/scheduler.d.ts.map +1 -0
  79. package/dist/scheduler.js +46 -0
  80. package/dist/scheduler.js.map +1 -0
  81. package/dist/store/history.d.ts +20 -0
  82. package/dist/store/history.d.ts.map +1 -0
  83. package/dist/store/history.js +60 -0
  84. package/dist/store/history.js.map +1 -0
  85. package/package.json +35 -0
  86. package/src/api/routes.ts +123 -0
  87. package/src/api/server.ts +20 -0
  88. package/src/config.ts +78 -0
  89. package/src/index.ts +228 -0
  90. package/src/installer/service.ts +50 -0
  91. package/src/integrations/mah.ts +22 -0
  92. package/src/integrations/total-recall.ts +27 -0
  93. package/src/platform/index.ts +13 -0
  94. package/src/platform/platform.interface.ts +46 -0
  95. package/src/platform/windows.ts +242 -0
  96. package/src/plugins/cpu-monitor.ts +67 -0
  97. package/src/plugins/disk-health.ts +128 -0
  98. package/src/plugins/memory-monitor.ts +70 -0
  99. package/src/plugins/plugin-loader.ts +27 -0
  100. package/src/plugins/plugin.interface.ts +31 -0
  101. package/src/plugins/process-guard.ts +165 -0
  102. package/src/plugins/startup-optimizer.ts +103 -0
  103. package/src/scheduler.ts +53 -0
  104. package/src/store/history.ts +90 -0
  105. package/tests/api/routes.test.ts +113 -0
  106. package/tests/config.test.ts +24 -0
  107. package/tests/platform/windows.test.ts +59 -0
  108. package/tests/plugins/cpu-monitor.test.ts +69 -0
  109. package/tests/plugins/disk-health.test.ts +69 -0
  110. package/tests/plugins/memory-monitor.test.ts +57 -0
  111. package/tests/plugins/plugin-loader.test.ts +35 -0
  112. package/tests/plugins/process-guard.test.ts +40 -0
  113. package/tests/plugins/startup-optimizer.test.ts +50 -0
  114. package/tests/scheduler.test.ts +69 -0
  115. package/tests/store/history.test.ts +89 -0
  116. package/tsconfig.json +18 -0
  117. package/vitest.config.ts +10 -0
@@ -0,0 +1,139 @@
1
+ import { getPlatform } from '../platform/index.js';
2
+ const TARGET_NAMES = ['node', 'cmd', 'bash'];
3
+ /** Safely get processes by name — returns [] if none found or platform throws */
4
+ async function safeGetProcessesByName(platform, name) {
5
+ try {
6
+ return await platform.getProcessesByName(name);
7
+ }
8
+ catch {
9
+ return [];
10
+ }
11
+ }
12
+ export class ProcessGuardPlugin {
13
+ name = 'process-guard';
14
+ description = 'Detect and kill orphaned processes before they accumulate';
15
+ defaultInterval = 300000;
16
+ config = {
17
+ enabled: true,
18
+ interval: 300000,
19
+ autoFix: true,
20
+ thresholds: { node: 50, cmd: 20, bash: 10 },
21
+ };
22
+ configure(opts) {
23
+ this.config = { ...this.config, ...opts };
24
+ }
25
+ async check() {
26
+ const platform = getPlatform();
27
+ const listeningPids = new Set(await platform.getListeningPids());
28
+ const counts = {};
29
+ const orphanCounts = {};
30
+ const warnings = [];
31
+ const suggestedActions = [];
32
+ for (const name of TARGET_NAMES) {
33
+ const procs = await safeGetProcessesByName(platform, name);
34
+ const total = procs.length;
35
+ const orphans = procs.filter((p) => !listeningPids.has(p.pid)).length;
36
+ counts[`${name}Count`] = total;
37
+ orphanCounts[`${name}Orphans`] = orphans;
38
+ const threshold = this.config.thresholds[name] ?? Infinity;
39
+ if (total > threshold) {
40
+ warnings.push(`${name}: ${total} processes exceeds threshold of ${threshold}`);
41
+ if (orphans > 0) {
42
+ suggestedActions.push(`Kill ${orphans} orphaned ${name} processes`);
43
+ }
44
+ }
45
+ }
46
+ const totalOrphans = (orphanCounts['nodeOrphans'] ?? 0) +
47
+ (orphanCounts['cmdOrphans'] ?? 0) +
48
+ (orphanCounts['bashOrphans'] ?? 0);
49
+ const metrics = {
50
+ nodeCount: counts['nodeCount'] ?? 0,
51
+ cmdCount: counts['cmdCount'] ?? 0,
52
+ bashCount: counts['bashCount'] ?? 0,
53
+ nodeOrphans: orphanCounts['nodeOrphans'] ?? 0,
54
+ cmdOrphans: orphanCounts['cmdOrphans'] ?? 0,
55
+ bashOrphans: orphanCounts['bashOrphans'] ?? 0,
56
+ totalOrphans,
57
+ };
58
+ let status;
59
+ if (totalOrphans > 100) {
60
+ status = 'critical';
61
+ }
62
+ else if (totalOrphans > 0 && warnings.length > 0) {
63
+ status = 'warning';
64
+ }
65
+ else {
66
+ status = 'healthy';
67
+ }
68
+ const message = warnings.length > 0 ? warnings.join('; ') : 'All process counts within thresholds';
69
+ return { status, metrics, message, suggestedActions };
70
+ }
71
+ async fix() {
72
+ const platform = getPlatform();
73
+ const listeningPids = new Set(await platform.getListeningPids());
74
+ // Capture before metrics
75
+ const beforeMetrics = {};
76
+ for (const name of TARGET_NAMES) {
77
+ const procs = await safeGetProcessesByName(platform, name);
78
+ beforeMetrics[`${name}Count`] = procs.length;
79
+ beforeMetrics[`${name}Orphans`] = procs.filter((p) => !listeningPids.has(p.pid)).length;
80
+ }
81
+ const actionsKept = [];
82
+ let totalMemoryFreedMB = 0;
83
+ // Protect only: this process, its parent, and processes listening on ports (dev servers)
84
+ const selfPid = process.pid;
85
+ const parentPid = process.ppid;
86
+ const protectedPids = new Set([selfPid, parentPid, ...listeningPids].filter((p) => typeof p === 'number'));
87
+ // Kill orphans only when process count exceeds threshold
88
+ for (const name of TARGET_NAMES) {
89
+ const procs = await safeGetProcessesByName(platform, name);
90
+ const threshold = this.config.thresholds[name] ?? Infinity;
91
+ if (procs.length <= threshold)
92
+ continue; // Within limits — skip
93
+ const orphans = procs.filter((p) => !listeningPids.has(p.pid) && !protectedPids.has(p.pid));
94
+ let killed = 0;
95
+ for (const proc of orphans) {
96
+ const success = await platform.killProcess(proc.pid);
97
+ if (success) {
98
+ killed++;
99
+ totalMemoryFreedMB += proc.memoryMB ?? 0;
100
+ }
101
+ }
102
+ if (orphans.length > 0) {
103
+ actionsKept.push(`Killed ${killed}/${orphans.length} orphaned ${name} processes`);
104
+ }
105
+ }
106
+ // Best-effort: clean up low-CPU conhost processes (skip any that were running at snapshot time)
107
+ try {
108
+ const conhosts = await safeGetProcessesByName(platform, 'conhost');
109
+ for (const proc of conhosts) {
110
+ if (proc.cpu < 0.1 && !protectedPids.has(proc.pid)) {
111
+ const success = await platform.killProcess(proc.pid);
112
+ if (success) {
113
+ totalMemoryFreedMB += proc.memoryMB ?? 0;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ catch {
119
+ // best-effort, ignore errors
120
+ }
121
+ // Capture after metrics (best-effort — system may be in transition after kills)
122
+ let afterListeningPids;
123
+ try {
124
+ afterListeningPids = new Set(await platform.getListeningPids());
125
+ }
126
+ catch {
127
+ afterListeningPids = new Set();
128
+ }
129
+ const afterMetrics = {};
130
+ for (const name of TARGET_NAMES) {
131
+ const procs = await safeGetProcessesByName(platform, name);
132
+ afterMetrics[`${name}Count`] = procs.length;
133
+ afterMetrics[`${name}Orphans`] = procs.filter((p) => !afterListeningPids.has(p.pid)).length;
134
+ }
135
+ const resourcesFreed = `Freed ~${Math.round(totalMemoryFreedMB)} MB RAM`;
136
+ return { actionsKept, resourcesFreed, beforeMetrics, afterMetrics };
137
+ }
138
+ }
139
+ //# sourceMappingURL=process-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/plugins/process-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { WatchdogPlugin, HealthCheck, FixResult, PluginConfig } from './plugin.interface.js';
2
+ export declare class StartupOptimizerPlugin implements WatchdogPlugin {
3
+ name: string;
4
+ description: string;
5
+ defaultInterval: number;
6
+ private config;
7
+ configure(opts: PluginConfig): void;
8
+ check(): Promise<HealthCheck>;
9
+ fix(): Promise<FixResult>;
10
+ }
11
+ //# sourceMappingURL=startup-optimizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-optimizer.d.ts","sourceRoot":"","sources":["../../src/plugins/startup-optimizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAa7F,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,IAAI,SAAuB;IAC3B,WAAW,SAAwD;IACnE,eAAe,SAAK;IACpB,OAAO,CAAC,MAAM,CAKZ;IAEF,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAI7B,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IA8B7B,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;CA4ChC"}
@@ -0,0 +1,78 @@
1
+ import { getPlatform } from '../platform/index.js';
2
+ const KNOWN_BLOAT = [
3
+ 'Docker Desktop',
4
+ 'GoogleDriveFS',
5
+ 'Figma Agent',
6
+ 'CiscoSpark',
7
+ 'CiscoMeetingDaemon',
8
+ 'EasySettingBox',
9
+ 'com.todesktop',
10
+ ];
11
+ export class StartupOptimizerPlugin {
12
+ name = 'startup-optimizer';
13
+ description = 'Manage startup items to reduce boot resource usage';
14
+ defaultInterval = 0;
15
+ config = {
16
+ enabled: true,
17
+ interval: 0,
18
+ autoFix: false,
19
+ thresholds: {},
20
+ };
21
+ configure(opts) {
22
+ this.config = { ...this.config, ...opts };
23
+ }
24
+ async check() {
25
+ const platform = getPlatform();
26
+ const items = await platform.getStartupItems();
27
+ const enabledItems = items.filter((item) => item.enabled);
28
+ const startupItemCount = enabledItems.length;
29
+ const bloatItems = enabledItems.filter((item) => KNOWN_BLOAT.some((bloat) => item.name.includes(bloat)));
30
+ const bloatItemCount = bloatItems.length;
31
+ const suggestedActions = [];
32
+ if (bloatItemCount > 0) {
33
+ suggestedActions.push(`Disable ${bloatItemCount} bloat startup item(s): ${bloatItems.map((b) => b.name).join(', ')}`);
34
+ }
35
+ const status = bloatItemCount > 3 ? 'warning' : 'healthy';
36
+ const message = `${startupItemCount} startup items enabled, ${bloatItemCount} identified as bloat`;
37
+ return {
38
+ status,
39
+ metrics: { startupItemCount, bloatItemCount },
40
+ message,
41
+ suggestedActions,
42
+ };
43
+ }
44
+ async fix() {
45
+ const platform = getPlatform();
46
+ const itemsBefore = await platform.getStartupItems();
47
+ const enabledBefore = itemsBefore.filter((item) => item.enabled);
48
+ const bloatBefore = enabledBefore.filter((item) => KNOWN_BLOAT.some((bloat) => item.name.includes(bloat)));
49
+ const beforeMetrics = {
50
+ startupItemCount: enabledBefore.length,
51
+ bloatItemCount: bloatBefore.length,
52
+ };
53
+ const actionsKept = [];
54
+ let disabledCount = 0;
55
+ for (const item of bloatBefore) {
56
+ try {
57
+ const success = await platform.disableStartupItem(item.name);
58
+ if (success) {
59
+ disabledCount++;
60
+ actionsKept.push(`Disabled startup item: ${item.name}`);
61
+ }
62
+ }
63
+ catch {
64
+ // skip items that fail to disable
65
+ }
66
+ }
67
+ const itemsAfter = await platform.getStartupItems();
68
+ const enabledAfter = itemsAfter.filter((item) => item.enabled);
69
+ const bloatAfter = enabledAfter.filter((item) => KNOWN_BLOAT.some((bloat) => item.name.includes(bloat)));
70
+ const afterMetrics = {
71
+ startupItemCount: enabledAfter.length,
72
+ bloatItemCount: bloatAfter.length,
73
+ };
74
+ const resourcesFreed = `Disabled ${disabledCount} startup item(s)`;
75
+ return { actionsKept, resourcesFreed, beforeMetrics, afterMetrics };
76
+ }
77
+ }
78
+ //# sourceMappingURL=startup-optimizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-optimizer.js","sourceRoot":"","sources":["../../src/plugins/startup-optimizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { WatchdogPlugin } from './plugins/plugin.interface.js';
2
+ interface SchedulerOptions {
3
+ autoFix: Record<string, boolean>;
4
+ }
5
+ export declare class Scheduler {
6
+ private plugins;
7
+ private options;
8
+ private timers;
9
+ private running;
10
+ constructor(plugins: WatchdogPlugin[], options: SchedulerOptions);
11
+ start(overrideIntervalMs?: number): void;
12
+ stop(): void;
13
+ isRunning(): boolean;
14
+ }
15
+ export {};
16
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,gBAAgB;IAKhE,KAAK,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI;IA4BxC,IAAI,IAAI,IAAI;IAMZ,SAAS,IAAI,OAAO;CACrB"}
@@ -0,0 +1,46 @@
1
+ export class Scheduler {
2
+ plugins;
3
+ options;
4
+ timers = [];
5
+ running = false;
6
+ constructor(plugins, options) {
7
+ this.plugins = plugins;
8
+ this.options = options;
9
+ }
10
+ start(overrideIntervalMs) {
11
+ if (this.running)
12
+ return;
13
+ this.running = true;
14
+ for (const plugin of this.plugins) {
15
+ const interval = overrideIntervalMs ?? plugin.defaultInterval;
16
+ if (interval <= 0)
17
+ continue;
18
+ const run = async () => {
19
+ if (!this.running)
20
+ return;
21
+ try {
22
+ const result = await plugin.check();
23
+ const shouldFix = this.options.autoFix[plugin.name] &&
24
+ (result.status === 'critical' || result.status === 'warning');
25
+ if (shouldFix) {
26
+ await plugin.fix();
27
+ }
28
+ }
29
+ catch (err) {
30
+ console.error(`[watchdog] ${plugin.name} error:`, err);
31
+ }
32
+ };
33
+ run(); // Run immediately
34
+ const timer = setInterval(run, interval);
35
+ this.timers.push(timer);
36
+ }
37
+ }
38
+ stop() {
39
+ this.running = false;
40
+ for (const timer of this.timers)
41
+ clearInterval(timer);
42
+ this.timers = [];
43
+ }
44
+ isRunning() { return this.running; }
45
+ }
46
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,SAAS;IACZ,OAAO,CAAmB;IAC1B,OAAO,CAAmB;IAC1B,MAAM,GAAqB,EAAE,CAAC;IAC9B,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,OAAyB,EAAE,OAAyB;QAC9D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,kBAA2B;QAC/B,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,kBAAkB,IAAI,MAAM,CAAC,eAAe,CAAC;YAC9D,IAAI,QAAQ,IAAI,CAAC;gBAAE,SAAS;YAE5B,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE;gBACrB,IAAI,CAAC,IAAI,CAAC,OAAO;oBAAE,OAAO;gBAC1B,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;oBACpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;wBACjD,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;oBAChE,IAAI,SAAS,EAAE,CAAC;wBACd,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;oBACrB,CAAC;gBACH,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,KAAK,CAAC,cAAc,MAAM,CAAC,IAAI,SAAS,EAAE,GAAG,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC,CAAC;YAEF,GAAG,EAAE,CAAC,CAAC,kBAAkB;YACzB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,aAAa,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,SAAS,KAAc,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;CAC9C"}
@@ -0,0 +1,20 @@
1
+ export interface HistoryRow {
2
+ id: number;
3
+ plugin: string;
4
+ type: string;
5
+ status: string;
6
+ metrics: string | null;
7
+ actions: string | null;
8
+ message: string | null;
9
+ timestamp: string;
10
+ }
11
+ export declare class HistoryStore {
12
+ private db;
13
+ constructor(dbPath?: string);
14
+ recordCheck(plugin: string, status: string, metrics: Record<string, unknown>, message: string): void;
15
+ recordFix(plugin: string, actions: string[], resourcesFreed: string): void;
16
+ getHistory(plugin: string | null, limit: number): HistoryRow[];
17
+ getLastChecks(): Record<string, HistoryRow>;
18
+ close(): void;
19
+ }
20
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/store/history.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,GAAE,MAAwB;IAiB5C,WAAW,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,MAAM,GACd,IAAI;IAQP,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAQ1E,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAa9D,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;IAmB3C,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,60 @@
1
+ import Database from 'better-sqlite3';
2
+ export class HistoryStore {
3
+ db;
4
+ constructor(dbPath = './watchdog.db') {
5
+ this.db = new Database(dbPath);
6
+ this.db.pragma('journal_mode = WAL');
7
+ this.db.exec(`
8
+ CREATE TABLE IF NOT EXISTS history (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ plugin TEXT NOT NULL,
11
+ type TEXT NOT NULL,
12
+ status TEXT NOT NULL,
13
+ metrics TEXT,
14
+ actions TEXT,
15
+ message TEXT,
16
+ timestamp TEXT NOT NULL DEFAULT (datetime('now'))
17
+ )
18
+ `);
19
+ }
20
+ recordCheck(plugin, status, metrics, message) {
21
+ const stmt = this.db.prepare(`INSERT INTO history (plugin, type, status, metrics, message)
22
+ VALUES (?, 'check', ?, ?, ?)`);
23
+ stmt.run(plugin, status, JSON.stringify(metrics), message);
24
+ }
25
+ recordFix(plugin, actions, resourcesFreed) {
26
+ const stmt = this.db.prepare(`INSERT INTO history (plugin, type, status, actions, message)
27
+ VALUES (?, 'fix', 'fixed', ?, ?)`);
28
+ stmt.run(plugin, JSON.stringify(actions), resourcesFreed);
29
+ }
30
+ getHistory(plugin, limit) {
31
+ if (plugin !== null) {
32
+ const stmt = this.db.prepare(`SELECT * FROM history WHERE plugin = ? ORDER BY id DESC LIMIT ?`);
33
+ return stmt.all(plugin, limit);
34
+ }
35
+ const stmt = this.db.prepare(`SELECT * FROM history ORDER BY id DESC LIMIT ?`);
36
+ return stmt.all(limit);
37
+ }
38
+ getLastChecks() {
39
+ const stmt = this.db.prepare(`
40
+ SELECT h.*
41
+ FROM history h
42
+ INNER JOIN (
43
+ SELECT plugin, MAX(id) AS max_id
44
+ FROM history
45
+ WHERE type = 'check'
46
+ GROUP BY plugin
47
+ ) latest ON h.id = latest.max_id
48
+ `);
49
+ const rows = stmt.all();
50
+ const result = {};
51
+ for (const row of rows) {
52
+ result[row.plugin] = row;
53
+ }
54
+ return result;
55
+ }
56
+ close() {
57
+ this.db.close();
58
+ }
59
+ }
60
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/store/history.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAatC,MAAM,OAAO,YAAY;IACf,EAAE,CAAoB;IAE9B,YAAY,SAAiB,eAAe;QAC1C,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;KAWZ,CAAC,CAAC;IACL,CAAC;IAED,WAAW,CACT,MAAc,EACd,MAAc,EACd,OAAgC,EAChC,OAAe;QAEf,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B;oCAC8B,CAC/B,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAED,SAAS,CAAC,MAAc,EAAE,OAAiB,EAAE,cAAsB;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B;wCACkC,CACnC,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC;IAC5D,CAAC;IAED,UAAU,CAAC,MAAqB,EAAE,KAAa;QAC7C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B,iEAAiE,CAClE,CAAC;YACF,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAiB,CAAC;QACjD,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B,gDAAgD,CACjD,CAAC;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAiB,CAAC;IACzC,CAAC;IAED,aAAa;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;;KAS5B,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAkB,CAAC;QACxC,MAAM,MAAM,GAA+B,EAAE,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "process-watchdog",
3
+ "version": "1.0.0",
4
+ "description": "Modular PC health agent for the aidev.com.au ecosystem",
5
+ "type": "module",
6
+ "bin": {
7
+ "watchdog": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "better-sqlite3": "^12.8.0",
20
+ "commander": "^14.0.3",
21
+ "express": "^5.2.1",
22
+ "node-windows": "^1.0.0-beta.8"
23
+ },
24
+ "devDependencies": {
25
+ "@types/better-sqlite3": "^7.6.13",
26
+ "@types/express": "^5.0.6",
27
+ "@types/node": "^25.6.0",
28
+ "@types/node-windows": "^0.1.6",
29
+ "@types/supertest": "^7.2.0",
30
+ "supertest": "^7.2.2",
31
+ "tsx": "^4.21.0",
32
+ "typescript": "^6.0.2",
33
+ "vitest": "^4.1.4"
34
+ }
35
+ }
@@ -0,0 +1,123 @@
1
+ import { Router } from 'express';
2
+ import { HistoryStore } from '../store/history.js';
3
+ import { WatchdogPlugin, HealthStatus } from '../plugins/plugin.interface.js';
4
+ import { WatchdogConfig } from '../config.js';
5
+
6
+ const STATUS_ORDER: HealthStatus[] = ['healthy', 'warning', 'critical'];
7
+
8
+ function worstStatus(statuses: HealthStatus[]): HealthStatus {
9
+ let worst: HealthStatus = 'healthy';
10
+ for (const s of statuses) {
11
+ if (STATUS_ORDER.indexOf(s) > STATUS_ORDER.indexOf(worst)) {
12
+ worst = s;
13
+ }
14
+ }
15
+ return worst;
16
+ }
17
+
18
+ export function createRoutes(
19
+ store: HistoryStore,
20
+ plugins: WatchdogPlugin[],
21
+ config: WatchdogConfig,
22
+ ): Router {
23
+ const router = Router();
24
+
25
+ // GET /health
26
+ router.get('/health', (_req, res) => {
27
+ const lastChecks = store.getLastChecks();
28
+ const pluginStatuses: Record<string, { status: string; lastRun: string | null }> = {};
29
+ const statuses: HealthStatus[] = [];
30
+
31
+ for (const plugin of plugins) {
32
+ const last = lastChecks[plugin.name];
33
+ pluginStatuses[plugin.name] = {
34
+ status: last ? last.status : 'healthy',
35
+ lastRun: last ? last.timestamp : null,
36
+ };
37
+ if (last) {
38
+ statuses.push(last.status as HealthStatus);
39
+ }
40
+ }
41
+
42
+ res.json({
43
+ service: 'process-watchdog',
44
+ version: '1.0.0',
45
+ status: worstStatus(statuses),
46
+ uptime: Math.floor(process.uptime()),
47
+ plugins: pluginStatuses,
48
+ });
49
+ });
50
+
51
+ // GET /plugins
52
+ router.get('/plugins', (_req, res) => {
53
+ const lastChecks = store.getLastChecks();
54
+ const result = plugins.map((plugin) => {
55
+ const last = lastChecks[plugin.name];
56
+ const pluginConfig = config.plugins[plugin.name];
57
+ return {
58
+ name: plugin.name,
59
+ description: plugin.description,
60
+ interval: pluginConfig ? pluginConfig.interval : plugin.defaultInterval,
61
+ lastCheck: last ? last.timestamp : null,
62
+ };
63
+ });
64
+ res.json(result);
65
+ });
66
+
67
+ // GET /plugins/:name
68
+ router.get('/plugins/:name', (req, res) => {
69
+ const plugin = plugins.find((p) => p.name === req.params.name);
70
+ if (!plugin) {
71
+ res.status(404).json({ error: `Plugin '${req.params.name}' not found` });
72
+ return;
73
+ }
74
+ const history = store.getHistory(plugin.name, 50);
75
+ res.json({
76
+ name: plugin.name,
77
+ description: plugin.description,
78
+ history,
79
+ });
80
+ });
81
+
82
+ // POST /plugins/:name/run
83
+ router.post('/plugins/:name/run', async (req, res) => {
84
+ const plugin = plugins.find((p) => p.name === req.params.name);
85
+ if (!plugin) {
86
+ res.status(404).json({ error: `Plugin '${req.params.name}' not found` });
87
+ return;
88
+ }
89
+
90
+ const action: 'check' | 'fix' = req.body?.action === 'fix' ? 'fix' : 'check';
91
+
92
+ try {
93
+ if (action === 'fix') {
94
+ const result = await plugin.fix();
95
+ store.recordFix(plugin.name, result.actionsKept, result.resourcesFreed);
96
+ res.json({ action, result });
97
+ } else {
98
+ const result = await plugin.check();
99
+ store.recordCheck(plugin.name, result.status, result.metrics, result.message);
100
+ res.json({ action, result });
101
+ }
102
+ } catch (err) {
103
+ const message = err instanceof Error ? err.message : String(err);
104
+ res.status(500).json({ error: message });
105
+ }
106
+ });
107
+
108
+ // GET /history
109
+ router.get('/history', (req, res) => {
110
+ const pluginFilter = typeof req.query.plugin === 'string' ? req.query.plugin : null;
111
+ const limitRaw = parseInt(String(req.query.limit ?? '50'), 10);
112
+ const limit = isNaN(limitRaw) || limitRaw <= 0 ? 50 : limitRaw;
113
+ const history = store.getHistory(pluginFilter, limit);
114
+ res.json(history);
115
+ });
116
+
117
+ // GET /config
118
+ router.get('/config', (_req, res) => {
119
+ res.json(config);
120
+ });
121
+
122
+ return router;
123
+ }
@@ -0,0 +1,20 @@
1
+ import express from 'express';
2
+ import { HistoryStore } from '../store/history.js';
3
+ import { WatchdogPlugin } from '../plugins/plugin.interface.js';
4
+ import { WatchdogConfig } from '../config.js';
5
+ import { createRoutes } from './routes.js';
6
+
7
+ export function createApp(store: HistoryStore, plugins: WatchdogPlugin[], config: WatchdogConfig): express.Express {
8
+ const app = express();
9
+ app.use(express.json());
10
+ app.use('/api/v1', createRoutes(store, plugins, config));
11
+ return app;
12
+ }
13
+
14
+ export function startServer(store: HistoryStore, plugins: WatchdogPlugin[], config: WatchdogConfig) {
15
+ const app = createApp(store, plugins, config);
16
+ const server = app.listen(config.port, () => {
17
+ console.log(`[watchdog] API server running on port ${config.port}`);
18
+ });
19
+ return { app, close: () => server.close() };
20
+ }