Skip to content

Commit ad943ec

Browse files
committed
fix(cli): guide auth and gateway setup errors
1 parent 4775b01 commit ad943ec

8 files changed

Lines changed: 73 additions & 29 deletions

File tree

src/cli/gateway-cli/run.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,14 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
520520
});
521521
const portOverride = parsePort(opts.port);
522522
if (opts.port !== undefined && portOverride === null) {
523-
defaultRuntime.error("Invalid port");
523+
defaultRuntime.error("Invalid --port. Use a positive port number, for example 3000.");
524524
defaultRuntime.exit(1);
525525
}
526526
const port = portOverride ?? resolveGatewayPort(cfg);
527527
if (!Number.isFinite(port) || port <= 0) {
528-
defaultRuntime.error("Invalid port");
528+
defaultRuntime.error(
529+
`Gateway port is invalid in config. Set it with ${formatCliCommand("openclaw config set gateway.port 3000")} or pass --port 3000.`,
530+
);
529531
defaultRuntime.exit(1);
530532
}
531533
const { formatFutureConfigActionBlock, resolveFutureConfigActionBlock } =
@@ -613,7 +615,9 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
613615
gatewayLog.info(`force: waited ${bindWaitMs}ms for port ${port} to become bindable`);
614616
}
615617
} catch (err) {
616-
defaultRuntime.error(`Force: ${String(err)}`);
618+
defaultRuntime.error(
619+
`Could not free port ${port}: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw gateway status --deep")} to inspect the listener.`,
620+
);
617621
defaultRuntime.exit(1);
618622
return;
619623
}
@@ -627,15 +631,15 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
627631
const authModeRaw = toOptionString(opts.auth);
628632
const authMode = parseEnumOption(authModeRaw, GATEWAY_AUTH_MODES);
629633
if (authModeRaw && !authMode) {
630-
defaultRuntime.error(`Invalid --auth (use ${formatModeErrorList(GATEWAY_AUTH_MODES)})`);
634+
defaultRuntime.error(`Invalid --auth. Use ${formatModeErrorList(GATEWAY_AUTH_MODES)}.`);
631635
defaultRuntime.exit(1);
632636
return;
633637
}
634638
const tailscaleRaw = toOptionString(opts.tailscale);
635639
const tailscaleMode = parseEnumOption(tailscaleRaw, GATEWAY_TAILSCALE_MODES);
636640
if (tailscaleRaw && !tailscaleMode) {
637641
defaultRuntime.error(
638-
`Invalid --tailscale (use ${formatModeErrorList(GATEWAY_TAILSCALE_MODES)})`,
642+
`Invalid --tailscale. Use ${formatModeErrorList(GATEWAY_TAILSCALE_MODES)}.`,
639643
);
640644
defaultRuntime.exit(1);
641645
return;
@@ -830,7 +834,9 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
830834
return;
831835
}
832836
await maybeWriteGatewayStartupFailureBundle(err);
833-
defaultRuntime.error(`Gateway failed to start: ${String(err)}`);
837+
defaultRuntime.error(
838+
`Gateway failed to start: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw gateway status --deep")} for diagnostics.`,
839+
);
834840
defaultRuntime.exit(1);
835841
}
836842
}

src/cli/plugins-inspect-command.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { defaultRuntime } from "../runtime.js";
88
import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
99
import { theme } from "../terminal/theme.js";
1010
import { shortenHomeInString, shortenHomePath } from "../utils.js";
11+
import { formatCliCommand } from "./command-format.js";
1112
import { quietPluginJsonLogger } from "./plugins-command-helpers.js";
1213

1314
export type PluginInspectOptions = {
@@ -232,7 +233,9 @@ export async function runPluginsInspectCommand(
232233
);
233234
const targetPlugin = snapshotReport.plugins.find((entry) => entry.id === id || entry.name === id);
234235
if (!targetPlugin) {
235-
defaultRuntime.error(`Plugin not found: ${id}`);
236+
defaultRuntime.error(
237+
`Plugin not found: ${id}. Run ${formatCliCommand("openclaw plugins list")} to see installed plugins.`,
238+
);
236239
return defaultRuntime.exit(1);
237240
}
238241
const report = runtimeInspect
@@ -254,7 +257,9 @@ export async function runPluginsInspectCommand(
254257
report,
255258
});
256259
if (!inspect) {
257-
defaultRuntime.error(`Plugin not found: ${id}`);
260+
defaultRuntime.error(
261+
`Plugin not found: ${id}. Run ${formatCliCommand("openclaw plugins list --json")} to inspect raw discovery state.`,
262+
);
258263
return defaultRuntime.exit(1);
259264
}
260265
const install = installRecords[inspect.plugin.id];

