* fix: infer --auth-choice from API key flags during non-interactive onboarding When --anthropic-api-key (or other provider key flags) is passed without an explicit --auth-choice, the auth choice defaults to "skip", silently discarding the API key. This means the gateway starts without credentials and fails on every inbound message with "No API key found for provider". Add inferAuthChoiceFromFlags() to derive the correct auth choice from whichever provider API key flag was supplied, so credentials are persisted to auth-profiles.json as expected. Fixes #8481 * fix: infer auth choice from API key flags (#8484) (thanks @f-trycua) * refactor: centralize auth choice inference flags (#8484) (thanks @f-trycua) --------- Co-authored-by: f-trycua <f@trycua.com>
148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
import type { OpenClawConfig } from "../../config/config.js";
|
|
import type { RuntimeEnv } from "../../runtime.js";
|
|
import type { OnboardOptions } from "../onboard-types.js";
|
|
import { formatCliCommand } from "../../cli/command-format.js";
|
|
import { resolveGatewayPort, writeConfigFile } from "../../config/config.js";
|
|
import { logConfigUpdated } from "../../config/logging.js";
|
|
import { DEFAULT_GATEWAY_DAEMON_RUNTIME } from "../daemon-runtime.js";
|
|
import { healthCommand } from "../health.js";
|
|
import {
|
|
applyWizardMetadata,
|
|
DEFAULT_WORKSPACE,
|
|
ensureWorkspaceAndSessions,
|
|
resolveControlUiLinks,
|
|
waitForGatewayReachable,
|
|
} from "../onboard-helpers.js";
|
|
import { inferAuthChoiceFromFlags } from "./local/auth-choice-inference.js";
|
|
import { applyNonInteractiveAuthChoice } from "./local/auth-choice.js";
|
|
import { installGatewayDaemonNonInteractive } from "./local/daemon-install.js";
|
|
import { applyNonInteractiveGatewayConfig } from "./local/gateway-config.js";
|
|
import { logNonInteractiveOnboardingJson } from "./local/output.js";
|
|
import { applyNonInteractiveSkillsConfig } from "./local/skills-config.js";
|
|
import { resolveNonInteractiveWorkspaceDir } from "./local/workspace.js";
|
|
|
|
export async function runNonInteractiveOnboardingLocal(params: {
|
|
opts: OnboardOptions;
|
|
runtime: RuntimeEnv;
|
|
baseConfig: OpenClawConfig;
|
|
}) {
|
|
const { opts, runtime, baseConfig } = params;
|
|
const mode = "local" as const;
|
|
|
|
const workspaceDir = resolveNonInteractiveWorkspaceDir({
|
|
opts,
|
|
baseConfig,
|
|
defaultWorkspaceDir: DEFAULT_WORKSPACE,
|
|
});
|
|
|
|
let nextConfig: OpenClawConfig = {
|
|
...baseConfig,
|
|
agents: {
|
|
...baseConfig.agents,
|
|
defaults: {
|
|
...baseConfig.agents?.defaults,
|
|
workspace: workspaceDir,
|
|
},
|
|
},
|
|
gateway: {
|
|
...baseConfig.gateway,
|
|
mode: "local",
|
|
},
|
|
};
|
|
|
|
const inferredAuthChoice = inferAuthChoiceFromFlags(opts);
|
|
if (!opts.authChoice && inferredAuthChoice.matches.length > 1) {
|
|
runtime.error(
|
|
[
|
|
"Multiple API key flags were provided for non-interactive onboarding.",
|
|
"Use a single provider flag or pass --auth-choice explicitly.",
|
|
`Flags: ${inferredAuthChoice.matches.map((match) => match.label).join(", ")}`,
|
|
].join("\n"),
|
|
);
|
|
runtime.exit(1);
|
|
return;
|
|
}
|
|
const authChoice = opts.authChoice ?? inferredAuthChoice.choice ?? "skip";
|
|
const nextConfigAfterAuth = await applyNonInteractiveAuthChoice({
|
|
nextConfig,
|
|
authChoice,
|
|
opts,
|
|
runtime,
|
|
baseConfig,
|
|
});
|
|
if (!nextConfigAfterAuth) {
|
|
return;
|
|
}
|
|
nextConfig = nextConfigAfterAuth;
|
|
|
|
const gatewayBasePort = resolveGatewayPort(baseConfig);
|
|
const gatewayResult = applyNonInteractiveGatewayConfig({
|
|
nextConfig,
|
|
opts,
|
|
runtime,
|
|
defaultPort: gatewayBasePort,
|
|
});
|
|
if (!gatewayResult) {
|
|
return;
|
|
}
|
|
nextConfig = gatewayResult.nextConfig;
|
|
|
|
nextConfig = applyNonInteractiveSkillsConfig({ nextConfig, opts, runtime });
|
|
|
|
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
|
await writeConfigFile(nextConfig);
|
|
logConfigUpdated(runtime);
|
|
|
|
await ensureWorkspaceAndSessions(workspaceDir, runtime, {
|
|
skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap),
|
|
});
|
|
|
|
await installGatewayDaemonNonInteractive({
|
|
nextConfig,
|
|
opts,
|
|
runtime,
|
|
port: gatewayResult.port,
|
|
gatewayToken: gatewayResult.gatewayToken,
|
|
});
|
|
|
|
const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
|
|
if (!opts.skipHealth) {
|
|
const links = resolveControlUiLinks({
|
|
bind: gatewayResult.bind as "auto" | "lan" | "loopback" | "custom" | "tailnet",
|
|
port: gatewayResult.port,
|
|
customBindHost: nextConfig.gateway?.customBindHost,
|
|
basePath: undefined,
|
|
});
|
|
await waitForGatewayReachable({
|
|
url: links.wsUrl,
|
|
token: gatewayResult.gatewayToken,
|
|
deadlineMs: 15_000,
|
|
});
|
|
await healthCommand({ json: false, timeoutMs: 10_000 }, runtime);
|
|
}
|
|
|
|
logNonInteractiveOnboardingJson({
|
|
opts,
|
|
runtime,
|
|
mode,
|
|
workspaceDir,
|
|
authChoice,
|
|
gateway: {
|
|
port: gatewayResult.port,
|
|
bind: gatewayResult.bind,
|
|
authMode: gatewayResult.authMode,
|
|
tailscaleMode: gatewayResult.tailscaleMode,
|
|
},
|
|
installDaemon: Boolean(opts.installDaemon),
|
|
daemonRuntime: opts.installDaemon ? daemonRuntimeRaw : undefined,
|
|
skipSkills: Boolean(opts.skipSkills),
|
|
skipHealth: Boolean(opts.skipHealth),
|
|
});
|
|
|
|
if (!opts.json) {
|
|
runtime.log(
|
|
`Tip: run \`${formatCliCommand("openclaw configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.openclaw.ai/tools/web`,
|
|
);
|
|
}
|
|
}
|