#!/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(); }