@lifeaitools/clauth 2.1.1 → 2.10.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 (98) hide show
  1. package/.clauth-skill/references/keys-guide.md +270 -270
  2. package/cli/api.js +238 -238
  3. package/cli/commands/install.js +413 -396
  4. package/cli/commands/login.test.js +49 -1
  5. package/cli/commands/ops-install.js +11 -10
  6. package/cli/commands/ops.test.js +270 -0
  7. package/cli/commands/scrub.test.js +70 -0
  8. package/cli/commands/serve.js +2613 -11219
  9. package/cli/commands/uninstall.js +164 -164
  10. package/cli/dashboard/dashboard.js +592 -0
  11. package/cli/dashboard/global.css +65 -0
  12. package/cli/dashboard/panel-element.js +79 -0
  13. package/cli/dashboard/panels/build-status-panel.css +12 -0
  14. package/cli/dashboard/panels/build-status-panel.js +98 -0
  15. package/cli/dashboard/panels/panel-registry.js +58 -0
  16. package/cli/dashboard/panels/services-panel.css +95 -0
  17. package/cli/dashboard/panels/services-panel.js +872 -0
  18. package/cli/dashboard/panels/surfaces-panel.css +59 -0
  19. package/cli/dashboard/panels/surfaces-panel.js +474 -0
  20. package/cli/dashboard/panels/toolbar-panel.css +42 -0
  21. package/cli/dashboard/panels/toolbar-panel.js +374 -0
  22. package/cli/dashboard/panels/tunnel-panel.css +61 -0
  23. package/cli/dashboard/panels/tunnel-panel.js +688 -0
  24. package/cli/dashboard/panels/webdav-panel.css +38 -0
  25. package/cli/dashboard/panels/webdav-panel.js +166 -0
  26. package/cli/fingerprint.test.js +95 -0
  27. package/cli/http/components/callagent-terminal-component.js +310 -0
  28. package/cli/http/components/dashboard-component.js +123 -0
  29. package/cli/http/components/inbox-component.js +41 -0
  30. package/cli/http/components/mcp-transport-component.js +275 -0
  31. package/cli/http/components/oauth-component.js +207 -0
  32. package/cli/http/components/service-crud-component.js +288 -0
  33. package/cli/http/components/supervisor-component.js +104 -0
  34. package/cli/http/components/system-component.js +184 -0
  35. package/cli/http/components/tunnel-component.js +508 -0
  36. package/cli/http/components/vault-auth-component.js +264 -0
  37. package/cli/http/components/watchdog-component.js +28 -0
  38. package/cli/http/request-utils.js +21 -0
  39. package/cli/http/servers/ops-server.js +335 -0
  40. package/cli/index.js +5 -91
  41. package/cli/lib/fs-git.test.js +467 -0
  42. package/cli/mcp/providers/call-agent-provider.js +30 -0
  43. package/cli/mcp/providers/clauth-core-provider.js +461 -0
  44. package/cli/mcp/providers/ops-provider.js +42 -0
  45. package/cli/mcp/providers/terminal-provider.js +81 -0
  46. package/cli/mcp/tool-registry.js +48 -0
  47. package/cli/mcp/tool-schemas.js +822 -0
  48. package/cli/mcp/tool-schemas.test.js +49 -0
  49. package/cli/ops/pm2-preflight.js +54 -0
  50. package/cli/recovery.js +6 -0
  51. package/cli/recovery.test.js +179 -0
  52. package/cli/services/agent-pool-instance.js +38 -0
  53. package/cli/services/claude-binary.js +29 -0
  54. package/cli/services/delegation-lane-instance.js +35 -0
  55. package/cli/services/delegation-lane-instance.test.js +37 -0
  56. package/cli/services/file-io.js +38 -0
  57. package/cli/services/file-io.test.js +10 -0
  58. package/cli/services/fs-mount.js +66 -0
  59. package/cli/services/fs-mount.test.js +14 -0
  60. package/cli/services/fs-upload-sessions.js +20 -0
  61. package/cli/services/fs-upload-sessions.test.js +14 -0
  62. package/cli/services/git-exec.js +37 -0
  63. package/cli/services/git-exec.test.js +41 -0
  64. package/cli/services/git-import-guard.js +28 -0
  65. package/cli/services/git-import-guard.test.js +18 -0
  66. package/cli/services/service-status.js +191 -0
  67. package/cli/services/service-status.test.js +39 -0
  68. package/cli/services/terminal-dispatch.js +135 -0
  69. package/cli/services/terminal-dispatch.test.js +9 -0
  70. package/cli/services/terminal-registry.js +39 -0
  71. package/cli/services/terminal-registry.test.js +31 -0
  72. package/cli/services/terminal-sessions.js +100 -0
  73. package/cli/services/terminal-sessions.test.js +26 -0
  74. package/cli/services/terminal-window.js +106 -0
  75. package/cli/services/vault-read.js +14 -0
  76. package/cli/services/vault-read.test.js +35 -0
  77. package/cli/supervisor-registry.js +116 -0
  78. package/cli/supervisor-registry.test.js +165 -0
  79. package/cli/supervisor-ui.test.js +230 -30
  80. package/cli/webdav-config.js +84 -0
  81. package/cli/webdav-config.test.js +79 -0
  82. package/cli/webdav-obscure.js +23 -0
  83. package/cli/webdav-obscure.test.js +41 -0
  84. package/cli/webdav-rclone-discovery.js +44 -0
  85. package/cli/webdav-service.js +30 -118
  86. package/cli/webdav-service.test.js +107 -0
  87. package/install.ps1 +102 -102
  88. package/install.sh +49 -49
  89. package/package.json +2 -2
  90. package/scripts/bootstrap.cjs +121 -121
  91. package/supabase/functions/auth-vault/index.ts +350 -350
  92. package/supabase/migrations/001_clauth_schema.sql +94 -94
  93. package/supabase/migrations/002_vault_helpers.sql +90 -90
  94. package/supabase/migrations/20260317_lockout.sql +26 -26
  95. package/cli/assets/codevelop/launcher-active.cmd.template +0 -20
  96. package/cli/assets/codevelop/launcher-static.cmd.template +0 -7
  97. package/cli/assets/codevelop/windows-terminal.profiles.json +0 -48
  98. package/cli/commands/codevelop.js +0 -1190
