@lifeaitools/clauth 1.4.1 → 1.4.2

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.
@@ -0,0 +1,42 @@
1
+ # clauth-watchdog.ps1
2
+ # Monitors clauth daemon on port 52437. Restarts it if not responding.
3
+ # Installed by: npm install -g @lifeaitools/clauth
4
+ # Managed by: clauth watchdog [install|uninstall|status]
5
+
6
+ $clauth = "$env:APPDATA\npm\clauth.cmd"
7
+ $logFile = "$env:APPDATA\clauth\watchdog.log"
8
+ $pingUrl = "http://127.0.0.1:52437/ping"
9
+ $interval = 15
10
+
11
+ function Write-Log($msg) {
12
+ $ts = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
13
+ $line = "[$ts] $msg"
14
+ Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
15
+ }
16
+
17
+ Write-Log "Watchdog started (PID $PID, clauth=$clauth)"
18
+
19
+ while ($true) {
20
+ $alive = $false
21
+ try {
22
+ $r = Invoke-WebRequest -Uri $pingUrl -TimeoutSec 3 -UseBasicParsing -ErrorAction Stop
23
+ if ($r.StatusCode -eq 200) { $alive = $true }
24
+ } catch { }
25
+
26
+ if (-not $alive) {
27
+ Write-Log "Daemon not responding — restarting..."
28
+ Start-Process -FilePath "cmd.exe" `
29
+ -ArgumentList "/c `"$clauth`" serve start" `
30
+ -WindowStyle Hidden
31
+ Start-Sleep -Seconds 5
32
+ try {
33
+ $r = Invoke-WebRequest -Uri $pingUrl -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop
34
+ if ($r.StatusCode -eq 200) { Write-Log "Daemon restarted OK." }
35
+ else { Write-Log "WARNING: Daemon still not responding." }
36
+ } catch {
37
+ Write-Log "WARNING: Daemon still not responding after restart."
38
+ }
39
+ }
40
+
41
+ Start-Sleep -Seconds $interval
42
+ }
@@ -0,0 +1,144 @@
1
+ // cli/commands/watchdog.js
2
+ // Manages the clauth watchdog Task Scheduler job on Windows.
3
+ // clauth watchdog install — register job (UAC elevation)
4
+ // clauth watchdog uninstall — remove job (UAC elevation)
5
+ // clauth watchdog status — check if job is registered and running
6
+ // clauth watchdog start — start the job now without waiting for login
7
+
8
+ import { execSync, spawnSync } from "child_process";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import os from "os";
12
+ import { fileURLToPath } from "url";
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const TASK_NAME = "ClautWatchdog";
16
+ const APPDATA = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
17
+ const CLAUTH_DIR = path.join(APPDATA, "clauth");
18
+ const DEST_PS1 = path.join(CLAUTH_DIR, "watchdog.ps1");
19
+ const SOURCE_PS1 = path.join(__dirname, "..", "assets", "watchdog.ps1");
20
+ const PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
21
+
22
+ function ensureWatchdogScript() {
23
+ fs.mkdirSync(CLAUTH_DIR, { recursive: true });
24
+ fs.copyFileSync(SOURCE_PS1, DEST_PS1);
25
+ }
26
+
27
+ function isElevated() {
28
+ try {
29
+ execSync(`${PS_EXE} -NoProfile -Command "[Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"`,
30
+ { stdio: "pipe", encoding: "utf8" });
31
+ return true;
32
+ } catch { return false; }
33
+ }
34
+
35
+ // Build the PowerShell registration command as a single string
36
+ function buildRegisterCmd(ps1Path) {
37
+ const escaped = ps1Path.replace(/'/g, "''");
38
+ return [
39
+ `$action = New-ScheduledTaskAction -Execute 'powershell.exe'`,
40
+ ` -Argument '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \\"${escaped}\\"';`,
41
+ `$trigger = New-ScheduledTaskTrigger -AtLogOn;`,
42
+ `$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3`,
43
+ ` -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew;`,
44
+ `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue;`,
45
+ `Register-ScheduledTask -TaskName '${TASK_NAME}' -Action $action -Trigger $trigger`,
46
+ ` -Settings $settings -RunLevel Highest`,
47
+ ` -Description 'clauth daemon watchdog — restarts on port 52437 if not responding.';`,
48
+ `Write-Host 'Watchdog registered.'`,
49
+ ].join(" ");
50
+ }
51
+
52
+ function buildUnregisterCmd() {
53
+ return `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue; Write-Host 'Watchdog removed.'`;
54
+ }
55
+
56
+ function runElevated(psCmd) {
57
+ // Wrap in Start-Process -Verb RunAs to trigger UAC dialog
58
+ const inner = psCmd.replace(/"/g, '\\"');
59
+ const result = spawnSync(PS_EXE, [
60
+ "-NoProfile", "-Command",
61
+ `Start-Process '${PS_EXE}' -Verb RunAs -Wait -ArgumentList '-NoProfile -ExecutionPolicy Bypass -Command "${inner}"'`,
62
+ ], { stdio: "inherit", encoding: "utf8" });
63
+ return result.status === 0;
64
+ }
65
+
66
+ function runDirect(psCmd) {
67
+ const result = spawnSync(PS_EXE, [
68
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd,
69
+ ], { stdio: "inherit", encoding: "utf8" });
70
+ return result.status === 0;
71
+ }
72
+
73
+ export async function runWatchdog(action) {
74
+ if (os.platform() !== "win32") {
75
+ console.log("Watchdog auto-start is Windows-only. On Linux/macOS, use systemd or launchd.");
76
+ return;
77
+ }
78
+
79
+ if (action === "status" || !action) {
80
+ try {
81
+ const out = execSync(
82
+ `${PS_EXE} -NoProfile -Command "Get-ScheduledTask -TaskName '${TASK_NAME}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty State"`,
83
+ { encoding: "utf8", stdio: ["pipe","pipe","pipe"], timeout: 5000 }
84
+ ).trim();
85
+ if (out) {
86
+ console.log(`Watchdog task: ${TASK_NAME} — State: ${out}`);
87
+ console.log(`Script: ${DEST_PS1}`);
88
+ console.log(`Log: ${path.join(CLAUTH_DIR, "watchdog.log")}`);
89
+ } else {
90
+ console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
91
+ console.log("Run: clauth watchdog install");
92
+ }
93
+ } catch {
94
+ console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
95
+ console.log("Run: clauth watchdog install");
96
+ }
97
+ return;
98
+ }
99
+
100
+ if (action === "install") {
101
+ ensureWatchdogScript();
102
+ console.log(`\n Installing clauth watchdog...`);
103
+ console.log(` Script: ${DEST_PS1}`);
104
+ console.log(` Task: ${TASK_NAME} (runs at login)\n`);
105
+ console.log(" Windows will ask for administrator approval — please click Yes.\n");
106
+
107
+ const cmd = buildRegisterCmd(DEST_PS1);
108
+ const ok = runElevated(cmd);
109
+
110
+ if (ok) {
111
+ console.log("\n Watchdog installed. Starting now...");
112
+ spawnSync(PS_EXE, [
113
+ "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
114
+ ], { stdio: "pipe" });
115
+ console.log(" Done. Watchdog is running.\n");
116
+ } else {
117
+ console.log("\n Installation cancelled or failed.");
118
+ console.log(` To install manually, run as Administrator:`);
119
+ console.log(` clauth watchdog install\n`);
120
+ }
121
+ return;
122
+ }
123
+
124
+ if (action === "uninstall") {
125
+ console.log("\n Removing clauth watchdog...");
126
+ console.log(" Windows will ask for administrator approval — please click Yes.\n");
127
+ const ok = runElevated(buildUnregisterCmd());
128
+ if (ok) console.log("\n Watchdog removed.\n");
129
+ else console.log("\n Removal cancelled or failed.\n");
130
+ return;
131
+ }
132
+
133
+ if (action === "start") {
134
+ console.log(` Starting ${TASK_NAME}...`);
135
+ const r = spawnSync(PS_EXE, [
136
+ "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
137
+ ], { stdio: "inherit", encoding: "utf8" });
138
+ if (r.status === 0) console.log(" Watchdog started.");
139
+ else console.log(" Could not start — is it registered? Run: clauth watchdog install");
140
+ return;
141
+ }
142
+
143
+ console.log("Usage: clauth watchdog [install|uninstall|status|start]");
144
+ }
package/cli/index.js CHANGED
@@ -462,6 +462,16 @@ Examples:
462
462
  });
