src/services/claude-login.js
Rama main · réplica de lectura del repositorio original.
import { spawn } from "node:child_process";
import { createLocalDockerEnvironment, validateLocalDockerHost } from "../infrastructure/local-process.js";
import { CANDIDATE_VERSION } from "../claude/launcher.js";
import {
resolveLocalN8nClaudeInstallRoot, acquireLocalN8nClaudeChangeLock,
attestLocalN8nClaudeInstallation, getLocalN8nClaudeAuthStatus,
} from "./local-n8n-claude-installer.js";
export async function runClaudeCompanionLogin({
isTTY = process.stdin.isTTY === true && process.stdout.isTTY === true,
environment = process.env, spawnProcess = spawn,
resolveInstallRoot = resolveLocalN8nClaudeInstallRoot,
acquireLock = acquireLocalN8nClaudeChangeLock,
attestInstallation = attestLocalN8nClaudeInstallation,
readAuthStatus = getLocalN8nClaudeAuthStatus,
} = {}) {
if (isTTY !== true) throw new Error("Claude companion sign-in requires an interactive terminal.");
const installDirectory = await resolveInstallRoot({ env: environment });
const release = await acquireLock({ installRoot: installDirectory });
let safeToRelease = true;
try {
const attested = await attestInstallation({ installDirectory }, { env: environment });
if (attested?.installDirectory !== installDirectory || attested.projectName !== "relmio-claude" ||
attested.serviceName !== "relmio-claude" || !/^[a-f0-9]{64}$/u.test(attested.containerId ?? "")) {
throw new Error("The managed local Claude companion could not be attested.");
}
const dockerHost = validateLocalDockerHost(attested.dockerHost);
let child;
try {
child = spawnProcess("docker", ["--host", dockerHost, "exec", "-it", "-w", "/data/account/empty", "relmio-claude",
"env", "-i", `TERM=${/^[A-Za-z0-9_.+-]{1,64}$/u.test(environment.TERM ?? "") ? environment.TERM : "xterm-256color"}`,
"HOME=/home/claude", "USER=claude", "LOGNAME=claude", "CLAUDE_CONFIG_DIR=/data/account/config",
"PATH=/usr/bin:/bin:/usr/sbin:/sbin", "TMPDIR=/tmp", "/usr/bin/timeout", "--signal=TERM", "--kill-after=2s", "900s",
`/opt/claude/${CANDIDATE_VERSION}/claude`, "auth", "login"], {
cwd: installDirectory, env: createLocalDockerEnvironment(environment), shell: false,
stdio: "inherit", windowsHide: true,
});
} catch { throw new Error("Claude companion sign-in could not start."); }
if (!child || typeof child.once !== "function") throw new Error("Claude companion sign-in could not start.");
safeToRelease = false;
const code = await new Promise(resolve => {
child.once("error", () => resolve(null));
child.once("close", resolve);
});
safeToRelease = Number.isInteger(code);
if (code !== 0) throw new Error("Claude companion sign-in was not completed.");
const status = await readAuthStatus({}, { env: environment });
if (typeof status?.loggedIn !== "boolean" || ![status.authMethod, status.subscriptionType].every(value =>
value === null || (typeof value === "string" && /^[A-Za-z0-9_.-]{1,64}$/u.test(value)))) {
throw new Error("Claude companion sign-in status is invalid.");
}
return { loggedIn: status.loggedIn, authMethod: status.authMethod, subscriptionType: status.subscriptionType };
} finally { if (safeToRelease) await release(); }
}