src/commands/models/auth-order.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
setAuthProfileOrder,
88
} from "../../agents/auth-profiles.js";
99
import { normalizeProviderId } from "../../agents/model-selection.js";
10+
import { formatCliCommand } from "../../cli/command-format.js";
1011
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
1112
import { normalizeStringEntries } from "../../shared/string-normalization.js";
1213
import { shortenHomePath } from "../../utils.js";
@@ -37,7 +38,9 @@ async function resolveAuthOrderContext(
3738
) {
3839
const rawProvider = opts.provider?.trim();
3940
if (!rawProvider) {
40-
throw new Error("Missing --provider.");
41+
throw new Error(
42+
`Missing --provider. Run ${formatCliCommand("openclaw models auth list")} to see saved provider profiles.`,
43+
);
4144
}
4245
const provider = normalizeProviderId(rawProvider);
4346
const cfg = await loadModelsConfig({ commandName: "models auth-order", runtime });
@@ -83,7 +86,9 @@ export async function modelsAuthOrderClearCommand(
8386
order: null,
8487
});
8588
if (!updated) {
86-
throw new Error("Failed to update auth-state.json (lock busy?).");
89+
throw new Error(
90+
`Failed to update auth-state.json; the auth state lock may be busy. Wait a moment and rerun ${formatCliCommand("openclaw models auth order clear --provider " + provider)}.`,
91+
);
8792
}
8893

