@lifeaitools/clauth 1.16.9 → 1.18.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.
- package/.clauth-skill/SKILL.md +111 -111
- package/README.md +109 -109
- package/cli/api.classify.test.js +75 -0
- package/cli/api.js +100 -12
- package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
- package/cli/assets/watchdog.ps1 +42 -42
- package/cli/commands/agent-cron.js +396 -396
- package/cli/commands/agent-pool.js +1745 -1745
- package/cli/commands/codevelop.js +1190 -1190
- package/cli/commands/doctor.js +302 -302
- package/cli/commands/install.js +10 -10
- package/cli/commands/invite.js +175 -175
- package/cli/commands/join.js +179 -179
- package/cli/commands/npm.js +182 -182
- package/cli/commands/scrub.js +327 -327
- package/cli/commands/scrub.test.js +115 -115
- package/cli/commands/serve.js +11838 -11716
- package/cli/commands/watchdog.js +209 -209
- package/cli/conf-path.js +21 -21
- package/cli/index.js +1089 -1089
- package/cli/lib/fs-git.js +282 -282
- package/cli/recovery.js +101 -101
- package/cli/studio-debug.js +594 -487
- package/cli/watchdog-registry.js +209 -209
- package/cli/watchdog-registry.test.js +89 -89
- package/install.ps1 +21 -21
- package/package.json +2 -2
- package/scripts/postinstall.js +164 -164
- package/supabase/migrations/001_clauth_schema.sql +12 -12
- package/supabase/migrations/003_clauth_config.sql +13 -13
- package/supabase/migrations/003_machine_enrollments.sql +39 -39
package/cli/api.js
CHANGED
|
@@ -37,22 +37,109 @@ const VAULT_FETCH_TIMEOUT_MS = (() => {
|
|
|
37
37
|
return Number.isFinite(n) && n > 0 ? n : 8000;
|
|
38
38
|
})();
|
|
39
39
|
|
|
40
|
+
// ============================================================
|
|
41
|
+
// Backend / external-resource error model
|
|
42
|
+
// ============================================================
|
|
43
|
+
// A VaultBackendError means the vault backend (the auth-vault Edge Function or
|
|
44
|
+
// the Postgres database behind it) never rendered an auth verdict — the request
|
|
45
|
+
// timed out, the network failed, the function 5xx'd, the DB was unreachable, or
|
|
46
|
+
// we were rate-limited. These are TRANSIENT and must NEVER be counted as an
|
|
47
|
+
// authentication failure (a wrong password). Treating a database timeout as a
|
|
48
|
+
// bad password is the bug that turned a DB blip into a permanent vault lockout.
|
|
49
|
+
export class VaultBackendError extends Error {
|
|
50
|
+
constructor(kind, message, { status = null, detail = null, cause = null } = {}) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "VaultBackendError";
|
|
53
|
+
this.kind = kind; // timeout | network | server_error | rate_limited | db_error | unknown
|
|
54
|
+
this.isBackend = true; // marker for callers: do not strike
|
|
55
|
+
this.retriable = true; // the verdict was never rendered — safe to retry
|
|
56
|
+
this.status = status; // HTTP status if the server responded
|
|
57
|
+
this.detail = detail; // human-readable backend detail
|
|
58
|
+
if (cause) this.cause = cause;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Server `reason` strings that are genuine AUTH VERDICTS (the backend evaluated
|
|
63
|
+
// the credential and answered). These DO count — they are not backend errors.
|
|
64
|
+
const AUTH_VERDICT_REASONS = [
|
|
65
|
+
"invalid_token", "wrong_password", "invalid_password",
|
|
66
|
+
"machine_locked", "machine_disabled", "machine_not_found",
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Classify a server-supplied `reason` string. Returns a VaultBackendError kind
|
|
70
|
+
// when the reason describes a backend/transient condition, or null when the
|
|
71
|
+
// reason is a real auth verdict (or unknown — caller decides).
|
|
72
|
+
export function classifyServerReason(reason) {
|
|
73
|
+
if (!reason || typeof reason !== "string") return null;
|
|
74
|
+
const r = reason.toLowerCase();
|
|
75
|
+
if (AUTH_VERDICT_REASONS.some((v) => r.includes(v))) return null; // genuine verdict
|
|
76
|
+
if (/rate[_\s-]?limit/.test(r)) return "rate_limited";
|
|
77
|
+
if (/(database|\bdb\b|postgres|connection|pool|statement timeout|unavailable|internal|timeout|5\d\d)/.test(r)) return "db_error";
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Classify a thrown error (transport/timeout/network/typed). Returns a kind
|
|
82
|
+
// string when it is a backend/transient failure, or null when it is not.
|
|
83
|
+
export function classifyBackendError(err) {
|
|
84
|
+
if (!err) return null;
|
|
85
|
+
if (err instanceof VaultBackendError) return err.kind;
|
|
86
|
+
const name = err.name || "";
|
|
87
|
+
const msg = (err.message || "").toString();
|
|
88
|
+
if (name === "TimeoutError" || name === "AbortError" || /\babort(ed)?\b|\btim(e|ed)?\s*out\b|timeout/i.test(msg)) return "timeout";
|
|
89
|
+
if (/fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|ECONNRESET|EPIPE|network|socket hang/i.test(msg)) return "network";
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
40
93
|
async function post(route, body) {
|
|
41
94
|
const url = `${getBaseUrl()}/${route}`;
|
|
42
95
|
const anonKey = getAnonKey();
|
|
43
96
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
let res;
|
|
98
|
+
try {
|
|
99
|
+
res = await fetch(url, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
"Authorization": `Bearer ${anonKey}`
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
signal: AbortSignal.timeout(VAULT_FETCH_TIMEOUT_MS)
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// Transport-level failure — the backend never answered. Surface a typed,
|
|
110
|
+
// non-strike error so callers report "backend unreachable", not "bad password".
|
|
111
|
+
const kind = classifyBackendError(err) || "network";
|
|
112
|
+
const why = kind === "timeout"
|
|
113
|
+
? `vault backend did not respond within ${VAULT_FETCH_TIMEOUT_MS}ms`
|
|
114
|
+
: `cannot reach vault backend (${err.message || "network error"})`;
|
|
115
|
+
throw new VaultBackendError(kind, why, { detail: err.message || String(err), cause: err });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let data;
|
|
119
|
+
try {
|
|
120
|
+
data = await res.json();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
// 2xx/5xx with a non-JSON or empty body — the function errored without a
|
|
123
|
+
// structured verdict. Transient backend condition, not an auth failure.
|
|
124
|
+
throw new VaultBackendError("server_error", `vault backend returned an unreadable response (HTTP ${res.status})`, { status: res.status, detail: err.message });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// HTTP 5xx (or any non-ok with no structured error) = backend problem, never a verdict.
|
|
128
|
+
if (res.status >= 500 || (!res.ok && !data.error)) {
|
|
129
|
+
const reasonKind = classifyServerReason(data.reason || data.error) || "server_error";
|
|
130
|
+
throw new VaultBackendError(reasonKind, `vault backend error (HTTP ${res.status})`, { status: res.status, detail: data.error || data.reason || `HTTP ${res.status}` });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 4xx WITH a structured error: could be a real verdict (invalid_token) or a
|
|
134
|
+
// transient backend signal surfaced as 4xx (rate_limited). Promote the latter
|
|
135
|
+
// to a typed backend error; leave genuine verdicts in `data` for the caller.
|
|
136
|
+
if (data.error) {
|
|
137
|
+
const reasonKind = classifyServerReason(data.reason || data.error);
|
|
138
|
+
if (reasonKind) {
|
|
139
|
+
throw new VaultBackendError(reasonKind, `vault backend ${reasonKind.replace("_", " ")}`, { status: res.status, detail: data.error || data.reason });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
53
142
|
|
|
54
|
-
const data = await res.json();
|
|
55
|
-
if (!res.ok && !data.error) throw new Error(`HTTP ${res.status}`);
|
|
56
143
|
return data;
|
|
57
144
|
}
|
|
58
145
|
|
|
@@ -146,5 +233,6 @@ export async function redeemEnrollment(machineHash, seedHash, label, enrollmentC
|
|
|
146
233
|
|
|
147
234
|
export default {
|
|
148
235
|
retrieve, write, enable, addService, updateService, removeService, revoke,
|
|
149
|
-
status, test, createEnrollment, registerMachine, redeemEnrollment, getBaseUrl, getAnonKey
|
|
236
|
+
status, test, createEnrollment, registerMachine, redeemEnrollment, getBaseUrl, getAnonKey,
|
|
237
|
+
VaultBackendError, classifyBackendError, classifyServerReason
|
|
150
238
|
};
|
|
@@ -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
|
+
}
|
package/cli/assets/watchdog.ps1
CHANGED
|
@@ -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
|
+
}
|