@@ -0,0 +1,44 @@
1
+ // cli/webdav-rclone-discovery.js
2
+ // Locates a usable rclone binary across the common Windows install shapes
3
+ // (PATH, Program Files, scoop, winget's version-stamped package dir). Split
4
+ // out of webdav-service.js -- the SOLID scorer flagged that file's original
5
+ // shape as mixing this discovery concern with the rclone child supervisor,
6
+ // config persistence, and the rclone-obscure crypto helper.
7
+
8
+ import { execSync } from "node:child_process";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { readdirSync } from "node:fs";
12
+
13
+ export function findRclone() {
14
+ const home = homedir();
15
+ const wingetBase = process.env.LOCALAPPDATA
16
+ ? join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Packages")
17
+ : null;
18
+ const execOpts = { stdio: "pipe", timeout: 4000, windowsHide: true };
19
+
20
+ const candidates = [
21
+ "rclone",
22
+ "C:\\Program Files\\rclone\\rclone.exe",
23
+ join(home, "scoop", "apps", "rclone", "current", "rclone.exe"),
24
+ "C:\\ProgramData\\scoop\\apps\\rclone\\current\\rclone.exe",
25
+ ];
26
+
27
+ // Winget installs into a version-stamped subdirectory — glob for it
28
+ if (wingetBase) {
29
+ const wingetPkg = join(wingetBase, "Rclone.Rclone_Microsoft.Winget.Source_8wekyb3d8bbwe");
30
+ try {
31
+ for (const d of readdirSync(wingetPkg)) {
32
+ if (d.startsWith("rclone-")) candidates.push(join(wingetPkg, d, "rclone.exe"));
33
+ }
34
+ } catch {}
35
+ }
36
+
37
+ for (const c of candidates) {
38
+ try {
39
+ execSync(`"${c}" version`, execOpts);
40
+ return c;
41
+ } catch {}
42
+ }
43
+ return null;
44
+ }
@@ -8,78 +8,24 @@
8
8
  // Design: lazy-start. The rclone child spawns on the first fs_dav_setup call.
9
9
  // Subsequent calls return the running child's status without restarting.
10
10
  // On child crash: exponential-backoff restart loop (2s → 5s → 15s → 60s).
11
-
12
- import { spawn, execSync } from "child_process";
13
- import { writeFileSync, readFileSync, unlinkSync, mkdirSync, readdirSync } from "fs";
14
- import { tmpdir, homedir } from "os";
11
+ //
12
+ // Config CRUD moved to webdav-config.js, the rclone-obscure crypto helper
13
+ // moved to webdav-obscure.js, and rclone binary discovery moved to
14
+ // webdav-rclone-discovery.js after the SOLID scorer flagged this file's
15
+ // original shape (74.8 total) as mixing all four concerns.
16
+
17
+ import { spawn } from "child_process";
18
+ import { writeFileSync, unlinkSync } from "fs";
19
+ import { tmpdir } from "os";
15
20
  import { join } from "path";
16
- import crypto from "crypto";
17
21
  import * as api from "./api.js";
18
22
  import { deriveToken } from "./fingerprint.js";
23
+ import { loadConfig, WEBDAV_PORT, CRED_NAME } from "./webdav-config.js";
24
+ import { findRclone } from "./webdav-rclone-discovery.js";
19
25
 
20
- const WEBDAV_PORT = 8620;
21
26
  const WEBDAV_USER = "claude";
