@lifeaitools/clauth 1.30.23 → 1.30.25

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 (49) hide show
  1. package/.clauth-skill/SKILL.md +306 -275
  2. package/.clauth-skill/references/operator-guide.md +175 -148
  3. package/README.md +363 -315
  4. package/cli/api.classify.test.js +75 -75
  5. package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
  6. package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
  7. package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
  8. package/cli/assets/watchdog.ps1 +42 -42
  9. package/cli/commands/agent-cron.js +396 -396
  10. package/cli/commands/agent-pool.js +1962 -1962
  11. package/cli/commands/codevelop.js +1190 -1190
  12. package/cli/commands/doctor.js +302 -302
  13. package/cli/commands/install.js +10 -10
  14. package/cli/commands/invite.js +175 -175
  15. package/cli/commands/join.js +179 -179
  16. package/cli/commands/npm.js +182 -182
  17. package/cli/commands/ops-install.js +211 -0
  18. package/cli/commands/ops.js +69 -0
  19. package/cli/commands/scrub.js +327 -327
  20. package/cli/commands/scrub.test.js +115 -115
  21. package/cli/commands/serve.js +381 -98
  22. package/cli/commands/watchdog.js +209 -209
  23. package/cli/conf-path.js +21 -21
  24. package/cli/enrollment-script.js +82 -82
  25. package/cli/fingerprint.js +143 -143
  26. package/cli/index.js +1073 -1053
  27. package/cli/lib/fs-git.js +282 -282
  28. package/cli/ops/coolify-adapter.js +80 -0
  29. package/cli/ops/deployment-adapter.js +63 -0
  30. package/cli/ops/job-store.js +116 -0
  31. package/cli/ops/operation-policy.js +51 -0
  32. package/cli/ops/pm2-adapter.js +128 -0
  33. package/cli/ops/serialized-executor.js +9 -0
  34. package/cli/recovery.js +101 -101
  35. package/cli/studio-debug.js +1095 -1095
  36. package/cli/supervisor-registry.js +594 -589
  37. package/cli/supervisor-registry.test.js +397 -397
  38. package/cli/supervisor-ui.test.js +5 -83
  39. package/cli/watchdog-registry.js +237 -209
  40. package/cli/watchdog-registry.test.js +112 -89
  41. package/install.ps1 +21 -21
  42. package/package.json +4 -2
  43. package/scripts/bin/bootstrap-linux +0 -0
  44. package/scripts/bin/bootstrap-macos +0 -0
  45. package/scripts/bin/bootstrap-win.exe +0 -0
  46. package/supabase/migrations/001_clauth_schema.sql +12 -12
  47. package/supabase/migrations/003_clauth_config.sql +13 -13
  48. package/supabase/migrations/003_machine_enrollments.sql +39 -39
  49. package/cli/served-script-syntax.test.mjs +0 -54
