mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
Fix setup TUI hatch terminal handoff (#69524)
* fix: relaunch setup tui in a fresh process
* fix: harden setup tui handoff
* fix: preserve tui hatch exit flow
* Revert "fix: preserve tui hatch exit flow"
This reverts commit f4f119a5a3.
* fix: let setup tui resolve gateway auth
* fix: support packaged tui relaunch
* fix: pin setup tui gateway target
* fix: preserve setup tui auth source
This commit is contained in:
@@ -145,6 +145,7 @@ export async function resolveGatewayInteractiveSurfaceAuth(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
explicitAuth?: ExplicitGatewayAuth;
|
||||
suppressEnvAuthFallback?: boolean;
|
||||
surface: "local" | "remote";
|
||||
}): Promise<{
|
||||
token?: string;
|
||||
@@ -155,8 +156,12 @@ export async function resolveGatewayInteractiveSurfaceAuth(params: {
|
||||
const diagnostics: string[] = [];
|
||||
const explicitToken = trimToUndefined(params.explicitAuth?.token);
|
||||
const explicitPassword = trimToUndefined(params.explicitAuth?.password);
|
||||
const envToken = trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
|
||||
const envPassword = trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
|
||||
const envToken = params.suppressEnvAuthFallback
|
||||
? undefined
|
||||
: trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
|
||||
const envPassword = params.suppressEnvAuthFallback
|
||||
? undefined
|
||||
: trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
|
||||
|
||||
if (params.surface === "remote") {
|
||||
const remoteToken = explicitToken
|
||||
|
||||
@@ -110,6 +110,7 @@ describe("resolveGatewayConnection", () => {
|
||||
"OPENCLAW_GATEWAY_URL",
|
||||
"OPENCLAW_GATEWAY_TOKEN",
|
||||
"OPENCLAW_GATEWAY_PASSWORD",
|
||||
"OPENCLAW_TUI_SETUP_AUTH_SOURCE",
|
||||
]);
|
||||
loadConfig.mockReset();
|
||||
resolveGatewayPort.mockReset();
|
||||
@@ -126,6 +127,7 @@ describe("resolveGatewayConnection", () => {
|
||||
delete process.env.OPENCLAW_GATEWAY_URL;
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||
delete process.env.OPENCLAW_TUI_SETUP_AUTH_SOURCE;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -199,6 +201,74 @@ describe("resolveGatewayConnection", () => {
|
||||
expect(result.token).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps normal TUI local password mode env precedence by default", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
gateway: {
|
||||
mode: "local",
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: "config-password", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await withEnvAsync({ OPENCLAW_GATEWAY_PASSWORD: "env-password" }, async () => {
|
||||
const result = await resolveGatewayConnection({});
|
||||
expect(result.password).toBe("env-password");
|
||||
});
|
||||
});
|
||||
|
||||
it("uses configured local password for setup-launched TUI despite stale gateway password env", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
gateway: {
|
||||
mode: "local",
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: "config-password", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_GATEWAY_PASSWORD: "stale-env-password", // pragma: allowlist secret
|
||||
OPENCLAW_TUI_SETUP_AUTH_SOURCE: "config",
|
||||
},
|
||||
async () => {
|
||||
const result = await resolveGatewayConnection({});
|
||||
expect(result.password).toBe("config-password");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("still resolves env SecretRefs for setup-launched TUI config auth", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
secrets: {
|
||||
providers: {
|
||||
default: { source: "env" },
|
||||
},
|
||||
},
|
||||
gateway: {
|
||||
mode: "local",
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: { source: "env", provider: "default", id: "OPENCLAW_GATEWAY_PASSWORD" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_GATEWAY_PASSWORD: "resolved-ref-password", // pragma: allowlist secret
|
||||
OPENCLAW_TUI_SETUP_AUTH_SOURCE: "config",
|
||||
},
|
||||
async () => {
|
||||
const result = await resolveGatewayConnection({});
|
||||
expect(result.password).toBe("resolved-ref-password");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("fails when both local token and password are configured but gateway.auth.mode is unset", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
gateway: {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "../gateway/protocol/index.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { TUI_SETUP_AUTH_SOURCE_CONFIG, TUI_SETUP_AUTH_SOURCE_ENV } from "./setup-launch-env.js";
|
||||
import type { ResponseUsageMode, SessionInfo, SessionScope } from "./tui-types.js";
|
||||
|
||||
export type GatewayConnectionOptions = {
|
||||
@@ -317,6 +318,7 @@ export async function resolveGatewayConnection(
|
||||
const env = process.env;
|
||||
const gatewayAuthMode = config.gateway?.auth?.mode;
|
||||
const isRemoteMode = config.gateway?.mode === "remote";
|
||||
const preferConfiguredAuth = env[TUI_SETUP_AUTH_SOURCE_ENV] === TUI_SETUP_AUTH_SOURCE_CONFIG;
|
||||
|
||||
const urlOverride =
|
||||
typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
|
||||
@@ -394,6 +396,7 @@ export async function resolveGatewayConnection(
|
||||
config,
|
||||
env,
|
||||
explicitAuth,
|
||||
suppressEnvAuthFallback: preferConfiguredAuth,
|
||||
surface: "local",
|
||||
});
|
||||
if (resolved.failureReason) {
|
||||
|
||||
2
src/tui/setup-launch-env.ts
Normal file
2
src/tui/setup-launch-env.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const TUI_SETUP_AUTH_SOURCE_ENV = "OPENCLAW_TUI_SETUP_AUTH_SOURCE";
|
||||
export const TUI_SETUP_AUTH_SOURCE_CONFIG = "config";
|
||||
129
src/tui/tui-launch.test.ts
Normal file
129
src/tui/tui-launch.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { ChildProcess, SpawnOptions } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const spawnMock = vi.hoisted(() => vi.fn());
|
||||
const detachMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: spawnMock,
|
||||
}));
|
||||
|
||||
vi.mock("../process/child-process-bridge.js", () => ({
|
||||
attachChildProcessBridge: vi.fn(() => ({ detach: detachMock })),
|
||||
}));
|
||||
|
||||
import { launchTuiCli } from "./tui-launch.js";
|
||||
|
||||
const originalArgv = [...process.argv];
|
||||
const originalExecArgv = [...process.execArgv];
|
||||
|
||||
function createChildProcess(): ChildProcess {
|
||||
return new EventEmitter() as ChildProcess;
|
||||
}
|
||||
|
||||
describe("launchTuiCli", () => {
|
||||
beforeEach(() => {
|
||||
process.argv = [...originalArgv];
|
||||
process.argv[1] = "/repo/openclaw.mjs";
|
||||
process.execArgv.length = 0;
|
||||
spawnMock.mockReset();
|
||||
detachMock.mockReset();
|
||||
vi.spyOn(process.stdin, "pause").mockImplementation(() => process.stdin);
|
||||
vi.spyOn(process.stdin, "resume").mockImplementation(() => process.stdin);
|
||||
vi.spyOn(process.stdin, "isPaused").mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = [...originalArgv];
|
||||
process.execArgv.length = 0;
|
||||
process.execArgv.push(...originalExecArgv);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("filters inherited inspector flags when relaunching TUI", async () => {
|
||||
process.execArgv.push(
|
||||
"--import",
|
||||
"tsx",
|
||||
"--inspect",
|
||||
"127.0.0.1:9231",
|
||||
"--inspect=127.0.0.1:9229",
|
||||
"--inspect-brk",
|
||||
"--inspect-wait=0",
|
||||
"--inspect-port",
|
||||
"9230",
|
||||
"--no-warnings",
|
||||
);
|
||||
const child = createChildProcess();
|
||||
spawnMock.mockImplementation((_cmd: string, _args: string[], _opts: SpawnOptions) => {
|
||||
queueMicrotask(() => child.emit("exit", 0, null));
|
||||
return child;
|
||||
});
|
||||
|
||||
await launchTuiCli({
|
||||
url: "ws://127.0.0.1:18789",
|
||||
token: "test-token",
|
||||
password: "test-password",
|
||||
deliver: false,
|
||||
});
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledWith(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
"--no-warnings",
|
||||
"/repo/openclaw.mjs",
|
||||
"tui",
|
||||
"--url",
|
||||
"ws://127.0.0.1:18789",
|
||||
"--token",
|
||||
"test-token",
|
||||
"--password",
|
||||
"test-password",
|
||||
],
|
||||
expect.objectContaining({ stdio: "inherit" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("launches compiled CLI shapes without repeating the current command", async () => {
|
||||
process.argv[1] = "setup";
|
||||
const child = createChildProcess();
|
||||
spawnMock.mockImplementation((_cmd: string, _args: string[], _opts: SpawnOptions) => {
|
||||
queueMicrotask(() => child.emit("exit", 0, null));
|
||||
return child;
|
||||
});
|
||||
|
||||
await launchTuiCli({ deliver: false });
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledWith(
|
||||
process.execPath,
|
||||
["tui"],
|
||||
expect.objectContaining({ stdio: "inherit" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("pins the child gateway URL and config auth source through env without adding url argv", async () => {
|
||||
const child = createChildProcess();
|
||||
spawnMock.mockImplementation((_cmd: string, _args: string[], _opts: SpawnOptions) => {
|
||||
queueMicrotask(() => child.emit("exit", 0, null));
|
||||
return child;
|
||||
});
|
||||
|
||||
await launchTuiCli(
|
||||
{ deliver: false },
|
||||
{ authSource: "config", gatewayUrl: "ws://127.0.0.1:18789" },
|
||||
);
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledWith(
|
||||
process.execPath,
|
||||
["/repo/openclaw.mjs", "tui"],
|
||||
expect.objectContaining({
|
||||
env: expect.objectContaining({
|
||||
OPENCLAW_GATEWAY_URL: "ws://127.0.0.1:18789",
|
||||
OPENCLAW_TUI_SETUP_AUTH_SOURCE: "config",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
126
src/tui/tui-launch.ts
Normal file
126
src/tui/tui-launch.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { attachChildProcessBridge } from "../process/child-process-bridge.js";
|
||||
import { TUI_SETUP_AUTH_SOURCE_CONFIG, TUI_SETUP_AUTH_SOURCE_ENV } from "./setup-launch-env.js";
|
||||
import type { TuiOptions } from "./tui.js";
|
||||
|
||||
type TuiLaunchOptions = {
|
||||
authSource?: "config";
|
||||
gatewayUrl?: string;
|
||||
};
|
||||
|
||||
function appendOption(args: string[], flag: string, value: string | number | undefined): void {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
args.push(flag, String(value));
|
||||
}
|
||||
|
||||
function filterTuiExecArgv(execArgv: readonly string[]): string[] {
|
||||
const filtered: string[] = [];
|
||||
for (let index = 0; index < execArgv.length; index += 1) {
|
||||
const arg = execArgv[index] ?? "";
|
||||
if (
|
||||
arg === "--inspect" ||
|
||||
arg.startsWith("--inspect=") ||
|
||||
arg === "--inspect-brk" ||
|
||||
arg.startsWith("--inspect-brk=") ||
|
||||
arg === "--inspect-wait" ||
|
||||
arg.startsWith("--inspect-wait=")
|
||||
) {
|
||||
const next = execArgv[index + 1];
|
||||
if (!arg.includes("=") && typeof next === "string" && !next.startsWith("-")) {
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (arg === "--inspect-port") {
|
||||
const next = execArgv[index + 1];
|
||||
if (typeof next === "string" && !next.startsWith("-")) {
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("--inspect-port=")) {
|
||||
continue;
|
||||
}
|
||||
filtered.push(arg);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function buildCurrentCliEntryArgs(): string[] {
|
||||
const entry = process.argv[1]?.trim();
|
||||
if (!entry) {
|
||||
throw new Error("unable to relaunch TUI: current CLI entry path is unavailable");
|
||||
}
|
||||
return path.isAbsolute(entry) ? [entry] : [];
|
||||
}
|
||||
|
||||
function buildTuiCliArgs(opts: TuiOptions): string[] {
|
||||
const args = [...filterTuiExecArgv(process.execArgv), ...buildCurrentCliEntryArgs(), "tui"];
|
||||
appendOption(args, "--url", opts.url);
|
||||
appendOption(args, "--token", opts.token);
|
||||
appendOption(args, "--password", opts.password);
|
||||
appendOption(args, "--session", opts.session);
|
||||
appendOption(args, "--thinking", opts.thinking);
|
||||
appendOption(args, "--message", opts.message);
|
||||
appendOption(args, "--timeout-ms", opts.timeoutMs);
|
||||
appendOption(args, "--history-limit", opts.historyLimit);
|
||||
if (opts.deliver) {
|
||||
args.push("--deliver");
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
export async function launchTuiCli(
|
||||
opts: TuiOptions,
|
||||
launchOptions: TuiLaunchOptions = {},
|
||||
): Promise<void> {
|
||||
const args = buildTuiCliArgs(opts);
|
||||
const env =
|
||||
launchOptions.gatewayUrl || launchOptions.authSource
|
||||
? {
|
||||
...process.env,
|
||||
...(launchOptions.gatewayUrl ? { OPENCLAW_GATEWAY_URL: launchOptions.gatewayUrl } : {}),
|
||||
...(launchOptions.authSource === "config"
|
||||
? { [TUI_SETUP_AUTH_SOURCE_ENV]: TUI_SETUP_AUTH_SOURCE_CONFIG }
|
||||
: {}),
|
||||
}
|
||||
: process.env;
|
||||
const stdinWasPaused =
|
||||
typeof process.stdin.isPaused === "function" ? process.stdin.isPaused() : false;
|
||||
|
||||
process.stdin.pause();
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const child = spawn(process.execPath, args, {
|
||||
stdio: "inherit",
|
||||
env,
|
||||
});
|
||||
const { detach } = attachChildProcessBridge(child);
|
||||
|
||||
child.once("error", (error) => {
|
||||
detach();
|
||||
reject(new Error(`failed to launch TUI: ${formatErrorMessage(error)}`));
|
||||
});
|
||||
|
||||
child.once("exit", (code, signal) => {
|
||||
detach();
|
||||
if (signal) {
|
||||
reject(new Error(`TUI exited from signal ${signal}`));
|
||||
return;
|
||||
}
|
||||
if ((code ?? 0) !== 0) {
|
||||
reject(new Error(`TUI exited with code ${code ?? 1}`));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}).finally(() => {
|
||||
if (!stdinWasPaused) {
|
||||
process.stdin.resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
const runTui = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const launchTuiCli = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const restoreTerminalState = vi.hoisted(() => vi.fn());
|
||||
const probeGatewayReachable = vi.hoisted(() =>
|
||||
vi.fn<() => Promise<{ ok: boolean; detail?: string }>>(async () => ({ ok: true })),
|
||||
);
|
||||
@@ -134,11 +135,11 @@ vi.mock("../infra/control-ui-assets.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../terminal/restore.js", () => ({
|
||||
restoreTerminalState: vi.fn(),
|
||||
restoreTerminalState,
|
||||
}));
|
||||
|
||||
vi.mock("../tui/tui.js", () => ({
|
||||
runTui,
|
||||
vi.mock("../tui/tui-launch.js", () => ({
|
||||
launchTuiCli,
|
||||
}));
|
||||
|
||||
vi.mock("./setup.secret-input.js", () => ({
|
||||
@@ -236,7 +237,8 @@ function createAdvancedFinalizeArgs(params: AdvancedFinalizeArgs = {}) {
|
||||
|
||||
describe("finalizeSetupWizard", () => {
|
||||
beforeEach(() => {
|
||||
runTui.mockClear();
|
||||
launchTuiCli.mockClear();
|
||||
restoreTerminalState.mockClear();
|
||||
probeGatewayReachable.mockClear();
|
||||
waitForGatewayReachable.mockReset();
|
||||
waitForGatewayReachable.mockResolvedValue({ ok: true });
|
||||
@@ -265,7 +267,7 @@ describe("finalizeSetupWizard", () => {
|
||||
listConfiguredWebSearchProviders.mockReturnValue([]);
|
||||
});
|
||||
|
||||
it("resolves gateway password SecretRef for probe and TUI", async () => {
|
||||
it("resolves gateway password SecretRef for probe but omits auth from TUI hatch", async () => {
|
||||
const previous = process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||
process.env.OPENCLAW_GATEWAY_PASSWORD = "resolved-gateway-password"; // pragma: allowlist secret
|
||||
resolveSetupSecretInputString.mockResolvedValueOnce("resolved-gateway-password");
|
||||
@@ -337,14 +339,62 @@ describe("finalizeSetupWizard", () => {
|
||||
password: "resolved-gateway-password", // pragma: allowlist secret
|
||||
}),
|
||||
);
|
||||
expect(runTui).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: "ws://127.0.0.1:18789",
|
||||
password: "resolved-gateway-password", // pragma: allowlist secret
|
||||
}),
|
||||
expect(launchTuiCli).toHaveBeenCalledWith(
|
||||
{
|
||||
deliver: false,
|
||||
message: undefined,
|
||||
},
|
||||
{
|
||||
authSource: "config",
|
||||
gatewayUrl: "ws://127.0.0.1:18789",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("restores terminal state after failed TUI hatch", async () => {
|
||||
launchTuiCli.mockRejectedValueOnce(new Error("TUI exited with code 1"));
|
||||
const select = vi.fn(async (params: { message: string }) => {
|
||||
if (params.message === "How do you want to hatch your bot?") {
|
||||
return "tui";
|
||||
}
|
||||
return "later";
|
||||
});
|
||||
const prompter = buildWizardPrompter({ select: select as never });
|
||||
|
||||
await expect(
|
||||
finalizeSetupWizard({
|
||||
flow: "advanced",
|
||||
opts: {
|
||||
acceptRisk: true,
|
||||
authChoice: "skip",
|
||||
installDaemon: false,
|
||||
skipHealth: true,
|
||||
skipUi: false,
|
||||
},
|
||||
baseConfig: {},
|
||||
nextConfig: {},
|
||||
workspaceDir: "/tmp",
|
||||
settings: {
|
||||
port: 18789,
|
||||
bind: "loopback",
|
||||
authMode: "token",
|
||||
gatewayToken: "test-token",
|
||||
tailscaleMode: "off",
|
||||
tailscaleResetOnExit: false,
|
||||
},
|
||||
prompter,
|
||||
runtime: createRuntime(),
|
||||
}),
|
||||
).rejects.toThrow("TUI exited with code 1");
|
||||
|
||||
expect(restoreTerminalState).toHaveBeenCalledWith("pre-setup tui", {
|
||||
resumeStdinIfPaused: true,
|
||||
});
|
||||
expect(restoreTerminalState).toHaveBeenCalledWith("post-setup tui", {
|
||||
resumeStdinIfPaused: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not persist resolved SecretRef token in daemon install plan", async () => {
|
||||
const prompter = buildWizardPrompter({
|
||||
select: vi.fn(async () => "later") as never,
|
||||
|
||||
@@ -30,7 +30,7 @@ import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { restoreTerminalState } from "../terminal/restore.js";
|
||||
import { runTui } from "../tui/tui.js";
|
||||
import { launchTuiCli } from "../tui/tui-launch.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { listConfiguredWebSearchProviders } from "../web-search/runtime.js";
|
||||
import type { WizardPrompter } from "./prompts.js";
|
||||
@@ -423,14 +423,21 @@ export async function finalizeSetupWizard(
|
||||
|
||||
if (hatchChoice === "tui") {
|
||||
restoreTerminalState("pre-setup tui", { resumeStdinIfPaused: true });
|
||||
await runTui({
|
||||
url: links.wsUrl,
|
||||
token: settings.authMode === "token" ? settings.gatewayToken : undefined,
|
||||
password: settings.authMode === "password" ? resolvedGatewayPassword : "",
|
||||
// Safety: setup TUI should not auto-deliver to lastProvider/lastTo.
|
||||
deliver: false,
|
||||
message: hasBootstrap ? "Wake up, my friend!" : undefined,
|
||||
});
|
||||
try {
|
||||
await launchTuiCli(
|
||||
{
|
||||
// Safety: setup TUI should not auto-deliver to lastProvider/lastTo.
|
||||
deliver: false,
|
||||
message: hasBootstrap ? "Wake up, my friend!" : undefined,
|
||||
},
|
||||
{
|
||||
authSource: "config",
|
||||
gatewayUrl: links.wsUrl,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
restoreTerminalState("post-setup tui", { resumeStdinIfPaused: true });
|
||||
}
|
||||
launchedTui = true;
|
||||
} else if (hatchChoice === "web") {
|
||||
const browserSupport = await detectBrowserOpenSupport();
|
||||
|
||||
Reference in New Issue
Block a user