22
- const CRED_NAME = "webdav-claude";
23
27
  const RESTART_DELAYS = [2000, 5000, 15000, 60000];
24
28
 
25
- const APPDATA = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
26
- const CONFIG_PATH = join(APPDATA, "clauth", "webdav-config.json");
27
-
28
- const DEFAULT_CONFIG = {
29
- enabled: true,
30
- port: WEBDAV_PORT,
31
- credential: CRED_NAME,
32
- upstreams: [],
33
- };
34
-
35
- // ── Config persistence ──────────────────────────────────────────
36
-
37
- export function loadConfig() {
38
- try {
39
- const raw = readFileSync(CONFIG_PATH, "utf8");
40
- const cfg = JSON.parse(raw);
41
- if (!Array.isArray(cfg.upstreams)) cfg.upstreams = [];
42
- return { ...DEFAULT_CONFIG, ...cfg };
43
- } catch {
44
- return { ...DEFAULT_CONFIG };
45
- }
46
- }
47
-
48
- export function saveConfig(cfg) {
49
- const dir = join(APPDATA, "clauth");
50
- mkdirSync(dir, { recursive: true });
51
- writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), "utf8");
52
- _fsCfgCache = null;
53
- }
54
-
55
- export function addMount(name, mountPath) {
56
- const cfg = loadConfig();
57
- if (cfg.upstreams.some(u => u.name === name)) return { error: `Mount '${name}' already exists` };
58
- cfg.upstreams.push({ name, path: mountPath });
59
- saveConfig(cfg);
60
- return { ok: true, upstreams: cfg.upstreams };
61
- }
62
-
63
- export function removeMount(name) {
64
- const cfg = loadConfig();
65
- const before = cfg.upstreams.length;
66
- cfg.upstreams = cfg.upstreams.filter(u => u.name !== name);
67
- if (cfg.upstreams.length === before) return { error: `Mount '${name}' not found` };
68
- saveConfig(cfg);
69
- return { ok: true, upstreams: cfg.upstreams };
70
- }
71
-
72
- export function updateMount(name, updates) {
73
- const cfg = loadConfig();
74
- const mount = cfg.upstreams.find(u => u.name === name);
75
- if (!mount) return { error: `Mount '${name}' not found` };
76
- if (updates.credential !== undefined) cfg.credential = updates.credential;
77
- saveConfig(cfg);
78
- return { ok: true, upstreams: cfg.upstreams };
79
- }
80
-
81
- let _fsCfgCache = null;
82
-
83
29
  // ── Module state ────────────────────────────────────────────────
84
30
  let _child = null;
85
31
  let _configPath = null;
@@ -131,25 +77,6 @@ export function shutdown() {
131
77
  _status = "stopped";
132
78
  }
133
79
 
134
- /**
135
- * rclone-compatible AES-CTR "obscure" — same algorithm as `rclone obscure`.
136
- * Used by fs_dav_setup to embed a non-plaintext credential in the mount script.
137
- * The result can be passed as RCLONE_CONFIG_xxx_PASS.
138
- */
139
- export function rcloneObscure(password) {
140
- // Key from rclone source: lib/obscure/obscure.go cryptKey
141
- const key = Buffer.from([
142
- 0x9c, 0x93, 0x5b, 0x48, 0x73, 0x0a, 0x55, 0x4d,
143
- 0x6b, 0xfd, 0x7c, 0x63, 0xc8, 0x86, 0xd9, 0x2d,
144
- 0x02, 0x01, 0x99, 0x87, 0x54, 0xd3, 0x9b, 0xeb,
145
- 0x40, 0x87, 0x3f, 0xa1, 0x3f, 0x7a, 0xbe, 0x5e,
146
- ]);
147
- const iv = crypto.randomBytes(16);
148
- const cipher = crypto.createCipheriv("aes-256-ctr", key, iv);
149
- const ct = Buffer.concat([cipher.update(Buffer.from(password, "utf8")), cipher.final()]);
150
- return Buffer.concat([iv, ct]).toString("base64url");
151
- }
152
-
153
80
  // ── Internal ─────────────────────────────────────────────────────
154
81
 
155
82
  async function _startChild() {
@@ -187,7 +114,7 @@ async function _startChild() {
187
114
  }
188
115
 
189
116
  // 2. Locate rclone
190
- const rclonePath = _findRclone();
117
+ const rclonePath = findRclone();
191
118
  if (!rclonePath) {
192
119
  _status = "error";
193
120
  _error = "rclone not found — install with: winget install Rclone.Rclone";
@@ -298,42 +225,27 @@ function _scheduleRestart() {
298
225
  }, delay);
299
226
  }
300
227
 
