@lifeaitools/clauth 1.30.23 → 1.30.24

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 (40) hide show
  1. package/.clauth-skill/SKILL.md +111 -111
  2. package/README.md +25 -0
  3. package/cli/api.classify.test.js +75 -75
  4. package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
  5. package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
  6. package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
  7. package/cli/assets/watchdog.ps1 +42 -42
  8. package/cli/commands/agent-cron.js +396 -396
  9. package/cli/commands/agent-pool.js +1962 -1962
  10. package/cli/commands/codevelop.js +1190 -1190
  11. package/cli/commands/doctor.js +302 -302
  12. package/cli/commands/install.js +10 -10
  13. package/cli/commands/invite.js +175 -175
  14. package/cli/commands/join.js +179 -179
  15. package/cli/commands/npm.js +182 -182
  16. package/cli/commands/scrub.js +327 -327
  17. package/cli/commands/scrub.test.js +115 -115
  18. package/cli/commands/serve.js +41 -95
  19. package/cli/commands/watchdog.js +209 -209
  20. package/cli/conf-path.js +21 -21
  21. package/cli/enrollment-script.js +82 -82
  22. package/cli/fingerprint.js +143 -143
  23. package/cli/index.js +1053 -1053
  24. package/cli/lib/fs-git.js +282 -282
  25. package/cli/recovery.js +101 -101
  26. package/cli/studio-debug.js +1095 -1095
  27. package/cli/supervisor-registry.js +594 -589
  28. package/cli/supervisor-registry.test.js +397 -397
  29. package/cli/supervisor-ui.test.js +5 -83
  30. package/cli/watchdog-registry.js +209 -209
  31. package/cli/watchdog-registry.test.js +89 -89
  32. package/install.ps1 +21 -21
  33. package/package.json +2 -2
  34. package/scripts/bin/bootstrap-linux +0 -0
  35. package/scripts/bin/bootstrap-macos +0 -0
  36. package/scripts/bin/bootstrap-win.exe +0 -0
  37. package/supabase/migrations/001_clauth_schema.sql +12 -12
  38. package/supabase/migrations/003_clauth_config.sql +13 -13
  39. package/supabase/migrations/003_machine_enrollments.sql +39 -39
  40. package/cli/served-script-syntax.test.mjs +0 -54