463
463
 
464
464
  // ──────────────────────────────────────────────
465
+ // clauth watchdog
466
+ // ──────────────────────────────────────────────
467
+ program
468
+ .command("watchdog [action]")
469
+ .description("Manage auto-restart watchdog (install|uninstall|status|start)")
470
+ .action(async (action) => {
471
+ const { runWatchdog } = await import("./commands/watchdog.js");
472
+ await runWatchdog(action);
473
+ });
474
+
465
475
  // clauth doctor
466
476
  // ──────────────────────────────────────────────
467
477
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,17 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  // scripts/postinstall.js
3
3
  // Runs after npm install -g @lifeaitools/clauth
4
- // Detects fresh install vs upgrade and acts accordingly
4
+ // On Windows: writes watchdog.ps1 to %APPDATA%\clauth\ and registers
5
+ // the Task Scheduler job via UAC elevation.
5
6
 
6
7
  import fs from "fs";
7
8
  import path from "path";
8
9
  import os from "os";
9
10
  import { fileURLToPath } from "url";
11
+ import { spawnSync } from "child_process";
10
12
 
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const APPDATA = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
15
+ const CLAUTH_DIR = path.join(APPDATA, "clauth");
16
+ const DEST_PS1 = path.join(CLAUTH_DIR, "watchdog.ps1");
17
+ const SOURCE_PS1 = path.join(__dirname, "..", "cli", "assets", "watchdog.ps1");
18
+ const PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
19
+ const TASK_NAME = "ClautWatchdog";
12
20
 