228
+ // Test-only reset of the module-level supervisor state. The rclone child
229
+ // lifecycle is deliberately a module singleton (one WebDAV server per clauth
230
+ // process) rather than an instance, which is right for production and means a
231
+ // test file importing this module once needs a way to clear _status/_cfg/etc.
232
+ // between cases — otherwise the SECOND test in the file inherits whatever
233
+ // state the first one left behind.
234
+ export function __resetForTests() {
235
+ if (_child) { try { _child.removeAllListeners(); _child.kill("SIGKILL"); } catch {} }
236
+ _child = null;
237
+ _configPath = null;
238
+ _status = "stopped";
239
+ _error = null;
240
+ _restartCount = 0;
241
+ _startedAt = null;
242
+ _vaultFn = null;
243
+ _cfg = null;
244
+ }
245
+
301
246
  function _cleanupConfig() {
302
247
  if (_configPath) {
303
248
  try { unlinkSync(_configPath); } catch {}
304
249
  _configPath = null;
305
250
  }
306
251
  }
307
-
308
- function _findRclone() {
309
- const home = homedir();
310
- const wingetBase = process.env.LOCALAPPDATA
311
- ? join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Packages")
312
- : null;
313
- const execOpts = { stdio: "pipe", timeout: 4000, windowsHide: true };
314
-
315
- const candidates = [
316
- "rclone",
317
- "C:\\Program Files\\rclone\\rclone.exe",
318
- join(home, "scoop", "apps", "rclone", "current", "rclone.exe"),
319
- "C:\\ProgramData\\scoop\\apps\\rclone\\current\\rclone.exe",
320
- ];
321
-
322
- // Winget installs into a version-stamped subdirectory — glob for it
323
- if (wingetBase) {
324
- const wingetPkg = join(wingetBase, "Rclone.Rclone_Microsoft.Winget.Source_8wekyb3d8bbwe");
325
- try {
326
- for (const d of readdirSync(wingetPkg)) {
327
- if (d.startsWith("rclone-")) candidates.push(join(wingetPkg, d, "rclone.exe"));
328
- }
329
- } catch {}
330
- }
331
-
332
- for (const c of candidates) {
333
- try {
334
- execSync(`"${c}" version`, execOpts);
335
- return c;
336
- } catch {}
337
- }
338
- return null;
339
- }
@@ -0,0 +1,107 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ import { addMount, loadConfig } from "./webdav-config.js";
8
+ import { ensureRunning, getStatus, shutdown, __resetForTests } from "./webdav-service.js";
9
+
10
+ function withIsolatedConfig(fn) {
11
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-webdav-test-"));
12
+ const configPath = path.join(dir, "webdav-config.json");
13
+ const old = process.env.CLAUTH_WEBDAV_CONFIG_PATH;
14
+ process.env.CLAUTH_WEBDAV_CONFIG_PATH = configPath;
15
+ __resetForTests();
16
+ return (async () => {
17
+ try { return await fn(configPath); } finally {
18
+ __resetForTests();
19
+ if (old === undefined) Reflect.deleteProperty(process.env, "CLAUTH_WEBDAV_CONFIG_PATH");
20
+ else process.env.CLAUTH_WEBDAV_CONFIG_PATH = old;
21
+ fs.rmSync(dir, { recursive: true, force: true });
22
+ }
23
+ })();
24
+ }
25
+
26
+ test("getStatus with no config loaded reports stopped and no error", () =>
27
+ withIsolatedConfig(() => {
28
+ const status = getStatus();
29
+ assert.equal(status.status, "stopped");
30
+ assert.equal(status.url, null);
31
+ assert.equal(status.restart_count, 0);
32
+ }));
33
+
34
+ test("ensureRunning refuses when no mount points are configured", () =>
35
+ withIsolatedConfig(async () => {
36
+ // loadConfig() called with no mounts added in THIS test's isolated dir.
37
+ const result = await ensureRunning(() => ({ password: "pw", machineHash: "h" }), loadConfig());
38
+ assert.match(result.error, /No mount points configured/);
39
+ assert.equal(getStatus().status, "error");
40
+ }));
41
+
42
+ test("ensureRunning refuses when the vault is locked", () =>
43
+ withIsolatedConfig(async () => {
44
+ addMount("projects", "C:\\Dev\\regen-root");
45
+ const result = await ensureRunning(() => ({ password: null }), loadConfig());
46
+ assert.match(result.error, /Vault is locked/);
47
+ }));
48
+
49
+ test("ensureRunning is idempotent — a second call while already running short-circuits without re-resolving the vault", () =>
50
+ withIsolatedConfig(async () => {
51
+ addMount("projects", "C:\\Dev\\regen-root");
52
+ let vaultCalls = 0;
53
+ const vaultFn = () => { vaultCalls++; return { password: null }; }; // locked, so _startChild always errors
54
+ await ensureRunning(vaultFn, loadConfig());
55
+ assert.equal(vaultCalls, 1);
56
+ // ensureRunning only short-circuits on status==="running"||"starting"; a
57
+ // locked vault leaves status "error", so a second call legitimately
58
+ // re-resolves — this asserts that behavior rather than a false "cached".
59
+ await ensureRunning(vaultFn, loadConfig());
60
+ assert.equal(vaultCalls, 2, "an errored (not running/starting) state must retry, not silently no-op forever");
61
+ }));
62
+
63
+ test("ensureRunning proceeds past credential resolution once the vault yields a value", () =>
64
+ withIsolatedConfig(async () => {
65
+ // Deliberately does NOT let this reach _startChild's rclone spawn step.
66
+ // `findRclone()` was assumed absent on this box (`where rclone` exits 1),
67
+ // but that only checks PATH — this machine has rclone installed via winget
68
+ // at a version-stamped path findRclone()'s own glob fallback discovers
69
+ // (verified: C:/Users/.../AppData/Local/Microsoft/WinGet/Packages/
70
+ // Rclone.Rclone_.../rclone-v1.75.0-windows-amd64/rclone.exe exists). A test
71
+ // that reached the spawn step here would start a REAL rclone WebDAV server
72
+ // on port 8620 — a genuine side effect on a developer's real machine, not
73
+ // a safe thing for a unit test to risk regardless of which branch it hits.
74
+ //
75
+ // api.js's `retrieve` cannot be monkeypatched directly — ESM namespace
76
+ // exports are non-configurable, so `Object.defineProperty(api, "retrieve",
77
+ // ...)` throws "Cannot redefine property". It calls the global `fetch`
78
+ // underneath, which IS mockable, same technique as recovery.test.js.
79
+ addMount("projects", "C:\\Dev\\regen-root");
80
+ const oldFetch = globalThis.fetch;
81
+ const oldUrl = process.env.CLAUTH_SUPABASE_URL;
82
+ const oldKey = process.env.CLAUTH_SUPABASE_ANON_KEY;
83
+ process.env.CLAUTH_SUPABASE_URL = "https://example.supabase.co";
84
+ process.env.CLAUTH_SUPABASE_ANON_KEY = "anon-key-not-real";
85
+ let fetchCalled = false;
86
+ globalThis.fetch = async () => { fetchCalled = true; return { ok: true, status: 200, json: async () => ({ value: "" }) }; };
87
+ try {
88
+ // An EMPTY credential value hits `if (!result.value)` — the
89
+ // credential-not-found branch — which is a few lines before findRclone
90
+ // is ever called, so this can never reach the spawn step no matter what
91
+ // is or isn't installed on the box running it.
92
+ const result = await ensureRunning(() => ({ password: "pw", machineHash: "h" }), loadConfig());
93
+ assert.equal(fetchCalled, true, "control: the mock must actually have been invoked, or this proves nothing about the credential path");
94
+ assert.match(result.error, /not found — register it in the vault first/);
95
+ assert.equal(getStatus().status, "error");
96
+ } finally {
97
+ globalThis.fetch = oldFetch;
98
+ if (oldUrl === undefined) Reflect.deleteProperty(process.env, "CLAUTH_SUPABASE_URL"); else process.env.CLAUTH_SUPABASE_URL = oldUrl;
99
+ if (oldKey === undefined) Reflect.deleteProperty(process.env, "CLAUTH_SUPABASE_ANON_KEY"); else process.env.CLAUTH_SUPABASE_ANON_KEY = oldKey;
100
+ }
101
+ }));
102
+
103
+ test("shutdown resets status to stopped even when nothing is running", () =>
104
+ withIsolatedConfig(() => {
105
+ shutdown();
106
+ assert.equal(getStatus().status, "stopped");
107
+ }));
package/install.ps1 CHANGED
@@ -1,105 +1,105 @@
1
- # clauth installer - Windows
2
- # Private repo - run locally:
3
- # cd C:\Dev\clauth && git pull && .\install.ps1
4
-
5
- $ErrorActionPreference = "Stop"
6
- $REPO = "https://github.com/LIFEAI/clauth.git"
7
- $DIR = "$env:USERPROFILE\.clauth"
8
-
9
- # Check git
10
- try { git --version | Out-Null } catch {
11
- Write-Host ""
12
- Write-Host " x git is required." -ForegroundColor Red
13
- Write-Host " Install from https://git-scm.com then re-run this script."
14
- Write-Host ""
15
- exit 1
16
- }
17
-
18
- # Check Node
19
- try { node --version | Out-Null } catch {
20
- Write-Host ""
21
- Write-Host " x Node.js v18+ is required." -ForegroundColor Red
22
- Write-Host " Install from https://nodejs.org then re-run this script."
23
- Write-Host ""
24
- exit 1
25
- }
26
-
27
- # Check cloudflared (soft warning - not required for install)
28
- if (-not (Get-Command cloudflared -ErrorAction SilentlyContinue)) {
29
- Write-Host " Note: cloudflared not found. Install after setup for claude.ai web integration." -ForegroundColor Yellow
30
- Write-Host " winget install Cloudflare.cloudflared" -ForegroundColor Gray
31
- Write-Host ""
32
- }
33
-
34
- # Skip bootstrap if CLAUTH npm package is already installed globally
35
- $alreadyInstalled = $false
36
- try {
37
- $ver = & npm list -g @lifeaitools/clauth --depth=0 2>$null | Select-String "@lifeaitools/clauth"
38
- if ($ver) { $alreadyInstalled = $true }
39
- } catch { }
40
-
41
- if ($alreadyInstalled) {
42
- Write-Host " clauth already installed globally - skipping bootstrap." -ForegroundColor Green
43
- } else {
44
- # Clone or update
45
- if (Test-Path "$DIR\.git") {
46
- Write-Host " Updating clauth..."
47
- Set-Location $DIR; git pull --quiet
48
- } else {
49
- Write-Host " Cloning clauth..."
50
- git clone --quiet $REPO $DIR
51
- }
52
-
53
- # Run compiled bootstrap binary
54
- $bootstrap = "$DIR\scripts\bin\bootstrap-win.exe"
55
- if (-not (Test-Path $bootstrap)) {
56
- Write-Host " x Bootstrap binary not found at $bootstrap" -ForegroundColor Red
57
- exit 1
58
- }
59
-
60
- Set-Location $DIR
61
- & $bootstrap
62
- }
63
-
64
- # Register autostart - idempotent, runs every install/update
65
- # Primary: Task Scheduler (preferred - supports delay and OS-managed restarts)
66
- # Fallback: Startup folder shortcut (fully functional - autostart.ps1 has its own crash-recovery loop)
67
- $autostartScript = "$env:APPDATA\clauth\autostart.ps1"
68
- $taskName = "CLAUTH Daemon"
69
-
70
- if (Test-Path $autostartScript) {
71
- Write-Host ""
72
- Write-Host " Registering autostart..." -ForegroundColor Cyan
73
-
74
- $registered = $false
75
-
76
- # Try Task Scheduler first
77
- try {
78
- Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
79
- $action = New-ScheduledTaskAction -Execute "powershell.exe" `
80
- -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$autostartScript`""
81
- $trigger = New-ScheduledTaskTrigger -AtLogOn
82
- $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
83
- Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
84
- -Settings $settings -RunLevel Limited -Force -ErrorAction Stop | Out-Null
85
- Write-Host " + Task Scheduler: '$taskName' registered (triggers at logon)." -ForegroundColor Green
86
- $registered = $true
87
- } catch { }
88
-
89
- # Fallback: Startup folder - valid solution, autostart.ps1 handles crash recovery internally
90
- if (-not $registered) {
91
- $startupDir = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
92
- $shortcut = "$startupDir\clauth-autostart.lnk"
93
- $wsh = New-Object -ComObject WScript.Shell
94
- $lnk = $wsh.CreateShortcut($shortcut)
95
- $lnk.TargetPath = "powershell.exe"
96
- # 15s delay baked in so profile is loaded before daemon starts
97
- $lnk.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -Command `"Start-Sleep 15; & '$autostartScript'`""
98
- $lnk.WorkingDirectory = "$env:APPDATA\clauth"
99
- $lnk.WindowStyle = 7
100
- $lnk.Save()
101
- Write-Host " + Autostart registered via Startup folder (15s delay, crash-recovery loop active)." -ForegroundColor Green
102
- }
1
+ # clauth installer - Windows
2
+ # Private repo - run locally:
3
+ # cd C:\Dev\clauth && git pull && .\install.ps1
4
+
5
+ $ErrorActionPreference = "Stop"
6
+ $REPO = "https://github.com/LIFEAI/clauth.git"
7
+ $DIR = "$env:USERPROFILE\.clauth"
8
+
9
+ # Check git
10
+ try { git --version | Out-Null } catch {
11
+ Write-Host ""
12
+ Write-Host " x git is required." -ForegroundColor Red
13
+ Write-Host " Install from https://git-scm.com then re-run this script."
14
+ Write-Host ""
15
+ exit 1
16
+ }
17
+
18
+ # Check Node
19
+ try { node --version | Out-Null } catch {
20
+ Write-Host ""
21
+ Write-Host " x Node.js v18+ is required." -ForegroundColor Red
22
+ Write-Host " Install from https://nodejs.org then re-run this script."
23
+ Write-Host ""
24
+ exit 1
25
+ }
26
+
27
+ # Check cloudflared (soft warning - not required for install)
28
+ if (-not (Get-Command cloudflared -ErrorAction SilentlyContinue)) {
29
+ Write-Host " Note: cloudflared not found. Install after setup for claude.ai web integration." -ForegroundColor Yellow
30
+ Write-Host " winget install Cloudflare.cloudflared" -ForegroundColor Gray
31
+ Write-Host ""
32
+ }
33
+
34
+ # Skip bootstrap if CLAUTH npm package is already installed globally
35
+ $alreadyInstalled = $false
36
+ try {
37
+ $ver = & npm list -g @lifeaitools/clauth --depth=0 2>$null | Select-String "@lifeaitools/clauth"
38
+ if ($ver) { $alreadyInstalled = $true }
39
+ } catch { }
40
+
41
+ if ($alreadyInstalled) {
42
+ Write-Host " clauth already installed globally - skipping bootstrap." -ForegroundColor Green
43
+ } else {
44
+ # Clone or update
45
+ if (Test-Path "$DIR\.git") {
46
+ Write-Host " Updating clauth..."
47
+ Set-Location $DIR; git pull --quiet
48
+ } else {
49
+ Write-Host " Cloning clauth..."
50
+ git clone --quiet $REPO $DIR
51
+ }
52
+
53
+ # Run compiled bootstrap binary
54
+ $bootstrap = "$DIR\scripts\bin\bootstrap-win.exe"
55
+ if (-not (Test-Path $bootstrap)) {
56
+ Write-Host " x Bootstrap binary not found at $bootstrap" -ForegroundColor Red
57
+ exit 1
58
+ }
59
+
60
+ Set-Location $DIR
61
+ & $bootstrap
62
+ }
63
+
64
+ # Register autostart - idempotent, runs every install/update
65
+ # Primary: Task Scheduler (preferred - supports delay and OS-managed restarts)
66
+ # Fallback: Startup folder shortcut (fully functional - autostart.ps1 has its own crash-recovery loop)
67
+ $autostartScript = "$env:APPDATA\clauth\autostart.ps1"
68
+ $taskName = "CLAUTH Daemon"
69
+
70
+ if (Test-Path $autostartScript) {
71
+ Write-Host ""
72
+ Write-Host " Registering autostart..." -ForegroundColor Cyan
73
+
74
+ $registered = $false
75
+
76
+ # Try Task Scheduler first
77
+ try {
78
+ Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
79
+ $action = New-ScheduledTaskAction -Execute "powershell.exe" `
80
+ -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$autostartScript`""
81
+ $trigger = New-ScheduledTaskTrigger -AtLogOn
82
+ $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
83
+ Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
84
+ -Settings $settings -RunLevel Limited -Force -ErrorAction Stop | Out-Null
85
+ Write-Host " + Task Scheduler: '$taskName' registered (triggers at logon)." -ForegroundColor Green
86
+ $registered = $true
87
+ } catch { }
88
+
89
+ # Fallback: Startup folder - valid solution, autostart.ps1 handles crash recovery internally
90
+ if (-not $registered) {
91
+ $startupDir = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
92
+ $shortcut = "$startupDir\clauth-autostart.lnk"
93
+ $wsh = New-Object -ComObject WScript.Shell
94
+ $lnk = $wsh.CreateShortcut($shortcut)
95
+ $lnk.TargetPath = "powershell.exe"
96
+ # 15s delay baked in so profile is loaded before daemon starts
97
+ $lnk.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -Command `"Start-Sleep 15; & '$autostartScript'`""
98
+ $lnk.WorkingDirectory = "$env:APPDATA\clauth"
99
+ $lnk.WindowStyle = 7
100
+ $lnk.Save()
101
+ Write-Host " + Autostart registered via Startup folder (15s delay, crash-recovery loop active)." -ForegroundColor Green
102
+ }
103
103
  } else {
104
104
  Write-Host ""
105
105
  Write-Host " ! autostart.ps1 not found - skipping autostart registration." -ForegroundColor Yellow
package/install.sh CHANGED
@@ -1,49 +1,49 @@
1
- #!/usr/bin/env bash
2
- # clauth installer
3
- set -e
4
-
5
- REPO="https://github.com/LIFEAI/clauth.git"
6
- DIR="$HOME/.clauth"
7
-
8
- # Check git
9
- if ! command -v git &>/dev/null; then
10
- echo ""; echo " x git is required."
11
- echo " Install git from https://git-scm.com then re-run this script."; echo ""
12
- exit 1
13
- fi
14
-
15
- # Check Node
16
- if ! command -v node &>/dev/null; then
17
- echo ""; echo " x Node.js v18+ is required."
18
- echo " Install from https://nodejs.org then re-run this script."; echo ""
19
- exit 1
20
- fi
21
-
22
- # Check cloudflared (soft warning)
23
- if ! command -v cloudflared &>/dev/null; then
24
- echo " Note: cloudflared not found. Install after setup for claude.ai web integration."
25
- if [[ "$OSTYPE" == "darwin"* ]]; then
26
- echo " brew install cloudflare/cloudflare/cloudflared"
27
- else
28
- echo " https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"
29
- fi
30
- echo ""
31
- fi
32
-
33
- # Clone or update
34
- if [ -d "$DIR/.git" ]; then
35
- echo " Updating clauth..."; cd "$DIR" && git pull --quiet
36
- else
37
- echo " Cloning clauth..."; git clone --quiet "$REPO" "$DIR"
38
- fi
39
-
40
- # Run compiled bootstrap
41
- UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
42
- if [[ "$UNAME" == *"darwin"* ]]; then
43
- BOOTSTRAP="$DIR/scripts/bin/bootstrap-macos"
44
- else
45
- BOOTSTRAP="$DIR/scripts/bin/bootstrap-linux"
46
- fi
47
-
48
- chmod +x "$BOOTSTRAP"
49
- cd "$DIR" && "$BOOTSTRAP"
1
+ #!/usr/bin/env bash
2
+ # clauth installer
3
+ set -e
4
+
5
+ REPO="https://github.com/LIFEAI/clauth.git"
6
+ DIR="$HOME/.clauth"
7
+
8
+ # Check git
9
+ if ! command -v git &>/dev/null; then
10
+ echo ""; echo " x git is required."
11
+ echo " Install git from https://git-scm.com then re-run this script."; echo ""
12
+ exit 1
13
+ fi
14
+
15
+ # Check Node
16
+ if ! command -v node &>/dev/null; then
17
+ echo ""; echo " x Node.js v18+ is required."
18
+ echo " Install from https://nodejs.org then re-run this script."; echo ""
19
+ exit 1
20
+ fi
21
+
22
+ # Check cloudflared (soft warning)
23
+ if ! command -v cloudflared &>/dev/null; then
24
+ echo " Note: cloudflared not found. Install after setup for claude.ai web integration."
25
+ if [[ "$OSTYPE" == "darwin"* ]]; then
26
+ echo " brew install cloudflare/cloudflare/cloudflared"
27
+ else
28
+ echo " https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"
29
+ fi
30
+ echo ""
31
+ fi
32
+
33
+ # Clone or update
34
+ if [ -d "$DIR/.git" ]; then
35
+ echo " Updating clauth..."; cd "$DIR" && git pull --quiet
36
+ else
37
+ echo " Cloning clauth..."; git clone --quiet "$REPO" "$DIR"
38
+ fi
39
+
40
+ # Run compiled bootstrap
41
+ UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
42
+ if [[ "$UNAME" == *"darwin"* ]]; then
43
+ BOOTSTRAP="$DIR/scripts/bin/bootstrap-macos"
44
+ else
45
+ BOOTSTRAP="$DIR/scripts/bin/bootstrap-linux"
46
+ fi
47
+
48
+ chmod +x "$BOOTSTRAP"
49
+ cd "$DIR" && "$BOOTSTRAP"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "2.1.1",
3
+ "version": "2.10.0",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "build": "node scripts/build.mjs",
11
- "test": "node --test cli/commands/scrub.test.js cli/commands/login.test.js cli/api.classify.test.js cli/supervisor-registry.test.js cli/supervisor-ui.test.js test/studio-debug.test.mjs test/dashboard-key-generator.test.mjs && node test-auth-verdict.mjs",
11
+ "test": "node --test cli/commands/scrub.test.js cli/commands/login.test.js cli/api.classify.test.js cli/supervisor-registry.test.js cli/supervisor-ui.test.js cli/fingerprint.test.js cli/recovery.test.js cli/watchdog-registry.test.js cli/commands/ops.test.js cli/lib/fs-git.test.js cli/webdav-service.test.js cli/webdav-config.test.js cli/webdav-obscure.test.js cli/mcp/tool-schemas.test.js cli/services/service-status.test.js cli/services/fs-mount.test.js cli/services/fs-upload-sessions.test.js cli/services/git-exec.test.js cli/services/file-io.test.js cli/services/vault-read.test.js cli/services/git-import-guard.test.js cli/services/delegation-lane-instance.test.js cli/services/terminal-sessions.test.js cli/services/terminal-registry.test.js cli/services/terminal-dispatch.test.js test/studio-debug.test.mjs test/dashboard-key-generator.test.mjs test/agent-corpus.test.mjs test/agent-cron.test.mjs test/agent-delegation.test.mjs test/agent-pool.test.mjs test/coolify-adapter.test.mjs test/coolify-promote-uuid.test.mjs test/deployment-adapter.test.mjs test/enrollment-script.test.mjs test/ops-client.test.mjs test/ops-fixtures.test.mjs test/ops-install.test.mjs test/ops-job-store.test.mjs test/ops-pm2-e2e.test.mjs test/pm2-adapter.test.mjs test/serialized-executor.test.mjs test/call-agent-guard.test.mjs test/serve-http-routes.test.mjs && node test-auth-verdict.mjs",
12
12
  "test:studio-debug": "node --test test/studio-debug.test.mjs",
13
13
  "test:agent-pool": "node test/agent-pool.test.mjs",
14
14
  "test:call-agent-10": "node test/call-agent-10-skills.test.mjs",