@lifeaitools/clauth 1.30.26 → 2.0.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/references/keys-guide.md +270 -270
- package/README.md +2 -0
- package/cli/api.js +238 -238
- package/cli/commands/agent-pool.js +51 -15
- package/cli/commands/install.js +396 -396
- package/cli/commands/login.js +135 -0
- package/cli/commands/login.test.js +73 -0
- package/cli/commands/serve.js +290 -1555
- package/cli/commands/uninstall.js +164 -164
- package/cli/index.js +102 -1
- package/cli/supervisor-registry.js +106 -5
- package/cli/supervisor-registry.test.js +151 -0
- package/cli/webdav-service.js +339 -339
- package/install.ps1 +102 -102
- package/install.sh +49 -49
- package/package.json +4 -5
- package/scripts/bootstrap.cjs +121 -121
- package/scripts/build.mjs +66 -0
- package/scripts/build.sh +5 -45
- package/scripts/postinstall.js +189 -189
- package/supabase/functions/auth-vault/index.ts +350 -350
- package/supabase/migrations/001_clauth_schema.sql +94 -94
- package/supabase/migrations/002_vault_helpers.sql +90 -90
- package/supabase/migrations/20260317_lockout.sql +26 -26
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// cli/commands/login.js
|
|
2
|
+
// clauth login <target> — the GLOBAL LOGIN DIRECTIVE.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS
|
|
5
|
+
// Before this command, "log into Vultr" meant an agent or human fetching a
|
|
6
|
+
// raw private key from the vault (curl .../v/vultr-dev-ssh) and hand-rolling
|
|
7
|
+
// an ssh-agent + ssh invocation per caller. That is exactly the failure mode
|
|
8
|
+
// this closes: one canonical, zero-friction verb for every clauth-managed box
|
|
9
|
+
// login, so nobody re-derives (or gets wrong) the key-handling dance. It does
|
|
10
|
+
// NOT reimplement that dance — services/ssh-clauth.sh and .ps1 already carry
|
|
11
|
+
// hard-won fixes (CRLF stripping, Windows-OpenSSH-vs-Git-ssh binary
|
|
12
|
+
// resolution, ephemeral key cleanup) documented in lifeai-env's lessons.
|
|
13
|
+
// This command only resolves a friendly target name and delegates to them.
|
|
14
|
+
//
|
|
15
|
+
// Add a new box by adding an entry to LOGIN_TARGETS — never by hand-fetching
|
|
16
|
+
// its key elsewhere.
|
|
17
|
+
|
|
18
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
export const LOGIN_TARGETS = {
|
|
22
|
+
vultr: {
|
|
23
|
+
service: "vultr-dev-ssh",
|
|
24
|
+
host: "root@64.237.54.189",
|
|
25
|
+
label: "Vultr dev box (PM2 apps, port 64.237.54.189)",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function resolveEnvRoot(env = process.env) {
|
|
30
|
+
return env.LIFEAI_ENV || "C:/Dev/lifeai-env";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function bashCandidates(platform) {
|
|
34
|
+
return platform === "win32"
|
|
35
|
+
? ["C:\\Program Files\\Git\\bin\\bash.exe", "bash"]
|
|
36
|
+
: ["bash"];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function findBash(platform = process.platform, probe = spawnSync) {
|
|
40
|
+
for (const candidate of bashCandidates(platform)) {
|
|
41
|
+
try {
|
|
42
|
+
const result = probe(candidate, ["--version"], { stdio: "ignore" });
|
|
43
|
+
if (result && result.status === 0) return candidate;
|
|
44
|
+
} catch {
|
|
45
|
+
// try next candidate
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return "bash";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function listTargets() {
|
|
52
|
+
return Object.entries(LOGIN_TARGETS).map(([name, target]) => ({ name, ...target }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pure resolver: target name + options -> {bin, args, env}. Kept separate
|
|
57
|
+
* from process spawning so the routing logic is testable without touching a
|
|
58
|
+
* real shell, ssh-agent, or the network.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveInvocation(targetName, opts = {}, ctx = {}) {
|
|
61
|
+
const target = LOGIN_TARGETS[targetName];
|
|
62
|
+
if (!target) {
|
|
63
|
+
const known = Object.keys(LOGIN_TARGETS).join(", ");
|
|
64
|
+
throw new Error(`Unknown login target "${targetName}". Known targets: ${known}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const platform = ctx.platform || process.platform;
|
|
68
|
+
const envRoot = resolveEnvRoot(ctx.env || process.env);
|
|
69
|
+
const host = opts.host || target.host;
|
|
70
|
+
|
|
71
|
+
const useNativeWindows = Boolean(opts.nativeWindows) && platform === "win32";
|
|
72
|
+
// The bash engine always gets a forward-slash path — it is fed to bash
|
|
73
|
+
// (Git Bash on Windows, or real bash elsewhere), never a native Windows API,
|
|
74
|
+
// so path.join's backslash normalization on a Windows host would be wrong.
|
|
75
|
+
const engine = useNativeWindows
|
|
76
|
+
? path.join(envRoot, "services", "ssh-clauth.ps1")
|
|
77
|
+
: `${envRoot.replace(/[\\/]+$/, "")}/services/ssh-clauth.sh`;
|
|
78
|
+
|
|
79
|
+
const bin = useNativeWindows ? "pwsh" : (ctx.findBash || findBash)(platform);
|
|
80
|
+
const args = useNativeWindows
|
|
81
|
+
? [
|
|
82
|
+
"-NoProfile", "-File", engine,
|
|
83
|
+
"-Service", target.service,
|
|
84
|
+
"-Target", host,
|
|
85
|
+
...(opts.command ? ["-Command", opts.command] : []),
|
|
86
|
+
]
|
|
87
|
+
: [engine, opts.command || "", host];
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
bin,
|
|
91
|
+
args,
|
|
92
|
+
env: { ...(ctx.env || process.env), SSH_CLAUTH_SERVICE: target.service },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function printTargetList(log = console.log) {
|
|
97
|
+
log("\nKnown login targets (clauth login <target>):\n");
|
|
98
|
+
for (const t of listTargets()) {
|
|
99
|
+
log(` ${t.name.padEnd(12)} ${t.host.padEnd(22)} ${t.label}`);
|
|
100
|
+
}
|
|
101
|
+
log("\nUsage:");
|
|
102
|
+
log(" clauth login vultr interactive shell");
|
|
103
|
+
log(" clauth login vultr -c \"pm2 list\" run one command");
|
|
104
|
+
log(" clauth login vultr --host user@1.2.3.4 override the target host\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function runLogin(targetName, opts = {}) {
|
|
108
|
+
if (!targetName) {
|
|
109
|
+
printTargetList();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let invocation;
|
|
114
|
+
try {
|
|
115
|
+
invocation = resolveInvocation(targetName, opts);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(err.message);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const child = spawn(invocation.bin, invocation.args, {
|
|
123
|
+
stdio: "inherit",
|
|
124
|
+
env: invocation.env,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const code = await new Promise((resolve) => {
|
|
128
|
+
child.on("exit", (c) => resolve(c ?? 1));
|
|
129
|
+
child.on("error", (err) => {
|
|
130
|
+
console.error(`clauth login: failed to launch ${invocation.bin}: ${err.message}`);
|
|
131
|
+
resolve(1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
process.exitCode = code;
|
|
135
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// node --test cli/commands/login.test.js
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
|
|
5
|
+
import { LOGIN_TARGETS, listTargets, resolveEnvRoot, resolveInvocation } from "./login.js";
|
|
6
|
+
|
|
7
|
+
test("listTargets exposes vultr with its clauth service + default host", () => {
|
|
8
|
+
const targets = listTargets();
|
|
9
|
+
const vultr = targets.find((t) => t.name === "vultr");
|
|
10
|
+
assert.ok(vultr, "vultr target must be registered");
|
|
11
|
+
assert.equal(vultr.service, "vultr-dev-ssh");
|
|
12
|
+
assert.equal(vultr.host, "root@64.237.54.189");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("resolveEnvRoot honors LIFEAI_ENV and falls back to the documented default", () => {
|
|
16
|
+
assert.equal(resolveEnvRoot({ LIFEAI_ENV: "D:/custom-env" }), "D:/custom-env");
|
|
17
|
+
assert.equal(resolveEnvRoot({}), "C:/Dev/lifeai-env");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("resolveInvocation rejects an unknown target instead of guessing", () => {
|
|
21
|
+
assert.throws(() => resolveInvocation("not-a-real-box"), /Unknown login target/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("resolveInvocation on posix delegates to the bash ssh-clauth engine, never fetches a key itself", () => {
|
|
25
|
+
const inv = resolveInvocation("vultr", {}, {
|
|
26
|
+
platform: "linux",
|
|
27
|
+
env: { LIFEAI_ENV: "/opt/lifeai-env" },
|
|
28
|
+
findBash: () => "bash",
|
|
29
|
+
});
|
|
30
|
+
assert.equal(inv.bin, "bash");
|
|
31
|
+
assert.equal(inv.args[0], "/opt/lifeai-env/services/ssh-clauth.sh");
|
|
32
|
+
assert.equal(inv.args[2], "root@64.237.54.189");
|
|
33
|
+
assert.equal(inv.env.SSH_CLAUTH_SERVICE, "vultr-dev-ssh");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("resolveInvocation passes a one-shot command through as $1 to ssh-clauth.sh", () => {
|
|
37
|
+
const inv = resolveInvocation("vultr", { command: "pm2 list --no-color" }, {
|
|
38
|
+
platform: "linux",
|
|
39
|
+
env: {},
|
|
40
|
+
findBash: () => "bash",
|
|
41
|
+
});
|
|
42
|
+
assert.equal(inv.args[1], "pm2 list --no-color");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("resolveInvocation honors --host override without touching the target registry", () => {
|
|
46
|
+
const inv = resolveInvocation("vultr", { host: "root@10.0.0.9" }, {
|
|
47
|
+
platform: "linux",
|
|
48
|
+
env: {},
|
|
49
|
+
findBash: () => "bash",
|
|
50
|
+
});
|
|
51
|
+
assert.equal(inv.args[2], "root@10.0.0.9");
|
|
52
|
+
assert.equal(LOGIN_TARGETS.vultr.host, "root@64.237.54.189", "registry default must stay untouched");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("resolveInvocation defaults to the Bash engine on Windows too (the ps1 engine self-blocks agent sessions)", () => {
|
|
56
|
+
const inv = resolveInvocation("vultr", {}, {
|
|
57
|
+
platform: "win32",
|
|
58
|
+
env: {},
|
|
59
|
+
findBash: () => "C:\\Program Files\\Git\\bin\\bash.exe",
|
|
60
|
+
});
|
|
61
|
+
assert.equal(inv.bin, "C:\\Program Files\\Git\\bin\\bash.exe");
|
|
62
|
+
assert.match(inv.args[0], /ssh-clauth\.sh$/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("resolveInvocation only switches to the native Windows engine when explicitly requested", () => {
|
|
66
|
+
const inv = resolveInvocation("vultr", { nativeWindows: true }, {
|
|
67
|
+
platform: "win32",
|
|
68
|
+
env: { LIFEAI_ENV: "C:/Dev/lifeai-env" },
|
|
69
|
+
});
|
|
70
|
+
assert.equal(inv.bin, "pwsh");
|
|
71
|
+
assert.match(inv.args[2], /ssh-clauth\.ps1$/);
|
|
72
|
+
assert.deepEqual(inv.args.slice(3, 5), ["-Service", "vultr-dev-ssh"]);
|
|
73
|
+
});
|