13
21
  const CONFIG_DIR = os.platform() === "win32"
14
- ? path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "clauth-nodejs", "Config")
22
+ ? path.join(APPDATA, "clauth-nodejs", "Config")
15
23
  : path.join(os.homedir(), ".config", "clauth-nodejs");
16
24
 
17
25
  let version = "1.0.0";
@@ -20,33 +28,117 @@ try {
20
28
  version = pkg.version;
21
29
  } catch {}
22
30
 
31
+ // ----------------------------------------------------------------
32
+ // Watchdog helpers (Windows only)
33
+ // ----------------------------------------------------------------
34
+
35
+ function writeWatchdogScript() {
36
+ try {
37
+ fs.mkdirSync(CLAUTH_DIR, { recursive: true });
38
+ fs.copyFileSync(SOURCE_PS1, DEST_PS1);
39
+ return true;
40
+ } catch (err) {
41
+ console.log(` ! Could not write watchdog script: ${err.message}`);
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function isWatchdogRegistered() {
47
+ try {
48
+ const r = spawnSync(PS_EXE, [
49
+ "-NoProfile", "-Command",
50
+ `(Get-ScheduledTask -TaskName '${TASK_NAME}' -ErrorAction SilentlyContinue) -ne $null`,
51
+ ], { encoding: "utf8", stdio: ["pipe","pipe","pipe"], timeout: 5000 });
52
+ return (r.stdout || "").trim().toLowerCase() === "true";
53
+ } catch { return false; }
54
+ }
55
+
56
+ function registerWatchdog() {
57
+ const escaped = DEST_PS1.replace(/'/g, "''");
58
+ const psCmd = [
59
+ `$a = New-ScheduledTaskAction -Execute 'powershell.exe'`,
60
+ ` -Argument '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \\"${escaped}\\"';`,
61
+ `$t = New-ScheduledTaskTrigger -AtLogOn;`,
62
+ `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3`,
63
+ ` -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew;`,
64
+ `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue;`,
65
+ `Register-ScheduledTask -TaskName '${TASK_NAME}' -Action $a -Trigger $t`,
66
+ ` -Settings $s -RunLevel Highest -Description 'clauth daemon watchdog';`,
67
+ ].join(" ");
68
+
69
+ // Try direct first (works if already running as Administrator)
70
+ const direct = spawnSync(PS_EXE, [
71
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd,
72
+ ], { encoding: "utf8", stdio: ["pipe","pipe","pipe"], timeout: 10000 });
73
+
74
+ if (direct.status === 0) return "ok";
75
+
76
+ // Trigger UAC elevation — opens the Windows approval dialog
77
+ const inner = psCmd.replace(/"/g, '\\"');
78
+ const elevated = spawnSync(PS_EXE, [
79
+ "-NoProfile", "-Command",
80
+ `Start-Process '${PS_EXE}' -Verb RunAs -Wait -ArgumentList '-NoProfile -ExecutionPolicy Bypass -Command "${inner}"'`,
81
+ ], { stdio: "inherit", encoding: "utf8", timeout: 60000 });
82
+
83
+ return elevated.status === 0 ? "ok" : "denied";
84
+ }
85
+
86
+ function startWatchdog() {
87
+ spawnSync(PS_EXE, [
88
+ "-NoProfile", "-Command",
89
+ `Start-ScheduledTask -TaskName '${TASK_NAME}' -ErrorAction SilentlyContinue`,
90
+ ], { stdio: "pipe", timeout: 5000 });
91
+ }
92
+
93
+ // ----------------------------------------------------------------
94
+ // Main
95
+ // ----------------------------------------------------------------
96
+
23
97
  async function main() {
24
98
  const configPath = path.join(CONFIG_DIR, "config.json");
25
- const isUpgrade = fs.existsSync(configPath);
99
+ const isUpgrade = fs.existsSync(configPath);
26
100
 
27
101
  if (isUpgrade) {
28
102
  console.log(`\n \u2713 clauth upgraded to v${version}`);
29
103
  console.log(` \u2713 Config preserved at ${CONFIG_DIR}`);
30
-
31
- // Try to detect running daemon
32
104
  try {
33
105
  const controller = new AbortController();
34
- const timeout = setTimeout(() => controller.abort(), 2000);
106
+ const to = setTimeout(() => controller.abort(), 2000);
35
107
  const res = await fetch("http://127.0.0.1:52437/ping", { signal: controller.signal });
36
- clearTimeout(timeout);
37
- if (res.ok) {
38
- console.log(" \u21BB Daemon is running \u2014 restart it with: clauth serve restart");
39
- }
108
+ clearTimeout(to);
109
+ if (res.ok) console.log(" \u21BB Daemon running \u2014 restart: clauth serve restart");
40
110
  } catch {
41
111
  console.log(" \u25CB Daemon not running");
42
112
  }
43
-
44
- console.log(" Run 'clauth doctor' to verify installation\n");
45
113
  } else {
46
114
  console.log(`\n Welcome to clauth v${version}!`);
47
115
  console.log(" Run 'clauth install' to set up your vault");
48
- console.log(" Run 'clauth doctor' to check prerequisites\n");
49
116
  }
117
+
118
+ // Windows: register watchdog on fresh install; refresh script on upgrade
119
+ if (os.platform() === "win32") {
120
+ const registered = isWatchdogRegistered();
121
+ if (!registered) {
122
+ console.log("\n Setting up auto-restart watchdog...");
123
+ console.log(" Windows will ask for administrator approval \u2014 please click Yes.\n");
124
+ if (writeWatchdogScript()) {
125
+ const result = registerWatchdog();
126
+ if (result === "ok") {
127
+ startWatchdog();
128
+ console.log(" \u2713 Watchdog registered and started (Task: ClautWatchdog)");
129
+ console.log(` \u2713 Script: ${DEST_PS1}`);
130
+ } else {
131
+ console.log(" ! Watchdog registration skipped (approval denied).");
132
+ console.log(" Install later with: clauth watchdog install");
133
+ }
134
+ }
135
+ } else {
136
+ writeWatchdogScript(); // refresh script on upgrade
137
+ console.log(" \u2713 Watchdog script updated");
138
+ }
139
+ }
140
+
141
+ console.log(" Run 'clauth doctor' to verify installation\n");
50
142
  }
51
143
 
52
144
  main().catch(() => {});