src/cli.js
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
import { randomBytes } from "node:crypto";
import { realpathSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import { fileURLToPath } from "node:url";
import { attachBrowserReopenOnEnter, openBrowser } from "./browser.js";
import { runClaudeCommand } from "./claude/command.js";
import { createPrivateBrowserHandoffRoot } from "./services/browser-handoff.js";
import {
inspectLocalDashboardControlPlane,
readLocalDashboardBrowserUrl,
runLocalDashboardDaemon,
startLocalDashboardControlPlane,
stopLocalDashboardControlPlane,
} from "./services/local-dashboard-control.js";
import { runGrokBuildLogin } from "./services/grok-login.js";
import { resolveLocalInstallRoot } from "./services/local-installer.js";
import { resolveLocalN8nSuperGrokInstallRoot } from "./services/local-n8n-supergrok-installer.js";
import { startWizardServer } from "./web/server.js";
const cliPath = fileURLToPath(import.meta.url);
const packageJsonUrl = new URL("../package.json", import.meta.url);
export function isCliEntryPath(entryPath, realpath = realpathSync) {
if (typeof entryPath !== "string" || entryPath === "") {
return false;
}
try {
return realpath(entryPath) === realpath(cliPath);
} catch {
return false;
}
}
export function cliMode(argumentsList, { commandName = "relmio" } = {}) {
if (argumentsList.length === 0) {
return "wizard";
}
if (argumentsList[0] === "claude") {
return "claude";
}
if (
(argumentsList.length === 2 || (argumentsList.length === 3 && argumentsList[2] === "--n8n")) &&
argumentsList[0] === "grok" &&
["login", "logout"].includes(argumentsList[1])
) {
return `grok-${argumentsList[1]}`;
}
if (argumentsList.length !== 1) {
throw new Error("Unknown Relmio command. Run relmio --help.");
}
switch (argumentsList[0]) {
case "--version":
case "-v":
return "version";
case "--help":
case "-h":
return "help";
case "local":
return "local";
case "vps":
return "wizard";
case "assistant":
return "assistant";
case "start":
return "start";
case "status":
return "status";
case "open":
case "gui":
return "open";
case "stop":
return "stop";
case "__relmio-dashboard-daemon":
return "daemon";
default:
throw new Error("Unknown Relmio command. Run relmio --help.");
}
}
function dashboardPath(mode) {
if (mode === "assistant") return "/assistant";
if (mode === "wizard") return "/";
return "/local";
}
async function logExperimentalNotice(readPackage, log) {
const { version } = JSON.parse(await readPackage());
if (typeof version !== "string" || !/^\d+\.\d+\.\d+-/u.test(version)) return;
log(`Experimental release ${version}: prerelease builds are not fully tested. Use a test setup.`);
log("Latest stable release: npx --yes --ignore-scripts relmio@latest");
}
async function runForegroundWizard({
mode,
env,
log,
startServer,
open,
attachReopen,
createBrowserLaunchRoot,
}) {
const sessionToken = randomBytes(32).toString("base64url");
const browserLaunchRoot = await createBrowserLaunchRoot({ env });
if (
!browserLaunchRoot || typeof browserLaunchRoot.path !== "string" ||
typeof browserLaunchRoot.dispose !== "function"
) {
throw new Error("Relmio could not create its private browser launch directory.");
}
let wizard;
try {
wizard = await startServer({
sessionToken,
browserHandoffRoot: browserLaunchRoot.path,
});
} catch (error) {
try { await browserLaunchRoot.dispose(); } catch { /* Preserve the startup failure. */ }
throw error;
}
const prepareLaunch = async () =>
await wizard.prepareBrowserLaunch(dashboardPath(mode));
log("");
log("Relmio");
log("---------");
const wizardLabel = mode === "local"
? "Local wizard"
: mode === "assistant"
? "Assistant wizard"
: "VPS wizard";
log(`${wizardLabel}: private browser handoff ready`);
log("");
if (mode === "assistant") {
log(
"This creates only the separate AI Assistant companion and never restarts n8n.",
);
} else if (mode === "local") {
log(
"The dashboard reads local service status without changing anything until you choose and confirm an action.",
);
} else {
log("This creates a separate sidecar and never restarts n8n.");
}
log("Keep this Terminal window open while using the wizard.");
log("Press Control+C to stop.");
log("");
let detachBrowserReopen = () => {};
let closing = false;
async function close() {
if (closing) {
return;
}
closing = true;
detachBrowserReopen();
try {
await wizard.close();
} finally {
await browserLaunchRoot.dispose();
}
}
let browserOpened = false;
try {
browserOpened = Boolean(await open(await prepareLaunch()));
} catch {
// The actionable default-browser error below is safe to show to users.
}
if (!browserOpened) {
await close();
throw new Error(
"Relmio could not open the private setup page. Check the Windows default browser, then run the installer again.",
);
}
try {
detachBrowserReopen = attachReopen({ prepareLaunch, open });
} catch (error) {
await close();
throw error;
}
process.once("SIGINT", async () => {
await close();
});
process.once("SIGTERM", async () => {
await close();
});
return 0;
}
export function hasInteractiveTerminal({
input = process.stdin,
output = process.stdout,
} = {}) {
return Boolean(input?.isTTY && output?.isTTY);
}
export async function runCli({
argumentsList = process.argv.slice(2),
commandName = basename(process.argv[1] ?? "relmio"),
env = process.env,
log = console.log,
readPackage = () => readFile(packageJsonUrl, "utf8"),
isInteractive = hasInteractiveTerminal,
startServer = startWizardServer,
open = openBrowser,
attachReopen = attachBrowserReopenOnEnter,
startControlPlane = startLocalDashboardControlPlane,
inspectControlPlane = inspectLocalDashboardControlPlane,
stopControlPlane = stopLocalDashboardControlPlane,
readBrowserUrl = readLocalDashboardBrowserUrl,
runDaemon = runLocalDashboardDaemon,
createBrowserLaunchRoot = createPrivateBrowserHandoffRoot,
runGrokLogin = runGrokBuildLogin,
resolveInstallRoot = resolveLocalInstallRoot,
resolveN8nSuperGrokInstallRoot = resolveLocalN8nSuperGrokInstallRoot,
runClaude = runClaudeCommand,
} = {}) {
const mode = cliMode(argumentsList, { commandName });
if (mode === "version") {
const packageJson = JSON.parse(await readPackage());
if (typeof packageJson.version !== "string" || packageJson.version === "") {
throw new Error("Relmio package metadata does not contain a version.");
}
log(packageJson.version);
return 0;
}
if (mode === "help") {
log("Usage: relmio [local|vps|assistant|start|status|open|stop|grok login|grok logout|claude|--version]");
log(" (no command) Open the ChatGPT-on-VPS setup wizard without persistent local state");
log(" local Open the persistent local services dashboard");
log(" vps Open the separate VPS setup wizard");
log(" assistant Open the dedicated AI Assistant companion wizard");
log(" start Start the local dashboard without opening a browser");
log(" status Report whether the exact local dashboard is running");
log(" open Start when needed and open the local dashboard");
log(" stop Stop only the Relmio dashboard process");
log(" grok login Start the official SuperGrok device sign-in");
log(" grok logout Sign out of the managed local SuperGrok endpoint");
log(" Add --n8n to grok login/logout for the private n8n SuperGrok companion");
log(" claude Experimental owner-only Claude Code adapter; run relmio claude --help");
await logExperimentalNotice(readPackage, log);
return 0;
}
if (mode === "claude") {
return await runClaude({ argumentsList: argumentsList.slice(1), env, log, isInteractive });
}
if (mode === "grok-login" || mode === "grok-logout") {
const interactive = isInteractive();
if (mode === "grok-login" && !interactive) {
log("Grok Build sign-in requires an interactive terminal.");
return 1;
}
const action = mode === "grok-login" ? "device-auth" : "logout";
const privateN8n = argumentsList[2] === "--n8n";
const target = privateN8n ? "n8n-supergrok-oauth" : "xai-grok-build";
const installDirectory = await (privateN8n ? resolveN8nSuperGrokInstallRoot : resolveInstallRoot)({
target,
env,
});
const result = await runGrokLogin({
...(privateN8n ? { target } : {}),
action,
installDirectory,
isTTY: interactive,
environment: env,
});
if (!result || result.success !== true || result.action !== action) {
throw new Error("Grok Build credential action was not completed.");
}
log(mode === "grok-login" ? "Grok Build sign-in completed." : "Grok Build sign-out completed.");
return 0;
}
if (mode === "daemon") {
const daemon = await runDaemon({ env, startServer });
if (!daemon?.completion || typeof daemon.completion.then !== "function") {
throw new Error("Relmio dashboard daemon did not provide a completion signal.");
}
await daemon.completion;
return 0;
}
if (mode === "start") {
const result = await startControlPlane({ env });
if (result?.state === "started") {
log("Relmio dashboard started.");
return 0;
}
if (result?.state === "existing") {
log("Relmio dashboard is already running.");
return 0;
}
throw new Error("Relmio dashboard returned an invalid start result.");
}
if (mode === "status") {
const result = await inspectControlPlane({ env });
if (result?.state === "healthy") {
log("Relmio dashboard is running.");
return 0;
}
if (result?.state === "absent") {
log("Relmio dashboard is not running.");
return 1;
}
if (result?.state === "version-mismatch") {
log("A dashboard from another Relmio version is running. Run relmio stop, then relmio start.");
return 1;
}
log("Relmio dashboard is not healthy. Run relmio start after inspecting its local state.");
return 1;
}
if (mode === "stop") {
const result = await stopControlPlane({ env });
if (result?.state === "stopped") {
log("Relmio dashboard stopped.");
return 0;
}
if (result?.state === "absent") {
log("Relmio dashboard is not running.");
return 0;
}
throw new Error("Relmio dashboard returned an invalid stop result.");
}
if (!isInteractive()) {
log(
"Relmio needs an interactive terminal to open the setup wizard. Run relmio from Command Prompt, PowerShell, or another terminal.",
);
const packageManagerProbe =
argumentsList.length === 0 && env?.RELMIO_FOREGROUND_WIZARD !== "1";
return packageManagerProbe ? 0 : 1;
}
await logExperimentalNotice(readPackage, log);
const foregroundWizard =
argumentsList.length === 0 || env?.RELMIO_FOREGROUND_WIZARD === "1";
if (
foregroundWizard &&
(mode === "local" || mode === "wizard" || mode === "assistant")
) {
return await runForegroundWizard({
mode,
env,
log,
startServer,
open,
attachReopen,
createBrowserLaunchRoot,
});
}
await startControlPlane({ env });
const privateLaunchUrl = await readBrowserUrl({
env,
route: dashboardPath(mode),
});
if (!(await open(privateLaunchUrl))) {
log("Relmio could not open the private dashboard. Fix the default browser and run relmio open again.");
return 1;
}
log("Relmio dashboard opened.");
return 0;
}
export async function runCliEntrypoint(options = {}) {
try {
const exitCode = await runCli(options);
if (exitCode !== 0) process.exitCode = exitCode;
return exitCode;
} catch (error) {
console.error(error.message);
process.exitCode = 1;
return 1;
}
}
if (isCliEntryPath(process.argv[1])) {
await runCliEntrypoint();
}