8994
runtime.log(`Agent: ${agentId}`);
@@ -103,13 +108,17 @@ export async function modelsAuthOrderSetCommand(
103108
const providerKey = provider;
104109
const requested = normalizeStringEntries(opts.order ?? []);
105110
if (requested.length === 0) {
106-
throw new Error("Missing profile ids. Provide one or more profile ids.");
111+
throw new Error(
112+
`Missing profile ids. Run ${formatCliCommand("openclaw models auth list --provider " + provider)} to choose one or more profile ids.`,
113+
);
107114
}
108115

109116
for (const profileId of requested) {
110117
const cred = store.profiles[profileId];
111118
if (!cred) {
112-
throw new Error(`Auth profile "${profileId}" not found in ${agentDir}.`);
119+
throw new Error(
120+
`Auth profile "${profileId}" not found in ${shortenHomePath(agentDir)}. Run ${formatCliCommand("openclaw models auth list --provider " + provider)} to see saved profiles.`,
121+
);
113122
}
114123
if (normalizeProviderId(cred.provider) !== providerKey) {
115124
throw new Error(`Auth profile "${profileId}" is for ${cred.provider}, not ${provider}.`);
@@ -122,7 +131,9 @@ export async function modelsAuthOrderSetCommand(
122131
order: requested,
123132
});
124133
if (!updated) {
125-
throw new Error("Failed to update auth-state.json (lock busy?).");
134+
throw new Error(
135+
`Failed to update auth-state.json; the auth state lock may be busy. Wait a moment and rerun ${formatCliCommand("openclaw models auth order set --provider " + provider + " <profileIds...>")}.`,
136+
);
126137
}
127138

128139
runtime.log(`Agent: ${agentId}`);

src/commands/models/auth.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ export async function modelsAuthSetupTokenCommand(
350350
runtime: RuntimeEnv,
351351
) {
352352
if (!process.stdin.isTTY) {
353-
throw new Error("setup-token requires an interactive TTY.");
353+
throw new Error(
354+
`setup-token requires an interactive TTY. In automation, use ${formatCliCommand("openclaw models auth paste-token --provider <provider>")} instead.`,
355+
);
354356
}
355357

356358
const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({
@@ -367,7 +369,9 @@ export async function modelsAuthSetupTokenCommand(
367369
const provider =
368370
resolveRequestedProviderOrThrow(tokenProviders, opts.provider) ?? tokenProviders[0] ?? null;
369371
if (!provider) {
370-
throw new Error("No token-capable provider is available.");
372+
throw new Error(
373+
`No token-capable provider is available. Run ${formatCliCommand("openclaw plugins list")} to verify provider plugins are installed.`,
374+
);
371375
}
372376

373377
if (!opts.yes) {
@@ -512,7 +516,9 @@ export async function modelsAuthAddCommand(opts: { agent?: string }, runtime: Ru
512516
const prompter = createClackPrompter();
513517
const method = tokenMethods.find((candidate) => candidate.id === methodId);
514518
if (!method) {
515-
throw new Error(`Unknown token auth method "${methodId}".`);
519+
throw new Error(
520+
`Unknown token auth method "${methodId}". Run ${formatCliCommand("openclaw models auth login --provider " + providerPlugin.id)} to choose interactively.`,
521+
);
516522
}
517523
await runProviderAuthMethod({
518524
config,
@@ -618,7 +624,9 @@ function maybeLogOpenAICodexNativeSearchTip(runtime: RuntimeEnv, providerId: str
618624
}
619625
export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: RuntimeEnv) {
620626
if (!process.stdin.isTTY) {
621-
throw new Error("models auth login requires an interactive TTY.");
627+
throw new Error(
628+
`models auth login requires an interactive TTY. In automation, use ${formatCliCommand("openclaw models auth paste-token --provider <provider>")} when token auth is available.`,
629+
);
622630
}
623631

624632
const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({
@@ -648,7 +656,9 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim
648656
.then((id) => resolveProviderMatch(authProviders, id)));
649657

650658
if (!selectedProvider) {
651-
throw new Error("Unknown provider. Use --provider <id> to pick a provider plugin.");
659+
throw new Error(
660+
`Unknown provider. Run ${formatCliCommand("openclaw models status")} or ${formatCliCommand("openclaw plugins list")} to see available provider plugins.`,
661+
);
652662
}
653663
const chosenMethod = await pickProviderAuthMethod({
654664
provider: selectedProvider,
@@ -657,7 +667,9 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim
657667
});
658668

659669
if (!chosenMethod) {
660-
throw new Error("Unknown auth method. Use --method <id> to select one.");
670+
throw new Error(
671+
`Unknown auth method. Run ${formatCliCommand("openclaw models auth login --provider " + selectedProvider.id)} without --method to choose interactively.`,
672+
);
661673
}
662674

663675
await runProviderAuthMethod({

src/commands/onboard-non-interactive.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ async function runNonInteractiveMigrationImport(params: {
6363
}) {
6464
const providerId = params.opts.importFrom?.trim();
6565
if (!providerId) {
66-
params.runtime.error("--import-from is required for non-interactive migration import.");
66+
params.runtime.error(
67+
`--import-from is required for non-interactive migration import. Run ${formatCliCommand("openclaw migrate list")} to choose a provider.`,
68+
);
6769
params.runtime.exit(1);
6870
return;
6971
}
@@ -111,7 +113,9 @@ export async function runNonInteractiveSetup(
111113
: {};
112114
const mode = opts.mode ?? "local";
113115
if (mode !== "local" && mode !== "remote") {
114-
runtime.error(`Invalid --mode "${String(mode)}" (use local|remote).`);
116+
runtime.error(
117+
`Invalid --mode "${String(mode)}". Use "local" or "remote", or run ${formatCliCommand("openclaw onboard")} for interactive setup.`,
118+
);
115119
runtime.exit(1);
116120
return;
117121
}

src/commands/onboard-non-interactive/local/daemon-install.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
22
import { resolveGatewayService } from "../../../daemon/service.js";
33
import { isSystemdUserServiceAvailable } from "../../../daemon/systemd.js";
4+
import { formatErrorMessage } from "../../../infra/errors.js";
45
import type { RuntimeEnv } from "../../../runtime.js";
56
import { buildGatewayInstallPlan, gatewayInstallErrorHint } from "../../daemon-install-helpers.js";
67
import { DEFAULT_GATEWAY_DAEMON_RUNTIME, isGatewayDaemonRuntime } from "../../daemon-runtime.js";
@@ -38,7 +39,7 @@ export async function installGatewayDaemonNonInteractive(params: {
3839
}
3940

4041
if (!isGatewayDaemonRuntime(daemonRuntimeRaw)) {
41-
runtime.error("Invalid --daemon-runtime (use node or bun)");
42+
runtime.error('Invalid --daemon-runtime. Use "node" or "bun".');
4243
runtime.exit(1);
4344
return { installed: false };
4445
}
@@ -80,7 +81,7 @@ export async function installGatewayDaemonNonInteractive(params: {
8081
environmentValueSources,
8182
});
8283
} catch (err) {
83-
runtime.error(`Gateway service install failed: ${String(err)}`);
84+
runtime.error(`Gateway service install failed: ${formatErrorMessage(err)}`);
8485
runtime.log(gatewayInstallErrorHint());
8586
return { installed: false };
8687
}

src/commands/onboard-non-interactive/local/gateway-config.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { formatCliCommand } from "../../../cli/command-format.js";
12
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
23
import { isValidEnvSecretRefId, resolveSecretInputRef } from "../../../config/types.secrets.js";
34
import type { RuntimeEnv } from "../../../runtime.js";
@@ -23,7 +24,7 @@ export function applyNonInteractiveGatewayConfig(params: {
2324

2425
const hasGatewayPort = opts.gatewayPort !== undefined;
2526
if (hasGatewayPort && (!Number.isFinite(opts.gatewayPort) || (opts.gatewayPort ?? 0) <= 0)) {
26-
runtime.error("Invalid --gateway-port");
27+
runtime.error("Invalid --gateway-port. Use a positive port number, for example 3000.");
2728
runtime.exit(1);
2829
return null;
2930
}
@@ -32,7 +33,7 @@ export function applyNonInteractiveGatewayConfig(params: {
3233
let bind = opts.gatewayBind ?? "loopback";
3334
const authModeRaw = opts.gatewayAuth ?? "token";
3435
if (authModeRaw !== "token" && authModeRaw !== "password") {
35-
runtime.error("Invalid --gateway-auth (use token|password).");
36+
runtime.error('Invalid --gateway-auth. Use "token" or "password".');
3637
runtime.exit(1);
3738
return null;
3839
}
@@ -70,19 +71,23 @@ export function applyNonInteractiveGatewayConfig(params: {
7071
if (gatewayTokenRefEnv) {
7172
if (!isValidEnvSecretRefId(gatewayTokenRefEnv)) {
7273
runtime.error(
73-
"Invalid --gateway-token-ref-env (use env var name like OPENCLAW_GATEWAY_TOKEN).",
74+
"Invalid --gateway-token-ref-env. Use an environment variable name like OPENCLAW_GATEWAY_TOKEN.",
7475
);
7576
runtime.exit(1);
7677
return null;
7778
}
7879
if (explicitGatewayToken) {
79-
runtime.error("Use either --gateway-token or --gateway-token-ref-env, not both.");
80+
runtime.error(
81+
"Use either --gateway-token or --gateway-token-ref-env, not both. Prefer --gateway-token-ref-env to avoid writing plaintext tokens.",
82+
);
8083
runtime.exit(1);
8184
return null;
8285
}
8386
const resolvedFromEnv = process.env[gatewayTokenRefEnv]?.trim();
8487
if (!resolvedFromEnv) {
85-
runtime.error(`Environment variable "${gatewayTokenRefEnv}" is missing or empty.`);
88+
runtime.error(
89+
`Environment variable "${gatewayTokenRefEnv}" is missing or empty. Export it first, then rerun ${formatCliCommand("openclaw onboard --non-interactive")}.`,
90+
);
8691
runtime.exit(1);
8792
return null;
8893
}

src/commands/onboard-non-interactive/local/skills-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function applyNonInteractiveSkillsConfig(params: {
1414

1515
const nodeManager = opts.nodeManager ?? "npm";
1616
if (!["npm", "pnpm", "bun"].includes(nodeManager)) {
17-
runtime.error("Invalid --node-manager (use npm, pnpm, or bun)");
17+
runtime.error('Invalid --node-manager. Use "npm", "pnpm", or "bun".');
1818
runtime.exit(1);
1919
return nextConfig;
2020
}

0 commit comments

Comments
 (0)