@@ -1,209 +1,209 @@
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
- import {
14
- getRegistryPath,
15
- getWatchdogStatuses,
16
- readWatchdogEvents,
17
- registerWatchdogManifest,
18
- restartWatchdogService,
19
- } from "../watchdog-registry.js";
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
- const TASK_NAME = "ClautWatchdog";
23
- const APPDATA = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
24
- const CLAUTH_DIR = path.join(APPDATA, "clauth");
25
- const DEST_PS1 = path.join(CLAUTH_DIR, "watchdog.ps1");
26
- const SOURCE_PS1 = path.join(__dirname, "..", "assets", "watchdog.ps1");
27
- const PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
28
-
29
- function ensureWatchdogScript() {
30
- fs.mkdirSync(CLAUTH_DIR, { recursive: true });
31
- fs.copyFileSync(SOURCE_PS1, DEST_PS1);
32
- }
33
-
34
- function isElevated() {
35
- try {
36
- const result = execSync(`${PS_EXE} -NoProfile -Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"`,
37
- { stdio: "pipe", encoding: "utf8" });
38
- return result.trim().toLowerCase() === "true";
39
- } catch { return false; }
40
- }
41
-
42
- // Build the PowerShell registration command as a single string
43
- function buildRegisterCmd(ps1Path) {
44
- const escaped = ps1Path.replace(/'/g, "''");
45
- return [
46
- `$action = New-ScheduledTaskAction -Execute 'powershell.exe'`,
47
- ` -Argument '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \\"${escaped}\\"';`,
48
- `$trigger = New-ScheduledTaskTrigger -AtLogOn;`,
49
- `$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3`,
50
- ` -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew;`,
51
- `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue;`,
52
- `Register-ScheduledTask -TaskName '${TASK_NAME}' -Action $action -Trigger $trigger`,
53
- ` -Settings $settings -RunLevel Highest`,
54
- ` -Description 'clauth daemon watchdog — restarts on port 52437 if not responding.';`,
55
- `Write-Host 'Watchdog registered.'`,
56
- ].join(" ");
57
- }
58
-
59
- function buildUnregisterCmd() {
60
- return `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue; Write-Host 'Watchdog removed.'`;
61
- }
62
-
63
- function runElevated(psCmd) {
64
- // Wrap in Start-Process -Verb RunAs to trigger UAC dialog
65
- const encoded = Buffer.from(psCmd, "utf16le").toString("base64");
66
- const result = spawnSync(PS_EXE, [
67
- "-NoProfile", "-Command",
68
- `Start-Process -FilePath '${PS_EXE}' -Verb RunAs -Wait -ArgumentList '-NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded}'`,
69
- ], { stdio: "inherit", encoding: "utf8" });
70
- return result.status === 0;
71
- }
72
-
73
- function runDirect(psCmd) {
74
- const result = spawnSync(PS_EXE, [
75
- "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd,
76
- ], { stdio: "inherit", encoding: "utf8" });
77
- return result.status === 0;
78
- }
79
-
80
- function readScheduledTaskState() {
81
- try {
82
- const out = execSync(
83
- `${PS_EXE} -NoProfile -Command "Get-ScheduledTask -TaskName '${TASK_NAME}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty State"`,
84
- { encoding: "utf8", stdio: ["pipe","pipe","pipe"], timeout: 5000 }
85
- ).trim();
86
- if (out) return out;
87
- } catch {}
88
-
89
- try {
90
- const out = execSync(`schtasks /Query /TN ${TASK_NAME} /FO LIST`, {
91
- encoding: "utf8",
92
- stdio: ["pipe","pipe","pipe"],
93
- timeout: 5000,
94
- });
95
- const status = out.match(/^Status:\\s*(.+)$/mi)?.[1]?.trim();
96
- return status || "Registered";
97
- } catch {
98
- return null;
99
- }
100
- }
101
-
102
- export async function runWatchdog(action, opts = {}) {
103
- if (action === "register") {
104
- const manifestPath = opts.manifest || opts.args?.[0];
105
- if (!manifestPath) {
106
- console.log("Usage: clauth watchdog register <manifest.json>");
107
- return;
108
- }
109
- const manifest = JSON.parse(fs.readFileSync(path.resolve(manifestPath), "utf8"));
110
- const result = registerWatchdogManifest(manifest);
111
- console.log(`Registered ${result.registered} watchdog service(s): ${result.services.join(", ")}`);
112
- console.log(`Registry: ${getRegistryPath()}`);
113
- return;
114
- }
115
-
116
- if (action === "list" || action === "services") {
117
- const status = await getWatchdogStatuses();
118
- console.log(JSON.stringify(status, null, 2));
119
- return;
120
- }
121
-
122
- if (action === "events") {
123
- const limit = opts.limit ? Number(opts.limit) : 100;
124
- console.log(JSON.stringify(readWatchdogEvents(limit), null, 2));
125
- return;
126
- }
127
-
128
- if (action === "restart") {
129
- const serviceId = opts.service || opts.args?.[0];
130
- if (!serviceId) {
131
- console.log("Usage: clauth watchdog restart <service-id>");
132
- return;
133
- }
134
- const result = restartWatchdogService(serviceId);
135
- console.log(JSON.stringify(result, null, 2));
136
- return;
137
- }
138
-
139
- if (os.platform() !== "win32") {
140
- console.log("Watchdog auto-start is Windows-only. On Linux/macOS, use systemd or launchd.");
141
- return;
142
- }
143
-
144
- if (action === "status" || !action) {
145
- const state = readScheduledTaskState();
146
- try {
147
- if (state) {
148
- console.log(`Watchdog task: ${TASK_NAME} — State: ${state}`);
149
- console.log(`Script: ${DEST_PS1}`);
150
- console.log(`Log: ${path.join(CLAUTH_DIR, "watchdog.log")}`);
151
- const status = await getWatchdogStatuses();
152
- console.log(`Services: ${status.total} registered (${status.healthy} healthy, ${status.degraded} degraded, ${status.unreachable} unreachable)`);
153
- console.log(`Registry: ${getRegistryPath()}`);
154
- } else {
155
- console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
156
- console.log("Run: clauth watchdog install");
157
- }
158
- } catch {
159
- console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
160
- console.log("Run: clauth watchdog install");
161
- }
162
- return;
163
- }
164
-
165
- if (action === "install") {
166
- ensureWatchdogScript();
167
- console.log(`\n Installing clauth watchdog...`);
168
- console.log(` Script: ${DEST_PS1}`);
169
- console.log(` Task: ${TASK_NAME} (runs at login)\n`);
170
- console.log(" Windows will ask for administrator approval — please click Yes.\n");
171
-
172
- const cmd = buildRegisterCmd(DEST_PS1);
173
- const ok = isElevated() ? runDirect(cmd) : runElevated(cmd);
174
-
175
- if (ok) {
176
- console.log("\n Watchdog installed. Starting now...");
177
- spawnSync(PS_EXE, [
178
- "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
179
- ], { stdio: "pipe" });
180
- console.log(" Done. Watchdog is running.\n");
181
- } else {
182
- console.log("\n Installation cancelled or failed.");
183
- console.log(` To install manually, run as Administrator:`);
184
- console.log(` clauth watchdog install\n`);
185
- }
186
- return;
187
- }
188
-
189
- if (action === "uninstall") {
190
- console.log("\n Removing clauth watchdog...");
191
- console.log(" Windows will ask for administrator approval — please click Yes.\n");
192
- const ok = isElevated() ? runDirect(buildUnregisterCmd()) : runElevated(buildUnregisterCmd());
193
- if (ok) console.log("\n Watchdog removed.\n");
194
- else console.log("\n Removal cancelled or failed.\n");
195
- return;
196
- }
197
-
198
- if (action === "start") {
199
- console.log(` Starting ${TASK_NAME}...`);
200
- const r = spawnSync(PS_EXE, [
201
- "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
202
- ], { stdio: "inherit", encoding: "utf8" });
203
- if (r.status === 0) console.log(" Watchdog started.");
204
- else console.log(" Could not start — is it registered? Run: clauth watchdog install");
205
- return;
206
- }
207
-
208
- console.log("Usage: clauth watchdog [install|uninstall|status|start|register|list|events|restart]");
209
- }
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
+ import {
14
+ getRegistryPath,
15
+ getWatchdogStatuses,
16
+ readWatchdogEvents,
17
+ registerWatchdogManifest,
18
+ restartWatchdogService,
19
+ } from "../watchdog-registry.js";
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const TASK_NAME = "ClautWatchdog";
23
+ const APPDATA = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
24
+ const CLAUTH_DIR = path.join(APPDATA, "clauth");
25
+ const DEST_PS1 = path.join(CLAUTH_DIR, "watchdog.ps1");
26
+ const SOURCE_PS1 = path.join(__dirname, "..", "assets", "watchdog.ps1");
27
+ const PS_EXE = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
28
+
29
+ function ensureWatchdogScript() {
30
+ fs.mkdirSync(CLAUTH_DIR, { recursive: true });
31
+ fs.copyFileSync(SOURCE_PS1, DEST_PS1);
32
+ }
33
+
34
+ function isElevated() {
35
+ try {
36
+ const result = execSync(`${PS_EXE} -NoProfile -Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"`,
37
+ { stdio: "pipe", encoding: "utf8" });
38
+ return result.trim().toLowerCase() === "true";
39
+ } catch { return false; }
40
+ }
41
+
42
+ // Build the PowerShell registration command as a single string
43
+ function buildRegisterCmd(ps1Path) {
44
+ const escaped = ps1Path.replace(/'/g, "''");
45
+ return [
46
+ `$action = New-ScheduledTaskAction -Execute 'powershell.exe'`,
47
+ ` -Argument '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \\"${escaped}\\"';`,
48
+ `$trigger = New-ScheduledTaskTrigger -AtLogOn;`,
49
+ `$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3`,
50
+ ` -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew;`,
51
+ `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue;`,
52
+ `Register-ScheduledTask -TaskName '${TASK_NAME}' -Action $action -Trigger $trigger`,
53
+ ` -Settings $settings -RunLevel Highest`,
54
+ ` -Description 'clauth daemon watchdog — restarts on port 52437 if not responding.';`,
55
+ `Write-Host 'Watchdog registered.'`,
56
+ ].join(" ");
57
+ }
58
+
59
+ function buildUnregisterCmd() {
60
+ return `Unregister-ScheduledTask -TaskName '${TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue; Write-Host 'Watchdog removed.'`;
61
+ }
62
+
63
+ function runElevated(psCmd) {
64
+ // Wrap in Start-Process -Verb RunAs to trigger UAC dialog
65
+ const encoded = Buffer.from(psCmd, "utf16le").toString("base64");
66
+ const result = spawnSync(PS_EXE, [
67
+ "-NoProfile", "-Command",
68
+ `Start-Process -FilePath '${PS_EXE}' -Verb RunAs -Wait -ArgumentList '-NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded}'`,
69
+ ], { stdio: "inherit", encoding: "utf8" });
70
+ return result.status === 0;
71
+ }
72
+
73
+ function runDirect(psCmd) {
74
+ const result = spawnSync(PS_EXE, [
75
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd,
76
+ ], { stdio: "inherit", encoding: "utf8" });
77
+ return result.status === 0;
78
+ }
79
+
80
+ function readScheduledTaskState() {
81
+ try {
82
+ const out = execSync(
83
+ `${PS_EXE} -NoProfile -Command "Get-ScheduledTask -TaskName '${TASK_NAME}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty State"`,
84
+ { encoding: "utf8", stdio: ["pipe","pipe","pipe"], timeout: 5000 }
85
+ ).trim();
86
+ if (out) return out;
87
+ } catch {}
88
+
89
+ try {
90
+ const out = execSync(`schtasks /Query /TN ${TASK_NAME} /FO LIST`, {
91
+ encoding: "utf8",
92
+ stdio: ["pipe","pipe","pipe"],
93
+ timeout: 5000,
94
+ });
95
+ const status = out.match(/^Status:\\s*(.+)$/mi)?.[1]?.trim();
96
+ return status || "Registered";
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ export async function runWatchdog(action, opts = {}) {
103
+ if (action === "register") {
104
+ const manifestPath = opts.manifest || opts.args?.[0];
105
+ if (!manifestPath) {
106
+ console.log("Usage: clauth watchdog register <manifest.json>");
107
+ return;
108
+ }
109
+ const manifest = JSON.parse(fs.readFileSync(path.resolve(manifestPath), "utf8"));
110
+ const result = registerWatchdogManifest(manifest);
111
+ console.log(`Registered ${result.registered} watchdog service(s): ${result.services.join(", ")}`);
112
+ console.log(`Registry: ${getRegistryPath()}`);
113
+ return;
114
+ }
115
+
116
+ if (action === "list" || action === "services") {
117
+ const status = await getWatchdogStatuses();
118
+ console.log(JSON.stringify(status, null, 2));
119
+ return;
120
+ }
121
+
122
+ if (action === "events") {
123
+ const limit = opts.limit ? Number(opts.limit) : 100;
124
+ console.log(JSON.stringify(readWatchdogEvents(limit), null, 2));
125
+ return;
126
+ }
127
+
128
+ if (action === "restart") {
129
+ const serviceId = opts.service || opts.args?.[0];
130
+ if (!serviceId) {
131
+ console.log("Usage: clauth watchdog restart <service-id>");
132
+ return;
133
+ }
134
+ const result = restartWatchdogService(serviceId);
135
+ console.log(JSON.stringify(result, null, 2));
136
+ return;
137
+ }
138
+
139
+ if (os.platform() !== "win32") {
140
+ console.log("Watchdog auto-start is Windows-only. On Linux/macOS, use systemd or launchd.");
141
+ return;
142
+ }
143
+
144
+ if (action === "status" || !action) {
145
+ const state = readScheduledTaskState();
146
+ try {
147
+ if (state) {
148
+ console.log(`Watchdog task: ${TASK_NAME} — State: ${state}`);
149
+ console.log(`Script: ${DEST_PS1}`);
150
+ console.log(`Log: ${path.join(CLAUTH_DIR, "watchdog.log")}`);
151
+ const status = await getWatchdogStatuses();
152
+ console.log(`Services: ${status.total} registered (${status.healthy} healthy, ${status.degraded} degraded, ${status.unreachable} unreachable)`);
153
+ console.log(`Registry: ${getRegistryPath()}`);
154
+ } else {
155
+ console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
156
+ console.log("Run: clauth watchdog install");
157
+ }
158
+ } catch {
159
+ console.log(`Watchdog task '${TASK_NAME}' is NOT registered.`);
160
+ console.log("Run: clauth watchdog install");
161
+ }
162
+ return;
163
+ }
164
+
165
+ if (action === "install") {
166
+ ensureWatchdogScript();
167
+ console.log(`\n Installing clauth watchdog...`);
168
+ console.log(` Script: ${DEST_PS1}`);
169
+ console.log(` Task: ${TASK_NAME} (runs at login)\n`);
170
+ console.log(" Windows will ask for administrator approval — please click Yes.\n");
171
+
172
+ const cmd = buildRegisterCmd(DEST_PS1);
173
+ const ok = isElevated() ? runDirect(cmd) : runElevated(cmd);
174
+
175
+ if (ok) {
176
+ console.log("\n Watchdog installed. Starting now...");
177
+ spawnSync(PS_EXE, [
178
+ "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
179
+ ], { stdio: "pipe" });
180
+ console.log(" Done. Watchdog is running.\n");
181
+ } else {
182
+ console.log("\n Installation cancelled or failed.");
183
+ console.log(` To install manually, run as Administrator:`);
184
+ console.log(` clauth watchdog install\n`);
185
+ }
186
+ return;
187
+ }
188
+
189
+ if (action === "uninstall") {
190
+ console.log("\n Removing clauth watchdog...");
191
+ console.log(" Windows will ask for administrator approval — please click Yes.\n");
192
+ const ok = isElevated() ? runDirect(buildUnregisterCmd()) : runElevated(buildUnregisterCmd());
193
+ if (ok) console.log("\n Watchdog removed.\n");
194
+ else console.log("\n Removal cancelled or failed.\n");
195
+ return;
196
+ }
197
+
198
+ if (action === "start") {
199
+ console.log(` Starting ${TASK_NAME}...`);
200
+ const r = spawnSync(PS_EXE, [
201
+ "-NoProfile", "-Command", `Start-ScheduledTask -TaskName '${TASK_NAME}'`,
202
+ ], { stdio: "inherit", encoding: "utf8" });
203
+ if (r.status === 0) console.log(" Watchdog started.");
204
+ else console.log(" Could not start — is it registered? Run: clauth watchdog install");
205
+ return;
206
+ }
207
+
208
+ console.log("Usage: clauth watchdog [install|uninstall|status|start|register|list|events|restart]");
209
+ }
package/cli/conf-path.js CHANGED
@@ -1,21 +1,21 @@
1
- // conf-path.js
2
- // Returns Conf options that always resolve to the real system AppData on Windows.
3
- //
4
- // Problem: When clauth runs inside an Electron app (e.g. Claude Desktop), %APPDATA%
5
- // is sandboxed to a package-specific path. The daemon runs as a plain system process
6
- // using the real %APPDATA%, so the two processes write/read different config files.
7
- //
8
- // Fix: On Windows, always build the path from %USERPROFILE% which is never sandboxed.
9
- // On Mac/Linux Conf's default behavior is correct.
10
-
11
- import os from "os";
12
- import path from "path";
13
-
14
- export function getConfOptions() {
15
- if (process.platform === "win32") {
16
- const userProfile = process.env.USERPROFILE || os.homedir();
17
- const cwd = path.join(userProfile, "AppData", "Roaming", "clauth-nodejs", "Config");
18
- return { projectName: "clauth", cwd };
19
- }
20
- return { projectName: "clauth" };
21
- }
1
+ // conf-path.js
2
+ // Returns Conf options that always resolve to the real system AppData on Windows.
3
+ //
4
+ // Problem: When clauth runs inside an Electron app (e.g. Claude Desktop), %APPDATA%
5
+ // is sandboxed to a package-specific path. The daemon runs as a plain system process
6
+ // using the real %APPDATA%, so the two processes write/read different config files.
7
+ //
8
+ // Fix: On Windows, always build the path from %USERPROFILE% which is never sandboxed.
9
+ // On Mac/Linux Conf's default behavior is correct.
10
+
11
+ import os from "os";
12
+ import path from "path";
13
+
14
+ export function getConfOptions() {
15
+ if (process.platform === "win32") {
16
+ const userProfile = process.env.USERPROFILE || os.homedir();
17
+ const cwd = path.join(userProfile, "AppData", "Roaming", "clauth-nodejs", "Config");
18
+ return { projectName: "clauth", cwd };
19
+ }
20
+ return { projectName: "clauth" };
21
+ }
@@ -1,82 +1,82 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
-
5
- function shellSingleQuote(value) {
6
- return `'${String(value ?? "").replace(/'/g, "''")}'`;
7
- }
8
-
9
- function posixShellQuote(value) {
10
- return `'${String(value ?? "").replace(/'/g, "'\\\"'\\\"'")}'`;
11
- }
12
-
13
- export function enrollmentScriptName(label, target = "windows") {
14
- const slug = String(label || "new-computer")
15
- .toLowerCase()
16
- .replace(/[^a-z0-9]+/g, "-")
17
- .replace(/^-+|-+$/g, "")
18
- .slice(0, 40) || "new-computer";
19
- return `clauth-enroll-${slug}${target === "linux" ? ".sh" : ".ps1"}`;
20
- }
21
-
22
- function windowsScript({ supabaseUrl, anonKey, enrollmentCode }) {
23
- return [
24
- "$ErrorActionPreference = 'Stop'",
25
- "$label = $env:COMPUTERNAME",
26
- "if (-not $label) { $label = [System.Net.Dns]::GetHostName() }",
27
- "npm install -g @lifeaitools/clauth@latest",
28
- ["clauth setup", `--supabase-url ${shellSingleQuote(supabaseUrl)}`, `--anon-key ${shellSingleQuote(anonKey)}`, `--enrollment-code ${shellSingleQuote(enrollmentCode)}`, "--label \"$label\""].join(" "),
29
- "clauth serve install",
30
- "$self = $PSCommandPath",
31
- "Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @('-NoProfile','-Command',\"Start-Sleep -Seconds 2; & ('Remove' + '-Item') -LiteralPath '$self' -Force -ErrorAction SilentlyContinue\")",
32
- ].join("\r\n");
33
- }
34
-
35
- function linuxScript({ supabaseUrl, anonKey, enrollmentCode }) {
36
- return [
37
- "#!/usr/bin/env sh",
38
- "set -eu",
39
- "label=$(hostname)",
40
- "as_root() { if [ \"$(id -u)\" -eq 0 ]; then \"$@\"; elif command -v sudo >/dev/null; then sudo \"$@\"; else echo 'Root or sudo is required to install prerequisites.' >&2; exit 1; fi; }",
41
- "install_prerequisites() {",
42
- " if command -v apt-get >/dev/null; then as_root apt-get update; as_root apt-get install -y nodejs npm openssl;",
43
- " elif command -v dnf >/dev/null; then as_root dnf install -y nodejs npm openssl;",
44
- " else echo 'Headless enrollment supports systemd hosts with apt-get or dnf. Install Node.js 18+, npm, and openssl, then rerun this script.' >&2; exit 1; fi",
45
- "}",
46
- "if ! command -v systemctl >/dev/null || ! command -v loginctl >/dev/null; then echo 'Headless enrollment requires systemd and loginctl.' >&2; exit 1; fi",
47
- "if ! command -v node >/dev/null || ! command -v npm >/dev/null || ! command -v openssl >/dev/null; then install_prerequisites; fi",
48
- "node_major=$(node -p \"process.versions.node.split('.')[0]\")",
49
- "if [ \"$node_major\" -lt 18 ]; then echo 'Node.js 18+ is required.' >&2; exit 1; fi",
50
- "if ! npm install -g @lifeaitools/clauth@latest; then as_root npm install -g @lifeaitools/clauth@latest; fi",
51
- ["clauth setup", `--supabase-url ${posixShellQuote(supabaseUrl)}`, `--anon-key ${posixShellQuote(anonKey)}`, `--enrollment-code ${posixShellQuote(enrollmentCode)}`, '--label "$label"'].join(" "),
52
- "user_name=$(id -un)",
53
- "if loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; elif as_root loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; else echo 'Could not enable linger for unattended restart.' >&2; exit 1; fi",
54
- "if ! test -t 0; then echo 'Headless enrollment requires an interactive TTY to set the vault password.' >&2; exit 1; fi",
55
- "restore_echo() { stty echo 2>/dev/null || true; }",
56
- "trap restore_echo EXIT HUP INT TERM",
57
- "printf 'Re-enter the vault password to enable unattended restart: ' >&2",
58
- "stty -echo",
59
- "IFS= read -r vault_password",
60
- "stty echo",
61
- "trap - EXIT HUP INT TERM",
62
- "printf '\\n' >&2",
63
- "[ -n \"$vault_password\" ] || { echo 'A vault password is required for unattended restart.' >&2; exit 1; }",
64
- "printf %s \"$vault_password\" | clauth serve install --pw-stdin",
65
- "unset vault_password",
66
- "rm -f -- \"$0\"",
67
- ].join("\n");
68
- }
69
-
70
- export function writeEnrollmentScript({ supabaseUrl, anonKey, enrollmentCode, label, target = "windows", appDir } = {}) {
71
- if (!["windows", "linux"].includes(target)) throw new Error(`Unsupported enrollment target: ${target}`);
72
- const outputDir = appDir || (process.platform === "win32"
73
- ? path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "clauth")
74
- : path.join(os.homedir(), ".config", "clauth"));
75
- fs.mkdirSync(outputDir, { recursive: true });
76
- const scriptPath = path.join(outputDir, enrollmentScriptName(label, target));
77
- const script = target === "linux"
78
- ? linuxScript({ supabaseUrl, anonKey, enrollmentCode })
79
- : windowsScript({ supabaseUrl, anonKey, enrollmentCode });
80
- fs.writeFileSync(scriptPath, `${script}\n`, { encoding: "utf8", mode: target === "linux" ? 0o700 : undefined });
81
- return scriptPath;
82
- }
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+
5
+ function shellSingleQuote(value) {
6
+ return `'${String(value ?? "").replace(/'/g, "''")}'`;
7
+ }
8
+
9
+ function posixShellQuote(value) {
10
+ return `'${String(value ?? "").replace(/'/g, "'\\\"'\\\"'")}'`;
11
+ }
12
+
13
+ export function enrollmentScriptName(label, target = "windows") {
14
+ const slug = String(label || "new-computer")
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9]+/g, "-")
17
+ .replace(/^-+|-+$/g, "")
18
+ .slice(0, 40) || "new-computer";
19
+ return `clauth-enroll-${slug}${target === "linux" ? ".sh" : ".ps1"}`;
20
+ }
21
+
22
+ function windowsScript({ supabaseUrl, anonKey, enrollmentCode }) {
23
+ return [
24
+ "$ErrorActionPreference = 'Stop'",
25
+ "$label = $env:COMPUTERNAME",
26
+ "if (-not $label) { $label = [System.Net.Dns]::GetHostName() }",
27
+ "npm install -g @lifeaitools/clauth@latest",
28
+ ["clauth setup", `--supabase-url ${shellSingleQuote(supabaseUrl)}`, `--anon-key ${shellSingleQuote(anonKey)}`, `--enrollment-code ${shellSingleQuote(enrollmentCode)}`, "--label \"$label\""].join(" "),
29
+ "clauth serve install",
30
+ "$self = $PSCommandPath",
31
+ "Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @('-NoProfile','-Command',\"Start-Sleep -Seconds 2; & ('Remove' + '-Item') -LiteralPath '$self' -Force -ErrorAction SilentlyContinue\")",
32
+ ].join("\r\n");
33
+ }
34
+
35
+ function linuxScript({ supabaseUrl, anonKey, enrollmentCode }) {
36
+ return [
37
+ "#!/usr/bin/env sh",
38
+ "set -eu",
39
+ "label=$(hostname)",
40
+ "as_root() { if [ \"$(id -u)\" -eq 0 ]; then \"$@\"; elif command -v sudo >/dev/null; then sudo \"$@\"; else echo 'Root or sudo is required to install prerequisites.' >&2; exit 1; fi; }",
41
+ "install_prerequisites() {",
42
+ " if command -v apt-get >/dev/null; then as_root apt-get update; as_root apt-get install -y nodejs npm openssl;",
43
+ " elif command -v dnf >/dev/null; then as_root dnf install -y nodejs npm openssl;",
44
+ " else echo 'Headless enrollment supports systemd hosts with apt-get or dnf. Install Node.js 18+, npm, and openssl, then rerun this script.' >&2; exit 1; fi",
45
+ "}",
46
+ "if ! command -v systemctl >/dev/null || ! command -v loginctl >/dev/null; then echo 'Headless enrollment requires systemd and loginctl.' >&2; exit 1; fi",
47
+ "if ! command -v node >/dev/null || ! command -v npm >/dev/null || ! command -v openssl >/dev/null; then install_prerequisites; fi",
48
+ "node_major=$(node -p \"process.versions.node.split('.')[0]\")",
49
+ "if [ \"$node_major\" -lt 18 ]; then echo 'Node.js 18+ is required.' >&2; exit 1; fi",
50
+ "if ! npm install -g @lifeaitools/clauth@latest; then as_root npm install -g @lifeaitools/clauth@latest; fi",
51
+ ["clauth setup", `--supabase-url ${posixShellQuote(supabaseUrl)}`, `--anon-key ${posixShellQuote(anonKey)}`, `--enrollment-code ${posixShellQuote(enrollmentCode)}`, '--label "$label"'].join(" "),
52
+ "user_name=$(id -un)",
53
+ "if loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; elif as_root loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; else echo 'Could not enable linger for unattended restart.' >&2; exit 1; fi",
54
+ "if ! test -t 0; then echo 'Headless enrollment requires an interactive TTY to set the vault password.' >&2; exit 1; fi",
55
+ "restore_echo() { stty echo 2>/dev/null || true; }",
56
+ "trap restore_echo EXIT HUP INT TERM",
57
+ "printf 'Re-enter the vault password to enable unattended restart: ' >&2",
58
+ "stty -echo",
59
+ "IFS= read -r vault_password",
60
+ "stty echo",
61
+ "trap - EXIT HUP INT TERM",
62
+ "printf '\\n' >&2",
63
+ "[ -n \"$vault_password\" ] || { echo 'A vault password is required for unattended restart.' >&2; exit 1; }",
64
+ "printf %s \"$vault_password\" | clauth serve install --pw-stdin",
65
+ "unset vault_password",
66
+ "rm -f -- \"$0\"",
67
+ ].join("\n");
68
+ }
69
+
70
+ export function writeEnrollmentScript({ supabaseUrl, anonKey, enrollmentCode, label, target = "windows", appDir } = {}) {
71
+ if (!["windows", "linux"].includes(target)) throw new Error(`Unsupported enrollment target: ${target}`);
72
+ const outputDir = appDir || (process.platform === "win32"
73
+ ? path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "clauth")
74
+ : path.join(os.homedir(), ".config", "clauth"));
75
+ fs.mkdirSync(outputDir, { recursive: true });
76
+ const scriptPath = path.join(outputDir, enrollmentScriptName(label, target));
77
+ const script = target === "linux"
78
+ ? linuxScript({ supabaseUrl, anonKey, enrollmentCode })
79
+ : windowsScript({ supabaseUrl, anonKey, enrollmentCode });
80
+ fs.writeFileSync(scriptPath, `${script}\n`, { encoding: "utf8", mode: target === "linux" ? 0o700 : undefined });
81
+ return scriptPath;
82
+ }