@lifeaitools/clauth 1.30.13 ā 1.30.14
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 +75 -17
- package/README.md +70 -10
- package/cli/api.classify.test.js +75 -0
- package/cli/api.js +110 -11
- package/cli/commands/agent-cron.js +396 -0
- package/cli/commands/agent-pool.js +1962 -0
- package/cli/commands/scrub.js +205 -109
- package/cli/commands/scrub.test.js +115 -0
- package/cli/commands/serve.js +3488 -1068
- package/cli/enrollment-script.js +82 -0
- package/cli/index.js +23 -57
- package/cli/studio-debug.js +679 -8
- package/cli/webdav-service.js +339 -0
- package/package.json +11 -3
- package/scripts/postinstall.js +25 -0
|
@@ -0,0 +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
|
+
}
|
package/cli/index.js
CHANGED
|
@@ -13,53 +13,11 @@ import { writeCredentialWithRecovery } from "./recovery.js";
|
|
|
13
13
|
import os from "os";
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import path from "path";
|
|
16
|
+
import { writeEnrollmentScript } from "./enrollment-script.js";
|
|
16
17
|
|
|
17
18
|
const config = new Conf(getConfOptions());
|
|
18
19
|
const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
19
20
|
|
|
20
|
-
function shellSingleQuote(value) {
|
|
21
|
-
return `'${String(value ?? "").replace(/'/g, "''")}'`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function enrollmentScriptName(label) {
|
|
25
|
-
const slug = String(label || "new-computer")
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
28
|
-
.replace(/^-+|-+$/g, "")
|
|
29
|
-
.slice(0, 40) || "new-computer";
|
|
30
|
-
return `clauth-enroll-${slug}.ps1`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function writeEnrollmentScript({ supabaseUrl, anonKey, enrollmentCode, label }) {
|
|
34
|
-
const appDir = process.platform === "win32"
|
|
35
|
-
? path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "clauth")
|
|
36
|
-
: path.join(os.homedir(), ".config", "clauth");
|
|
37
|
-
fs.mkdirSync(appDir, { recursive: true });
|
|
38
|
-
const scriptPath = path.join(appDir, enrollmentScriptName(label));
|
|
39
|
-
const script = [
|
|
40
|
-
"$ErrorActionPreference = 'Stop'",
|
|
41
|
-
"$label = $env:COMPUTERNAME",
|
|
42
|
-
"if (-not $label) { $label = [System.Net.Dns]::GetHostName() }",
|
|
43
|
-
"Write-Host 'Installing clauth...'",
|
|
44
|
-
"npm install -g @lifeaitools/clauth@latest",
|
|
45
|
-
"Write-Host 'Enrolling this computer with clauth...'",
|
|
46
|
-
[
|
|
47
|
-
"clauth setup",
|
|
48
|
-
`--supabase-url ${shellSingleQuote(supabaseUrl)}`,
|
|
49
|
-
`--anon-key ${shellSingleQuote(anonKey)}`,
|
|
50
|
-
`--enrollment-code ${shellSingleQuote(enrollmentCode)}`,
|
|
51
|
-
"--label \"$label\"",
|
|
52
|
-
].join(" "),
|
|
53
|
-
"Write-Host 'Installing clauth startup service...'",
|
|
54
|
-
"clauth serve install",
|
|
55
|
-
"Write-Host 'clauth enrollment complete.'",
|
|
56
|
-
"$self = $PSCommandPath",
|
|
57
|
-
"Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @('-NoProfile','-Command',\"Start-Sleep -Seconds 2; Remove-Item -LiteralPath '$self' -Force -ErrorAction SilentlyContinue\")",
|
|
58
|
-
].join("\r\n");
|
|
59
|
-
fs.writeFileSync(scriptPath, `${script}\r\n`, "utf8");
|
|
60
|
-
return scriptPath;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
21
|
// ============================================================
|
|
64
22
|
// Password prompt helper
|
|
65
23
|
// ============================================================
|
|
@@ -161,7 +119,7 @@ async function searchServices(auth, query, opts = {}) {
|
|
|
161
119
|
.map(([field]) => field);
|
|
162
120
|
|
|
163
121
|
let addressHints = [];
|
|
164
|
-
if (opts.addresses
|
|
122
|
+
if (opts.addresses === true && ADDRESS_KEY_TYPES.has(String(s.key_type || "").toLowerCase()) && s.vault_key) {
|
|
165
123
|
const secret = await api.retrieve(auth.password, auth.machineHash, auth.token, auth.timestamp, s.name);
|
|
166
124
|
if (!secret.error) {
|
|
167
125
|
addressHints = collectAddressHints(secret.value, s.key_type);
|
|
@@ -391,6 +349,7 @@ program
|
|
|
391
349
|
.option("--label <label>", "Suggested label for the new computer")
|
|
392
350
|
.option("--ttl-minutes <minutes>", "Enrollment lifetime, 5 to 1440 minutes", "60")
|
|
393
351
|
.option("--install-id <id>", "Override install id; default is current machine's install id")
|
|
352
|
+
.option("--target <target>", "Enrollment target: windows or linux", "windows")
|
|
394
353
|
.option("-p, --pw <password>", "Password (or will prompt)")
|
|
395
354
|
.action(async (opts) => {
|
|
396
355
|
console.log(chalk.cyan("\nš clauth enroll\n"));
|
|
@@ -414,6 +373,7 @@ program
|
|
|
414
373
|
anonKey,
|
|
415
374
|
enrollmentCode: result.enrollment_code,
|
|
416
375
|
label: opts.label,
|
|
376
|
+
target: opts.target,
|
|
417
377
|
});
|
|
418
378
|
spinner.succeed(chalk.green(`Enrollment created for install_id=${result.install_id}`));
|
|
419
379
|
console.log("");
|
|
@@ -675,15 +635,15 @@ program
|
|
|
675
635
|
|
|
676
636
|
program
|
|
677
637
|
.command("search <query>")
|
|
678
|
-
.description("Search services by name, label, project, description,
|
|
638
|
+
.description("Search services by name, label, project, description, or type")
|
|
679
639
|
.option("-p, --pw <password>")
|
|
680
640
|
.option("--project <name>", "Filter by project scope")
|
|
681
|
-
.option("--
|
|
641
|
+
.option("--addresses", "Also search redacted address hints from address-bearing secrets (may retrieve multiple secrets)")
|
|
682
642
|
.action(async (query, opts) => {
|
|
683
643
|
const auth = await getAuth(opts.pw);
|
|
684
644
|
const spinner = ora("Searching services...").start();
|
|
685
645
|
try {
|
|
686
|
-
const rows = await searchServices(auth, query, { project: opts.project, addresses: opts.addresses });
|
|
646
|
+
const rows = await searchServices(auth, query, { project: opts.project, addresses: opts.addresses === true });
|
|
687
647
|
spinner.stop();
|
|
688
648
|
console.log(chalk.cyan(`\n Search results for "${query}":\n`));
|
|
689
649
|
if (!rows.length) {
|
|
@@ -786,9 +746,13 @@ program
|
|
|
786
746
|
.addHelpText("after", `
|
|
787
747
|
Examples:
|
|
788
748
|
clauth scrub Scrub the most recent (active) transcript
|
|
789
|
-
clauth scrub <file> Scrub a specific
|
|
790
|
-
clauth scrub all Scrub every transcript
|
|
749
|
+
clauth scrub <file> Scrub a specific file
|
|
750
|
+
clauth scrub all Scrub every transcript + tool-result sidecar (.jsonl + .txt)
|
|
791
751
|
clauth scrub all --force Rescrub all files (ignore markers)
|
|
752
|
+
clauth scrub session Scrub ONLY the ending session (transcript + sidecars); reads SessionEnd hook JSON on stdin
|
|
753
|
+
|
|
754
|
+
Redacts: built-in token patterns, your ~/.clauth/scrub-patterns.json,
|
|
755
|
+
and this machine's live vault values (best-effort via the daemon).
|
|
792
756
|
`)
|
|
793
757
|
.action(async (target, opts) => {
|
|
794
758
|
await runScrub(target, opts);
|
|
@@ -1036,7 +1000,7 @@ program.addHelpText("beforeAll", chalk.cyan(`
|
|
|
1036
1000
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1037
1001
|
program
|
|
1038
1002
|
.command("serve [action]")
|
|
1039
|
-
.description("Manage localhost HTTP vault daemon and supervisor (start|stop|restart|ping|supervisor|install|uninstall)")
|
|
1003
|
+
.description("Manage localhost HTTP vault daemon and supervisor (start|stop|restart|ping|supervisor|install|uninstall)")
|
|
1040
1004
|
.option("--port <n>", "Port (default: 52437)")
|
|
1041
1005
|
.option("-p, --pw <password>", "clauth password (optional ā omit to start locked, unlock in browser)")
|
|
1042
1006
|
.option("--services <list>", "Comma-separated service whitelist (default: all)")
|
|
@@ -1044,6 +1008,8 @@ program
|
|
|
1044
1008
|
.option("--staged", "Start on staging port (52438) for blue-green verification before make-live")
|
|
1045
1009
|
.option("--isolated", "Run on a non-live port without touching live PID files, browser, or boot-key credentials")
|
|
1046
1010
|
.option("--from-boot-key", "Internal: password came from boot.key auto-unlock (degrade gracefully on verify failure)")
|
|
1011
|
+
.option("--pw-env", "Internal: read the daemon password from CLAUTH_BOOT_PASSWORD")
|
|
1012
|
+
.option("--pw-stdin", "Internal: read the installer password from standard input")
|
|
1047
1013
|
.option("--action <action>", "Internal: action override for daemon child")
|
|
1048
1014
|
.addHelpText("after", `
|
|
1049
1015
|
Actions:
|
|
@@ -1052,9 +1018,9 @@ Actions:
|
|
|
1052
1018
|
restart Stop + start
|
|
1053
1019
|
ping Check if the daemon is running
|
|
1054
1020
|
foreground Run in foreground (Ctrl+C to stop) ā default if no action given
|
|
1055
|
-
mcp Run as MCP stdio server for Claude Code (JSON-RPC over stdin/stdout)
|
|
1056
|
-
supervisor Run localhost-only supervisor control plane on port 52439
|
|
1057
|
-
install Store password securely + register auto-start service (cross-platform)
|
|
1021
|
+
mcp Run as MCP stdio server for Claude Code (JSON-RPC over stdin/stdout)
|
|
1022
|
+
supervisor Run localhost-only supervisor control plane on port 52439
|
|
1023
|
+
install Store password securely + register auto-start service (cross-platform)
|
|
1058
1024
|
Windows: DPAPI + HKCU\\Run | macOS: Keychain + LaunchAgent | Linux: libsecret/openssl + systemd
|
|
1059
1025
|
uninstall Remove auto-start service + delete stored password
|
|
1060
1026
|
upgrade Blue-green upgrade: start new version on staging port, verify, then make live
|
|
@@ -1070,10 +1036,10 @@ Examples:
|
|
|
1070
1036
|
clauth serve ping Check status
|
|
1071
1037
|
clauth serve restart Restart (stays locked until browser unlock)
|
|
1072
1038
|
clauth serve start --services github,vercel
|
|
1073
|
-
clauth serve mcp Start MCP server for Claude Code
|
|
1074
|
-
clauth serve mcp -p mypass Start MCP server pre-unlocked
|
|
1075
|
-
clauth serve supervisor Start supervisor API on http://127.0.0.1:52439
|
|
1076
|
-
clauth serve foreground --port 53137 --isolated
|
|
1039
|
+
clauth serve mcp Start MCP server for Claude Code
|
|
1040
|
+
clauth serve mcp -p mypass Start MCP server pre-unlocked
|
|
1041
|
+
clauth serve supervisor Start supervisor API on http://127.0.0.1:52439
|
|
1042
|
+
clauth serve foreground --port 53137 --isolated
|
|
1077
1043
|
Start isolated passwordless server for route tests
|
|
1078
1044
|
clauth serve install Set up auto-start on login (DPAPI/Keychain/libsecret)
|
|
1079
1045
|
clauth serve install --tunnel host Auto-start with Cloudflare Tunnel
|