mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(cli): align configure and channel wizard behavior (#111720)
This commit is contained in:
@@ -100,7 +100,14 @@ Non-interactive add flags shared across channels: `--account <id>`, `--name <nam
|
||||
|
||||
If a channel plugin needs to be installed during a flag-driven add command, OpenClaw uses the channel's default install source without opening the interactive plugin install prompt.
|
||||
|
||||
When you run `openclaw channels add` without flags, the interactive wizard can prompt:
|
||||
When you run `openclaw channels add` with no direct account, credential, or channel-config flags, the interactive wizard can prompt. A positional channel id and `--channel <id>` both preselect that channel without bypassing guidance:
|
||||
|
||||
```bash
|
||||
openclaw channels add telegram
|
||||
openclaw channels add --channel telegram
|
||||
```
|
||||
|
||||
The wizard can prompt for:
|
||||
|
||||
- account ids per selected channel
|
||||
- optional display names for those accounts
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ Setup commands by intent:
|
||||
- `openclaw setup` and `openclaw onboard` verify inference first, then start OpenClaw for Gateway, workspace, channels, skills, and health setup.
|
||||
- `openclaw setup --baseline` creates the baseline config and workspace without walking the guided onboarding flow.
|
||||
- `openclaw configure` changes targeted parts of an existing setup: model auth, gateway, channels, plugins, or skills.
|
||||
- `openclaw channels add` configures channel accounts after the baseline exists; run without flags for guided setup, or with channel-specific flags for scripts.
|
||||
- `openclaw channels add` configures channel accounts after the baseline exists; a channel selection alone uses guided setup, while account, credential, or channel-config flags use the direct path for scripts.
|
||||
|
||||
## Command pages
|
||||
|
||||
|
||||
@@ -8,11 +8,25 @@ import { registerChannelsCli } from "./channels-cli.js";
|
||||
const listBundledPackageChannelMetadataMock = vi.hoisted(() =>
|
||||
vi.fn<() => readonly PluginPackageChannel[]>(() => []),
|
||||
);
|
||||
const channelsAddCommandMock = vi.hoisted(() => vi.fn(async () => undefined));
|
||||
const runtimeMock = vi.hoisted(() => ({
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/bundled-package-channel-metadata.js", () => ({
|
||||
listBundledPackageChannelMetadata: listBundledPackageChannelMetadataMock,
|
||||
}));
|
||||
|
||||
vi.mock("../commands/channels.js", () => ({
|
||||
channelsAddCommand: channelsAddCommandMock,
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", () => ({
|
||||
defaultRuntime: runtimeMock,
|
||||
}));
|
||||
|
||||
function getChannelAddOptionFlags(program: Command): string[] {
|
||||
const channels = program.commands.find((command) => command.name() === "channels");
|
||||
const add = channels?.commands.find((command) => command.name() === "add");
|
||||
@@ -25,6 +39,12 @@ function getChannelSubcommandNames(program: Command, parentName: string): string
|
||||
return parent?.commands.map((command) => command.name()) ?? [];
|
||||
}
|
||||
|
||||
async function runChannelsAddCli(args: string[]) {
|
||||
const program = new Command().name("openclaw");
|
||||
await registerChannelsCli(program, ["node", "openclaw", ...args]);
|
||||
await program.parseAsync(args, { from: "user" });
|
||||
}
|
||||
|
||||
describe("registerChannelsCli", () => {
|
||||
const originalArgv = [...process.argv];
|
||||
|
||||
@@ -135,4 +155,56 @@ describe("registerChannelsCli", () => {
|
||||
expect(listBundledPackageChannelMetadataMock).toHaveBeenCalledTimes(1);
|
||||
expect(getChannelAddOptionFlags(program)).toContain("--homeserver <url>");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["positional", ["channels", "add", "telegram"]],
|
||||
["--channel", ["channels", "add", "--channel", "telegram"]],
|
||||
])("keeps selection-only %s channel adds on the guided path", async (_label, args) => {
|
||||
await runChannelsAddCli(args);
|
||||
|
||||
expect(channelsAddCommandMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ channel: "telegram" }),
|
||||
runtimeMock,
|
||||
{ hasFlags: false },
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["token", ["--token", "test-token"]],
|
||||
["token file", ["--token-file", "/tmp/test-token"]],
|
||||
["environment", ["--use-env"]],
|
||||
["account", ["--account", "work"]],
|
||||
])("keeps explicit %s inputs on the direct path", async (_label, extraArgs) => {
|
||||
await runChannelsAddCli(["channels", "add", "--channel", "telegram", ...extraArgs]);
|
||||
|
||||
expect(channelsAddCommandMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ channel: "telegram" }),
|
||||
runtimeMock,
|
||||
{ hasFlags: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("treats plugin-provided config flags as direct automation inputs", async () => {
|
||||
listBundledPackageChannelMetadataMock.mockReturnValueOnce([
|
||||
{
|
||||
id: "matrix",
|
||||
cliAddOptions: [{ flags: "--homeserver <url>", description: "Matrix homeserver URL" }],
|
||||
},
|
||||
]);
|
||||
|
||||
await runChannelsAddCli([
|
||||
"channels",
|
||||
"add",
|
||||
"--channel",
|
||||
"matrix",
|
||||
"--homeserver",
|
||||
"https://matrix.example.org",
|
||||
]);
|
||||
|
||||
expect(channelsAddCommandMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ channel: "matrix", homeserver: "https://matrix.example.org" }),
|
||||
runtimeMock,
|
||||
{ hasFlags: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ type BundledPackageChannelMetadataModule =
|
||||
typeof import("../plugins/bundled-package-channel-metadata.js");
|
||||
|
||||
const optionNamesRemove = ["channel", "account", "delete"] as const;
|
||||
const CHANNEL_ADD_SELECTION_OPTION_NAMES = new Set(["channel"]);
|
||||
|
||||
type RegisterChannelsCliOptions = {
|
||||
includeSetupOptions?: boolean;
|
||||
@@ -282,7 +283,10 @@ export async function registerChannelsCli(
|
||||
addCommand.action(async (channelArg: string | undefined, opts, command) => {
|
||||
await runChannelsCommand(async () => {
|
||||
const { channelsAddCommand } = await loadChannelsCommands();
|
||||
const hasFlags = hasExplicitOptions(command, getOptionNames(command));
|
||||
const hasFlags = hasExplicitOptions(
|
||||
command,
|
||||
getOptionNames(command).filter((name) => !CHANNEL_ADD_SELECTION_OPTION_NAMES.has(name)),
|
||||
);
|
||||
await channelsAddCommand(resolveChannelsAddOptions(channelArg, opts), defaultRuntime, {
|
||||
hasFlags,
|
||||
});
|
||||
|
||||
@@ -100,6 +100,7 @@ export async function removeChannelConfigWizard(
|
||||
options,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
if (choice.kind === "done") {
|
||||
@@ -114,6 +115,7 @@ export async function removeChannelConfigWizard(
|
||||
initialValue: false,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
if (!confirmed) {
|
||||
continue;
|
||||
|
||||
@@ -83,7 +83,21 @@ describe("configureCommandFromSectionsArg", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("fails closed for a mixed valid/invalid section list on a non-interactive terminal", async () => {
|
||||
it("rejects a lone invalid section before unrestricted wizard dispatch", async () => {
|
||||
const runtime = makeRuntime();
|
||||
|
||||
await expect(
|
||||
configureCommandFromSectionsArg(["bogus"], runtime, { interactive: true }),
|
||||
).rejects.toThrow("exit 1");
|
||||
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(runtime.error.mock.calls[0]?.[0]).toBe(
|
||||
"Invalid --section: bogus. Expected one of: workspace, model, web, gateway, daemon, channels, plugins, skills, health. Run openclaw configure without --section to use the full wizard.",
|
||||
);
|
||||
expect(runConfigureWizardMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("validates invalid sections before the interactive-terminal guard", async () => {
|
||||
const runtime = makeRuntime();
|
||||
|
||||
await expect(
|
||||
@@ -92,9 +106,8 @@ describe("configureCommandFromSectionsArg", () => {
|
||||
}),
|
||||
).rejects.toThrow("exit 1");
|
||||
|
||||
// Non-TTY guard fires before any section validation, so the wizard never runs.
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(runtime.error.mock.calls[0]?.[0]).toContain("interactive terminal (TTY)");
|
||||
expect(runtime.error.mock.calls[0]?.[0]).toContain("Invalid --section: bogus");
|
||||
expect(runConfigureWizardMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,6 +60,15 @@ export async function configureCommandFromSectionsArg(
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
options?: { interactive?: boolean },
|
||||
): Promise<void> {
|
||||
const { sections, invalid } = parseConfigureWizardSections(rawSections);
|
||||
if (invalid.length > 0) {
|
||||
runtime.error(
|
||||
`Invalid --section: ${invalid.join(", ")}. Expected one of: ${CONFIGURE_WIZARD_SECTIONS.join(", ")}. Run ${formatCliCommand("openclaw configure")} without --section to use the full wizard.`,
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail closed once at the shared entry: both `openclaw configure` and the
|
||||
// no-subcommand `openclaw config` route here, so a single guard keeps them
|
||||
// consistent instead of partially entering the wizard on a non-TTY pipe.
|
||||
@@ -69,19 +78,10 @@ export async function configureCommandFromSectionsArg(
|
||||
return;
|
||||
}
|
||||
|
||||
const { sections, invalid } = parseConfigureWizardSections(rawSections);
|
||||
if (sections.length === 0) {
|
||||
await configureCommand(runtime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (invalid.length > 0) {
|
||||
runtime.error(
|
||||
`Invalid --section: ${invalid.join(", ")}. Expected one of: ${CONFIGURE_WIZARD_SECTIONS.join(", ")}. Run ${formatCliCommand("openclaw configure")} without --section to use the full wizard.`,
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
await configureCommandWithSections(sections as never, runtime);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export async function maybeInstallDaemon(params: {
|
||||
],
|
||||
}),
|
||||
params.runtime,
|
||||
1,
|
||||
);
|
||||
if (action === "restart") {
|
||||
await withProgress(
|
||||
@@ -93,6 +94,7 @@ export async function maybeInstallDaemon(params: {
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
}),
|
||||
params.runtime,
|
||||
1,
|
||||
) as GatewayDaemonRuntime;
|
||||
}
|
||||
}
|
||||
@@ -156,7 +158,7 @@ export async function maybeInstallDaemon(params: {
|
||||
await ensureSystemdUserLingerInteractive({
|
||||
runtime: params.runtime,
|
||||
prompter: {
|
||||
confirm: async (p) => guardCancel(await confirm(p), params.runtime),
|
||||
confirm: async (p) => guardCancel(await confirm(p), params.runtime, 1),
|
||||
note,
|
||||
},
|
||||
reason:
|
||||
|
||||
@@ -55,6 +55,7 @@ export async function promptGatewayConfig(
|
||||
validate: validateGatewayPortInput,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
const port = parsePort(portRaw) ?? resolveGatewayPort(cfg);
|
||||
|
||||
@@ -90,6 +91,7 @@ export async function promptGatewayConfig(
|
||||
],
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
let customBindHost: string | undefined;
|
||||
@@ -101,6 +103,7 @@ export async function promptGatewayConfig(
|
||||
validate: validateDottedDecimalIPv4Input,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
customBindHost = readStringValue(input);
|
||||
}
|
||||
@@ -120,6 +123,7 @@ export async function promptGatewayConfig(
|
||||
initialValue: "token",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
) as GatewayAuthChoice;
|
||||
|
||||
let tailscaleMode = guardCancel(
|
||||
@@ -128,6 +132,7 @@ export async function promptGatewayConfig(
|
||||
options: [...TAILSCALE_EXPOSURE_OPTIONS],
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
// Detect Tailscale binary before proceeding with serve/funnel setup.
|
||||
@@ -149,6 +154,7 @@ export async function promptGatewayConfig(
|
||||
initialValue: false,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,6 +207,7 @@ export async function promptGatewayConfig(
|
||||
initialValue: "plaintext",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
if (tokenInputMode === "ref") {
|
||||
const envVar = guardCancel(
|
||||
@@ -221,6 +228,7 @@ export async function promptGatewayConfig(
|
||||
},
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
const envVarName = normalizeOptionalString(envVar) ?? "";
|
||||
gatewayToken = {
|
||||
@@ -237,6 +245,7 @@ export async function promptGatewayConfig(
|
||||
message: "Gateway token (blank to generate)",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
gatewayTokenForCalls = normalizeGatewayTokenInput(tokenInput) || randomToken();
|
||||
gatewayToken = gatewayTokenForCalls;
|
||||
@@ -250,6 +259,7 @@ export async function promptGatewayConfig(
|
||||
validate: validateGatewayPasswordInput,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
gatewayPassword = normalizeOptionalString(passwordInput) ?? "";
|
||||
}
|
||||
@@ -275,6 +285,7 @@ export async function promptGatewayConfig(
|
||||
validate: (value) => (value?.trim() ? undefined : "User header is required"),
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
const requiredHeadersRaw = guardCancel(
|
||||
@@ -283,6 +294,7 @@ export async function promptGatewayConfig(
|
||||
placeholder: "x-forwarded-proto,x-forwarded-host",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
const requiredHeaders = requiredHeadersRaw
|
||||
? normalizeStringEntries(requiredHeadersRaw.split(","))
|
||||
@@ -294,6 +306,7 @@ export async function promptGatewayConfig(
|
||||
placeholder: "nick@example.com,admin@company.com",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
const allowUsers = allowUsersRaw ? normalizeStringEntries(allowUsersRaw.split(",")) : [];
|
||||
|
||||
@@ -309,6 +322,7 @@ export async function promptGatewayConfig(
|
||||
},
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
trustedProxies = normalizeStringEntries(trustedProxiesRaw.split(","));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Configure wizard tests cover guided setup routing across gateway, auth, channels, skills, and search.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const writeConfigFile = vi.fn();
|
||||
@@ -48,6 +49,7 @@ const mocks = vi.hoisted(() => {
|
||||
Boolean(config.auth?.profiles?.["openai:default"]),
|
||||
),
|
||||
setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg),
|
||||
guardCancel: vi.fn((value: unknown, _runtime: RuntimeEnv, _exitCode?: number) => value),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -114,7 +116,7 @@ vi.mock("./onboard-helpers.js", () => ({
|
||||
DEFAULT_WORKSPACE: "~/.openclaw/workspace",
|
||||
applyWizardMetadata: (cfg: OpenClawConfig) => cfg,
|
||||
ensureWorkspaceAndSessions: vi.fn(),
|
||||
guardCancel: <T>(value: T) => value,
|
||||
guardCancel: mocks.guardCancel,
|
||||
printWizardHeader: mocks.printWizardHeader,
|
||||
probeGatewayReachable: mocks.probeGatewayReachable,
|
||||
resolveAdvertisedControlUiLinks: mocks.resolveAdvertisedControlUiLinks,
|
||||
@@ -346,6 +348,8 @@ describe("runConfigureWizard", () => {
|
||||
config: cfg,
|
||||
port: 18789,
|
||||
}));
|
||||
mocks.guardCancel.mockReset();
|
||||
mocks.guardCancel.mockImplementation((value: unknown) => value);
|
||||
});
|
||||
|
||||
it("persists gateway.mode=local when only the run mode is selected", async () => {
|
||||
@@ -393,6 +397,37 @@ describe("runConfigureWizard", () => {
|
||||
expect(remoteProbe?.timeoutMs).toBe(300);
|
||||
});
|
||||
|
||||
it("uses the resolved configured port for the local gateway startup hint", async () => {
|
||||
setupBaseWizardState({
|
||||
gateway: {
|
||||
mode: "local",
|
||||
port: 18991,
|
||||
},
|
||||
});
|
||||
mocks.resolveGatewayPort.mockReturnValue(18991);
|
||||
mocks.probeGatewayReachable
|
||||
.mockResolvedValueOnce({ ok: true })
|
||||
.mockResolvedValue({ ok: false });
|
||||
mocks.clackSelect.mockResolvedValue("local");
|
||||
|
||||
await runConfigureWizard({ command: "configure", sections: ["gateway"] }, createRuntime());
|
||||
|
||||
expect(mocks.probeGatewayReachable).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ url: "ws://127.0.0.1:18991", timeoutMs: 300 }),
|
||||
);
|
||||
expect(mocks.clackSelect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: "Where will the Gateway run?",
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
value: "local",
|
||||
hint: "Gateway reachable (ws://127.0.0.1:18991)",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("advertises LAN Control UI links while probing the local gateway", async () => {
|
||||
setupBaseWizardState({
|
||||
gateway: {
|
||||
@@ -455,6 +490,24 @@ describe("runConfigureWizard", () => {
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("uses nonzero exit semantics for cancellation at the first direct Clack prompt", async () => {
|
||||
const runtime = createRuntime();
|
||||
setupBaseWizardState();
|
||||
mocks.guardCancel.mockImplementationOnce(
|
||||
(_value: unknown, promptRuntime: RuntimeEnv, exitCode?: number) => {
|
||||
promptRuntime.exit(exitCode ?? 0);
|
||||
throw new Error("direct prompt cancelled");
|
||||
},
|
||||
);
|
||||
|
||||
await expect(runConfigureWizard({ command: "configure" }, runtime)).rejects.toThrow(
|
||||
"direct prompt cancelled",
|
||||
);
|
||||
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(mocks.writeConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not gate model-only configure behind Gateway run-mode selection", async () => {
|
||||
setupBaseWizardState();
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ async function promptConfigureSection(
|
||||
initialValue: CONFIGURE_SECTION_OPTIONS[0]?.value,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,6 +188,7 @@ async function promptChannelMode(runtime: RuntimeEnv): Promise<ChannelsWizardMod
|
||||
initialValue: "configure",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
) as ChannelsWizardMode;
|
||||
}
|
||||
|
||||
@@ -221,6 +223,7 @@ async function promptWebToolsConfig(
|
||||
initialValue: existingSearch?.enabled ?? hasManagedSearchProviders,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
let nextSearch: WebSearchConfig = {
|
||||
@@ -252,6 +255,7 @@ async function promptWebToolsConfig(
|
||||
initialValue: existingSearch?.openaiCodex?.enabled === true,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
if (enableCodexNative) {
|
||||
@@ -273,6 +277,7 @@ async function promptWebToolsConfig(
|
||||
initialValue: existingSearch?.openaiCodex?.mode ?? "cached",
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
nextSearch = {
|
||||
...nextSearch,
|
||||
@@ -288,6 +293,7 @@ async function promptWebToolsConfig(
|
||||
initialValue: Boolean(existingSearch?.provider),
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
nextSearch = {
|
||||
@@ -342,6 +348,7 @@ async function promptWebToolsConfig(
|
||||
initialValue: existingFetch?.enabled ?? true,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
|
||||
const nextFetch = {
|
||||
@@ -422,7 +429,7 @@ export async function runConfigureWizard(
|
||||
selectedSections.includes("daemon") ||
|
||||
selectedSections.includes("health");
|
||||
const promptGatewayRunMode = async (): Promise<OnboardMode> => {
|
||||
const localUrl = "ws://127.0.0.1:18789";
|
||||
const localUrl = `ws://127.0.0.1:${resolveGatewayPort(baseConfig)}`;
|
||||
const remoteUrl = normalizeOptionalString(baseConfig.gateway?.remote?.url) ?? "";
|
||||
const localProbePromise = (async () => {
|
||||
const [baseLocalProbeToken, baseLocalProbePassword] = await Promise.all([
|
||||
@@ -482,6 +489,7 @@ export async function runConfigureWizard(
|
||||
],
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -580,6 +588,7 @@ export async function runConfigureWizard(
|
||||
initialValue: workspaceDir,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
workspaceDir = resolveUserPath(
|
||||
normalizeOptionalString(workspaceInput ?? "") || DEFAULT_WORKSPACE,
|
||||
@@ -650,6 +659,7 @@ export async function runConfigureWizard(
|
||||
validate: validateGatewayPortInput,
|
||||
}),
|
||||
runtime,
|
||||
1,
|
||||
);
|
||||
gatewayPort = parsePort(portInput) ?? gatewayPort;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user