@@ -1,75 +1,75 @@
1
- // cli/api.classify.test.js
2
- // Regression tests for backend/external-resource error classification.
3
- //
4
- // WHY THIS EXISTS: on 2026-06-19 a saturated Postgres database made every
5
- // auth-vault verify call time out. clauth counted each timeout as an
6
- // authentication failure ([AUTH FAIL n/10]), hit the strike ceiling, and
7
- // hard-locked the vault — turning a transient DB blip into a full lockout that
8
- // blocked Claude/Codex from starting. A database timeout, a 5xx, a network
9
- // drop, or a rate-limit is NOT a wrong password and must never take a strike.
10
- // These tests pin that classification so the regression can't return.
11
-
12
- import { test } from "node:test";
13
- import assert from "node:assert/strict";
14
- import {
15
- VaultBackendError,
16
- classifyBackendError,
17
- classifyServerReason,
18
- } from "./api.js";
19
-
20
- test("VaultBackendError marks failures as non-strike + retriable", () => {
21
- const e = new VaultBackendError("timeout", "no response", { status: 504, detail: "abort" });
22
- assert.equal(e.kind, "timeout");
23
- assert.equal(e.isBackend, true);
24
- assert.equal(e.retriable, true);
25
- assert.equal(e.status, 504);
26
- assert.ok(e instanceof Error);
27
- });
28
-
29
- test("classifyBackendError: AbortSignal.timeout shapes => 'timeout'", () => {
30
- // This is the exact error that slipped through the old substring check.
31
- assert.equal(classifyBackendError({ name: "TimeoutError", message: "The operation was aborted due to timeout" }), "timeout");
32
- assert.equal(classifyBackendError({ name: "AbortError", message: "This operation was aborted" }), "timeout");
33
- assert.equal(classifyBackendError(new Error("request timed out")), "timeout");
34
- });
35
-
36
- test("classifyBackendError: network/transport shapes => 'network'", () => {
37
- assert.equal(classifyBackendError(new Error("fetch failed")), "network");
38
- assert.equal(classifyBackendError(new Error("connect ECONNREFUSED 127.0.0.1:443")), "network");
39
- assert.equal(classifyBackendError(new Error("getaddrinfo ENOTFOUND db.supabase.co")), "network");
40
- assert.equal(classifyBackendError(new Error("read ECONNRESET")), "network");
41
- });
42
-
43
- test("classifyBackendError: a typed VaultBackendError passes its kind through", () => {
44
- assert.equal(classifyBackendError(new VaultBackendError("rate_limited", "slow down")), "rate_limited");
45
- assert.equal(classifyBackendError(new VaultBackendError("db_error", "pool exhausted")), "db_error");
46
- });
47
-
48
- test("classifyBackendError: a genuine credential error is NOT a backend error", () => {
49
- assert.equal(classifyBackendError(new Error("Wrong password")), null);
50
- assert.equal(classifyBackendError(new Error("invalid_token")), null);
51
- assert.equal(classifyBackendError(null), null);
52
- assert.equal(classifyBackendError(undefined), null);
53
- });
54
-
55
- test("classifyServerReason: rate-limit reasons => 'rate_limited'", () => {
56
- assert.equal(classifyServerReason("rate_limited"), "rate_limited");
57
- assert.equal(classifyServerReason("Rate limit: 38/30 per 60s"), "rate_limited");
58
- });
59
-
60
- test("classifyServerReason: database/backend reasons => 'db_error'", () => {
61
- assert.equal(classifyServerReason("canceling statement due to statement timeout"), "db_error");
62
- assert.equal(classifyServerReason("database connection pool exhausted"), "db_error");
63
- assert.equal(classifyServerReason("internal server error"), "db_error");
64
- assert.equal(classifyServerReason("service unavailable"), "db_error");
65
- });
66
-
67
- test("classifyServerReason: genuine auth verdicts are NOT backend errors (they strike)", () => {
68
- assert.equal(classifyServerReason("invalid_token (3/5)"), null);
69
- assert.equal(classifyServerReason("machine_locked"), null);
70
- assert.equal(classifyServerReason("machine_disabled"), null);
71
- assert.equal(classifyServerReason("machine_not_found"), null);
72
- assert.equal(classifyServerReason("wrong_password"), null);
73
- assert.equal(classifyServerReason(null), null);
74
- assert.equal(classifyServerReason(""), null);
75
- });
1
+ // cli/api.classify.test.js
2
+ // Regression tests for backend/external-resource error classification.
3
+ //
4
+ // WHY THIS EXISTS: on 2026-06-19 a saturated Postgres database made every
5
+ // auth-vault verify call time out. clauth counted each timeout as an
6
+ // authentication failure ([AUTH FAIL n/10]), hit the strike ceiling, and
7
+ // hard-locked the vault — turning a transient DB blip into a full lockout that
8
+ // blocked Claude/Codex from starting. A database timeout, a 5xx, a network
9
+ // drop, or a rate-limit is NOT a wrong password and must never take a strike.
10
+ // These tests pin that classification so the regression can't return.
11
+
12
+ import { test } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import {
15
+ VaultBackendError,
16
+ classifyBackendError,
17
+ classifyServerReason,
18
+ } from "./api.js";
19
+
20
+ test("VaultBackendError marks failures as non-strike + retriable", () => {
21
+ const e = new VaultBackendError("timeout", "no response", { status: 504, detail: "abort" });
22
+ assert.equal(e.kind, "timeout");
23
+ assert.equal(e.isBackend, true);
24
+ assert.equal(e.retriable, true);
25
+ assert.equal(e.status, 504);
26
+ assert.ok(e instanceof Error);
27
+ });
28
+
29
+ test("classifyBackendError: AbortSignal.timeout shapes => 'timeout'", () => {
30
+ // This is the exact error that slipped through the old substring check.
31
+ assert.equal(classifyBackendError({ name: "TimeoutError", message: "The operation was aborted due to timeout" }), "timeout");
32
+ assert.equal(classifyBackendError({ name: "AbortError", message: "This operation was aborted" }), "timeout");
33
+ assert.equal(classifyBackendError(new Error("request timed out")), "timeout");
34
+ });
35
+
36
+ test("classifyBackendError: network/transport shapes => 'network'", () => {
37
+ assert.equal(classifyBackendError(new Error("fetch failed")), "network");
38
+ assert.equal(classifyBackendError(new Error("connect ECONNREFUSED 127.0.0.1:443")), "network");
39
+ assert.equal(classifyBackendError(new Error("getaddrinfo ENOTFOUND db.supabase.co")), "network");
40
+ assert.equal(classifyBackendError(new Error("read ECONNRESET")), "network");
41
+ });
42
+
43
+ test("classifyBackendError: a typed VaultBackendError passes its kind through", () => {
44
+ assert.equal(classifyBackendError(new VaultBackendError("rate_limited", "slow down")), "rate_limited");
45
+ assert.equal(classifyBackendError(new VaultBackendError("db_error", "pool exhausted")), "db_error");
46
+ });
47
+
48
+ test("classifyBackendError: a genuine credential error is NOT a backend error", () => {
49
+ assert.equal(classifyBackendError(new Error("Wrong password")), null);
50
+ assert.equal(classifyBackendError(new Error("invalid_token")), null);
51
+ assert.equal(classifyBackendError(null), null);
52
+ assert.equal(classifyBackendError(undefined), null);
53
+ });
54
+
55
+ test("classifyServerReason: rate-limit reasons => 'rate_limited'", () => {
56
+ assert.equal(classifyServerReason("rate_limited"), "rate_limited");
57
+ assert.equal(classifyServerReason("Rate limit: 38/30 per 60s"), "rate_limited");
58
+ });
59
+
60
+ test("classifyServerReason: database/backend reasons => 'db_error'", () => {
61
+ assert.equal(classifyServerReason("canceling statement due to statement timeout"), "db_error");
62
+ assert.equal(classifyServerReason("database connection pool exhausted"), "db_error");
63
+ assert.equal(classifyServerReason("internal server error"), "db_error");
64
+ assert.equal(classifyServerReason("service unavailable"), "db_error");
65
+ });
66
+
67
+ test("classifyServerReason: genuine auth verdicts are NOT backend errors (they strike)", () => {
68
+ assert.equal(classifyServerReason("invalid_token (3/5)"), null);
69
+ assert.equal(classifyServerReason("machine_locked"), null);
70
+ assert.equal(classifyServerReason("machine_disabled"), null);
71
+ assert.equal(classifyServerReason("machine_not_found"), null);
72
+ assert.equal(classifyServerReason("wrong_password"), null);
73
+ assert.equal(classifyServerReason(null), null);
74
+ assert.equal(classifyServerReason(""), null);
75
+ });
@@ -1,20 +1,20 @@
1
- @echo off
2
- setlocal
3
- set "CODEVELOP_SESSION=${SESSION_ID}"
4
- set "CODEVELOP_PEER=${PEER}"
5
- set "CODEVELOP_TARGET=${TARGET_PEER}"
6
- set "CODEVELOP_BASE_URL=${BASE_URL}"
7
- set "CODEVELOP_MANIFEST=${MANIFEST}"
8
- set "CODEVELOP_CONTEXT=${CONTEXT_FILE}"
9
- set "CELL_ROLE=codevelop-${PEER}"
10
- cd /d "${CWD}"
11
- if /i "%~1"=="--print-only" (
12
- echo command: ${PRINT_COMMAND}
13
- exit /b 0
14
- )
15
- echo Co-develop ${PEER} ready. Session ${SESSION_ID}. Listening for collaboration.
16
- echo Context: ${CONTEXT_FILE}
17
- node "${CLAUTH_CLI}" codevelop check-partner --repo "${REPO}" --peer ${PEER}
18
- ${COMMAND}
19
- set EXITCODE=%ERRORLEVEL%
20
- endlocal & exit /b %EXITCODE%
1
+ @echo off
2
+ setlocal
3
+ set "CODEVELOP_SESSION=${SESSION_ID}"
4
+ set "CODEVELOP_PEER=${PEER}"
5
+ set "CODEVELOP_TARGET=${TARGET_PEER}"
6
+ set "CODEVELOP_BASE_URL=${BASE_URL}"
7
+ set "CODEVELOP_MANIFEST=${MANIFEST}"
8
+ set "CODEVELOP_CONTEXT=${CONTEXT_FILE}"
9
+ set "CELL_ROLE=codevelop-${PEER}"
10
+ cd /d "${CWD}"
11
+ if /i "%~1"=="--print-only" (
12
+ echo command: ${PRINT_COMMAND}
13
+ exit /b 0
14
+ )
15
+ echo Co-develop ${PEER} ready. Session ${SESSION_ID}. Listening for collaboration.
16
+ echo Context: ${CONTEXT_FILE}
17
+ node "${CLAUTH_CLI}" codevelop check-partner --repo "${REPO}" --peer ${PEER}
18
+ ${COMMAND}
19
+ set EXITCODE=%ERRORLEVEL%
20
+ endlocal & exit /b %EXITCODE%
@@ -1,7 +1,7 @@
1
- @echo off
2
- if not exist "${ACTIVE_LAUNCHER}" (
3
- echo No active co-develop launcher has been written.
4
- echo Run: clauth codevelop start --repo "${REPO}" --port ${PORT} --start-isolated-clauth
5
- exit /b 1
6
- )
7
- call "${ACTIVE_LAUNCHER}" %*
1
+ @echo off
2
+ if not exist "${ACTIVE_LAUNCHER}" (
3
+ echo No active co-develop launcher has been written.
4
+ echo Run: clauth codevelop start --repo "${REPO}" --port ${PORT} --start-isolated-clauth
5
+ exit /b 1
6
+ )
7
+ call "${ACTIVE_LAUNCHER}" %*
@@ -1,48 +1,48 @@
1
- {
2
- "profiles": [
3
- {
4
- "name": "Co Develop Claude",
5
- "guid": "{a2f8548f-82e6-44b4-bdbb-d934e8881f01}",
6
- "commandline": "cmd.exe /k \"${CLAUTH_APPDATA}\\codevelop-claude.cmd\"",
7
- "startingDirectory": "${REPO}",
8
- "environment": {
9
- "CELL_ROLE": "codevelop-claude"
10
- },
11
- "icon": "🔴",
12
- "tabColor": "#B91C1C",
13
- "colorScheme": "Dimidium"
14
- },
15
- {
16
- "name": "Co Develop Codex",
17
- "guid": "{31a52b07-6efd-4b3d-96dd-ded73488140a}",
18
- "commandline": "cmd.exe /k \"${CLAUTH_APPDATA}\\codevelop-codex.cmd\"",
19
- "startingDirectory": "${REPO}",
20
- "environment": {
21
- "CELL_ROLE": "codevelop-codex"
22
- },
23
- "icon": "🧬",
24
- "tabColor": "#0F766E",
25
- "colorScheme": "Dimidium"
26
- }
27
- ],
28
- "layout": {
29
- "wtArgs": [
30
- "-w",
31
- "new",
32
- "new-tab",
33
- "--profile",
34
- "Co Develop Claude",
35
- "--title",
36
- "Co Develop Claude",
37
- ";",
38
- "split-pane",
39
- "-V",
40
- "--size",
41
- "0.50",
42
- "--profile",
43
- "Co Develop Codex",
44
- "--title",
45
- "Co Develop Codex"
46
- ]
47
- }
48
- }
1
+ {
2
+ "profiles": [
3
+ {
4
+ "name": "Co Develop Claude",
5
+ "guid": "{a2f8548f-82e6-44b4-bdbb-d934e8881f01}",
6
+ "commandline": "cmd.exe /k \"${CLAUTH_APPDATA}\\codevelop-claude.cmd\"",
7
+ "startingDirectory": "${REPO}",
8
+ "environment": {
9
+ "CELL_ROLE": "codevelop-claude"
10
+ },
11
+ "icon": "🔴",
12
+ "tabColor": "#B91C1C",
13
+ "colorScheme": "Dimidium"
14
+ },
15
+ {
16
+ "name": "Co Develop Codex",
17
+ "guid": "{31a52b07-6efd-4b3d-96dd-ded73488140a}",
18
+ "commandline": "cmd.exe /k \"${CLAUTH_APPDATA}\\codevelop-codex.cmd\"",
19
+ "startingDirectory": "${REPO}",
20
+ "environment": {
21
+ "CELL_ROLE": "codevelop-codex"
22
+ },
23
+ "icon": "🧬",
24
+ "tabColor": "#0F766E",
25
+ "colorScheme": "Dimidium"
26
+ }
27
+ ],
28
+ "layout": {
29
+ "wtArgs": [
30
+ "-w",
31
+ "new",
32
+ "new-tab",
33
+ "--profile",
34
+ "Co Develop Claude",
35
+ "--title",
36
+ "Co Develop Claude",
37
+ ";",
38
+ "split-pane",
39
+ "-V",
40
+ "--size",
41
+ "0.50",
42
+ "--profile",
43
+ "Co Develop Codex",
44
+ "--title",
45
+ "Co Develop Codex"
46
+ ]
47
+ }
48
+ }
@@ -1,42 +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
- }
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
+ }