Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
671502e
fix(inference): allow keyless localhost endpoints
sandl99 Jul 23, 2026
d41a167
test(onboard): cover keyless localhost prompt
sandl99 Jul 23, 2026
c0f5742
refactor(onboard): scope localhost credential check
sandl99 Jul 23, 2026
ef99a83
fix(onboard): accept blank localhost API key
sandl99 Jul 23, 2026
3ea59fb
fix(onboard): require explicit loopback no-auth mode
sandl99 Jul 23, 2026
6d00107
refactor(onboard): keep entrypoint net-neutral
sandl99 Jul 23, 2026
6985ef8
fix(onboard): preserve local inference proxy ownership
sandl99 Jul 23, 2026
4e4cb49
docs(inference): document loopback proxy ownership
sandl99 Jul 23, 2026
83e4f31
fix(inference): enforce loopback proxy ownership
sandl99 Jul 23, 2026
78f8f55
test(onboard): keep proxy failure setup linear
sandl99 Jul 23, 2026
d0a8cb7
fix(inference): preserve compatible proxy on Ollama conflict
sandl99 Jul 23, 2026
bb24177
fix(onboard): simplify loopback no-auth support
sandl99 Jul 23, 2026
43c024e
docs(inference): clarify loopback no-auth scope
sandl99 Jul 23, 2026
70830e1
refactor(onboard): keep entrypoint net neutral
sandl99 Jul 23, 2026
d2990bd
fix(inference): preserve no-auth proxy backend
sandl99 Jul 23, 2026
275e3cf
test(inference): cover no-auth provider registration
sandl99 Jul 23, 2026
414cc99
fix(inference): defer no-auth proxy persistence
sandl99 Jul 23, 2026
c7b77da
fix(inference): preserve selected proxy backend
jyaunches Jul 24, 2026
842f989
test(inference): define proxy recovery boundary
jyaunches Jul 24, 2026
222641d
fix(ci): scope shard evidence to selected tests
jyaunches Jul 24, 2026
0e28b71
merge(main): sync PR 7427
jyaunches Jul 24, 2026
42a3f33
merge(main): sync PR 7427
jyaunches Jul 24, 2026
8cd7198
fix(ci): preserve CLI test selector in risk signals
jyaunches Jul 24, 2026
25c595d
fix(inference): restore proxy after abandoned setup
jyaunches Jul 24, 2026
aecaaa4
test(inference): stabilize proxy rollback harness
jyaunches Jul 24, 2026
6b067f4
test(inference): mock proxy readiness probe
jyaunches Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ Use these details when your first-run path needs more control.
| Local Ollama | You want a host-local Ollama model. | None |
| Model Router | You want NemoClaw to start the host-side model router. | `NVIDIA_INFERENCE_API_KEY` |

If a compatible endpoint does not require authentication, set its credential variable to any non-empty placeholder.
For an OpenAI-compatible endpoint using HTTP on `localhost`, `127.0.0.1`, or `[::1]` and port `8000`, `11434`, or `11435`, press Enter at the API key prompt to select no authentication.

After you enter a sandbox name, the wizard asks for final confirmation before it registers the provider, prompts for integrations, and builds the sandbox image.

Expand Down
11 changes: 6 additions & 5 deletions docs/inference/set-up-openai-compatible-endpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ If you manually enter a sandbox-internal alias such as `http://host.openshell.in
Use a host-routable endpoint such as `localhost` when you need onboarding to verify the API, tool-calling, and streaming paths before gateway registration.
Otherwise, verify the runtime route after onboarding with `$$nemoclaw <name> status` and a short agent request.

The wizard prompts for an API key.
Enter any non-empty placeholder such as `dummy` when the server does not require authentication.
For an HTTP URL using the exact host `localhost`, `127.0.0.1`, or `[::1]` and port `8000`, `11434`, or `11435`, the API key prompt says that pressing Enter selects no authentication.
Other URLs still require `COMPATIBLE_API_KEY`.

Refer to [Choose a Compatible Inference API](choose-compatible-inference-api) for the probe order and runtime API selection.

Expand Down Expand Up @@ -102,7 +102,7 @@ Enable it only when the endpoint supports the capabilities your agent requires.
NEMOCLAW_PROVIDER=custom \
NEMOCLAW_ENDPOINT_URL=http://localhost:8000/v1 \
NEMOCLAW_MODEL=meta-llama/Llama-3.1-8B-Instruct \
COMPATIBLE_API_KEY=dummy \
NEMOCLAW_COMPATIBLE_AUTH_MODE=none \
$$nemoclaw onboard --non-interactive
```

Expand All @@ -112,7 +112,7 @@ For the raw model example, use the ID returned by `/v1/models`.
NEMOCLAW_PROVIDER=custom \
NEMOCLAW_ENDPOINT_URL=http://localhost:8000/v1 \
NEMOCLAW_MODEL=NVIDIA-Nemotron3-Nano-4B-Q4_K_M.gguf \
COMPATIBLE_API_KEY=dummy \
NEMOCLAW_COMPATIBLE_AUTH_MODE=none \
$$nemoclaw onboard --non-interactive
```

Expand All @@ -121,9 +121,10 @@ NEMOCLAW_PROVIDER=custom \
| `NEMOCLAW_PROVIDER` | Set to `custom`. |
| `NEMOCLAW_ENDPOINT_URL` | Base URL of the server. |
| `NEMOCLAW_MODEL` | Model ID reported by the server. |
| `NEMOCLAW_COMPATIBLE_AUTH_MODE` | Set to `none` to explicitly select no authentication for an HTTP endpoint using `localhost`, `127.0.0.1`, or `[::1]` and port `8000`, `11434`, or `11435`. |
| `NEMOCLAW_REASONING` | Enables reasoning-only validation with the case-insensitive true values `true`, `1`, `yes`, and `y`. |
| `NEMOCLAW_TRUSTED_PRIVATE_INFERENCE_HOSTS` | Optional comma-separated exact hostnames or IP literals for operator-owned private inference endpoints. Wildcards are not supported. |
| `COMPATIBLE_API_KEY` | Endpoint API key, or a non-empty placeholder when authentication is not required. |
| `COMPATIBLE_API_KEY` | Endpoint API key. Required unless loopback no-auth mode is selected. |

Private and reserved addresses are blocked by default.
To use an inference gateway on a trusted corporate network, list only its exact host and keep the endpoint URL on that host:
Expand Down
1 change: 1 addition & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3392,6 +3392,7 @@ Set them before running `$$nemoclaw onboard`.
| `NEMOCLAW_PROVIDER` | provider key (e.g. `build`, `openrouter`, `openai`, `anthropic`, `anthropicCompatible`, `gemini`, `ollama`, `custom`, `vllm`, `nim-local`, `routed`, `hermes-provider`, `install-vllm`, `install-ollama`, `install-windows-ollama`, `start-windows-ollama`) | Selects the inference provider during onboarding. The wizard skips the provider menu in both interactive and non-interactive runs when this is set. Aliases: `cloud` → `build`, `open-router` / `openrouterai` → `openrouter`, `nim` → `nim-local`, `hermes` / `nous` / `nous-portal` → `hermes-provider`, `anthropiccompatible` → `anthropicCompatible`. Invalid values fail fast with the list of accepted keys. |
| `NEMOCLAW_TOOL_DISCLOSURE` | `progressive` or `direct` | Selects progressive tool discovery or the prior direct-exposure behavior. Defaults to `progressive`; `--tool-disclosure` takes precedence when both are set. |
| `NEMOCLAW_ENDPOINT_URL` | URL | Custom endpoint URL. Used together with `NEMOCLAW_PROVIDER=custom` for OpenAI-compatible endpoints or `NEMOCLAW_PROVIDER=anthropicCompatible` for Anthropic-compatible endpoints. |
| `NEMOCLAW_COMPATIBLE_AUTH_MODE` | `none` or unset | Explicitly selects no authentication for an HTTP OpenAI-compatible endpoint using `localhost`, `127.0.0.1`, or `[::1]` and port `8000`, `11434`, or `11435` during non-interactive onboarding. |
| `NEMOCLAW_TRUSTED_PRIVATE_INFERENCE_HOSTS` | comma-separated exact hostnames or IP literals | Allows operator-owned RFC1918, CGNAT, or IPv6 ULA inference endpoints during custom endpoint onboarding. Link-local metadata and other reserved ranges remain blocked; DNS resolution and connection pinning remain active; wildcards are not supported. |
| `NEMOCLAW_PREFERRED_API` | `completions` (currently the only honored value) | Forces the validation probe to use the `/v1/chat/completions` API path instead of the newer `/v1/responses` API. |
| `NEMOCLAW_INFERENCE_INPUTS` | comma-separated list of `text` and/or `image` | Declares model input modalities for vision-capable models. Validated strictly; unknown tokens are ignored. |
Expand Down
7 changes: 4 additions & 3 deletions scripts/ollama-auth-proxy.mts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ if (!TOKEN) {

const LISTEN_PORT = parseInt(process.env.OLLAMA_PROXY_PORT || "11435", 10);
const BACKEND_PORT = parseInt(process.env.OLLAMA_BACKEND_PORT || "11434", 10);
const BACKEND_URL = new URL(process.env.OLLAMA_BACKEND_URL || `http://127.0.0.1:${BACKEND_PORT}`);

const server = http.createServer(
(clientReq: http.IncomingMessage, clientRes: http.ServerResponse) => {
Expand Down Expand Up @@ -71,8 +72,8 @@ const server = http.createServer(

const proxyReq = http.request(
{
hostname: "127.0.0.1",
port: BACKEND_PORT,
hostname: BACKEND_URL.hostname,
port: BACKEND_URL.port,
path: clientReq.url,
method: clientReq.method,
headers,
Expand Down Expand Up @@ -112,5 +113,5 @@ server.on("error", (err: NodeJS.ErrnoException) => {
});

server.listen(LISTEN_PORT, "0.0.0.0", () => {
console.log(`Ollama auth proxy listening on 0.0.0.0:${LISTEN_PORT} -> 127.0.0.1:${BACKEND_PORT}`);
console.log(`Ollama auth proxy listening on 0.0.0.0:${LISTEN_PORT} -> ${BACKEND_URL.origin}`);
});
48 changes: 43 additions & 5 deletions src/lib/inference/ollama/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ function sleep(seconds: number): void {

// ── Token persistence ────────────────────────────────────────────

function persistProxyToken(token: string): void {
function persistProxyToken(token: string, backendUrl = `http://127.0.0.1:${OLLAMA_PORT}`): void {
writeLocalAdapterSecretFile(path.join(PROXY_STATE_DIR, "ollama-backend"), backendUrl);
writeLocalAdapterSecretFile(PROXY_TOKEN_PATH, token);
}

Expand Down Expand Up @@ -142,13 +143,15 @@ function isOllamaProxyProcess(pid: number | null | undefined): boolean {
return isLocalAdapterProcess(pid, isOllamaAuthProxyCommandLine, runCapture);
}

function spawnOllamaAuthProxy(token: string): number | null {
function spawnOllamaAuthProxy(token: string, backendUrl?: string): number | null {
const url = backendUrl || readLocalAdapterTextFile(path.join(PROXY_STATE_DIR, "ollama-backend"));
const child = spawnDetachedNodeAdapter({
scriptPath: path.join(SCRIPTS, "ollama-auth-proxy.mts"),
env: {
OLLAMA_PROXY_TOKEN: token,
OLLAMA_PROXY_PORT: String(OLLAMA_PROXY_PORT),
OLLAMA_BACKEND_PORT: String(OLLAMA_PORT),
...(url ? { OLLAMA_BACKEND_URL: url } : {}),
},
buildEnv: buildSubprocessEnv,
});
Expand Down Expand Up @@ -256,7 +259,7 @@ function printProxyPortConflict(owners: { pids: number[]; descriptions: string[]
// so poll with backoff instead of the previous single 2s probe (issue #4820).
const PROXY_START_ATTEMPTS = 12;

function startOllamaAuthProxy(): boolean {
function startOllamaAuthProxy(backendUrl?: string): boolean {
const crypto = require("crypto");
killStaleProxy();

Expand All @@ -277,7 +280,7 @@ function startOllamaAuthProxy(): boolean {
// Don't persist yet — wait until provider is confirmed in setupInference.
// If the user backs out to a different provider, the token stays in memory
// only and is discarded.
const pid = spawnOllamaAuthProxy(proxyToken);
const pid = spawnOllamaAuthProxy(proxyToken, backendUrl || `http://127.0.0.1:${OLLAMA_PORT}`);

// Poll for readiness with backoff. Three terminal outcomes:
// • proxy alive and listening → success
Expand Down Expand Up @@ -316,6 +319,26 @@ function startOllamaAuthProxy(): boolean {
return false;
}

function noAuthProxy(endpointUrl: string) {
const endpoint = new URL(endpointUrl);
if (!startOllamaAuthProxy(endpoint.origin)) {
restorePersistedOllamaAuthProxy();
throw new Error("Could not start the protected loopback route.");
}
return {
baseUrl: `http://host.openshell.internal:${OLLAMA_PROXY_PORT}${endpoint.pathname}`,
credentialValue: getOllamaProxyToken()!,
persist: () => persistProxyToken(getOllamaProxyToken()!, endpoint.origin),
restore: restorePersistedOllamaAuthProxy,
};
}

function restorePersistedOllamaAuthProxy(): void {
killStaleProxy();
ollamaProxyToken = null;
ensureOllamaAuthProxy();
}

/**
* Probe the running proxy to confirm it accepts the given token.
* The proxy validates auth before forwarding to Ollama. A backend error like
Expand Down Expand Up @@ -363,11 +386,25 @@ function proxyOwnsPortWithToken(token: string): boolean {
* after a failed re-onboard (see issue #2553).
*/
function ensureOllamaAuthProxy(): void {
const pid = loadPersistedProxyPid();
// startOllamaAuthProxy replaces the live proxy before setupInference confirms
// the selected provider and calls persistProxyToken. It cannot persist sooner:
// the user may still back out, leaving the previous route as the committed one.
// Preserve this in-memory proxy across recovery during that transition. This
// exception can go away when provider selection commits the replacement token
// and backend before any recovery path can call ensureOllamaAuthProxy.
if (
ollamaProxyToken &&
isOllamaProxyProcess(pid) &&
probeProxyToken(ollamaProxyToken) === "accepted"
) {
return;
}

// Try to load persisted token first — if none, this isn't an Ollama setup.
const token = loadPersistedProxyToken();
if (!token) return;

const pid = loadPersistedProxyPid();
if (isOllamaProxyProcess(pid)) {
const tokenStatus = probeProxyToken(token);
if (tokenStatus === "accepted") {
Expand Down Expand Up @@ -974,6 +1011,7 @@ export {
getOllamaPullTimeoutMs,
isProxyHealthy,
killStaleProxy,
noAuthProxy,
persistAndProbeOllamaProxy,
persistProxyToken,
prepareOllamaModel,
Expand Down
22 changes: 10 additions & 12 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const {
finalizeCreatedSandbox,
}: typeof import("./onboard/created-sandbox-finalization") = require("./onboard/created-sandbox-finalization");
const providerKeyBridge: typeof import("./onboard/provider-key-bridge") = require("./onboard/provider-key-bridge");
const compatibleEndpointGatewayRoute: typeof import("./onboard/inference-providers/compatible-endpoint-gateway-route") = require("./onboard/inference-providers/compatible-endpoint-gateway-route");
const {
isLinuxDockerDriverGatewayEnabled,
}: typeof import("./onboard/docker-driver-platform") = require("./onboard/docker-driver-platform");
Expand Down Expand Up @@ -3399,10 +3400,10 @@ async function handleRemoteProviderSelection(args: RemoteProviderSelectionArgs,
_envModelRemote ||
(recoveredFromSandbox && recoveredModel) ||
remoteConfig.defaultModel;
const selectedCredentialEnv = requireValue(
state.credentialEnv,
`Missing credential env for ${remoteConfig.label}`,
);
// biome-ignore format: keep src/lib/onboard.ts net-neutral for growth guardrail.
const selectedCredentialEnv = requireValue(state.credentialEnv, `Missing credential env for ${remoteConfig.label}`);
const compatibleNoAuth = selected.key === "custom" && Boolean(state.endpointUrl && compatibleEndpointGatewayRoute.gatewayReachableCompatibleEndpointUrl(state.provider, state.endpointUrl) !== state.endpointUrl);
const useNoAuth = compatibleNoAuth && (!isNonInteractive() || (process.env.NEMOCLAW_COMPATIBLE_AUTH_MODE || "").trim().toLowerCase() === "none");
const bedrockSelection = await bedrockRuntimeOnboard.selectBedrockRuntimeCustomAnthropic({
selectedKey: selected.key,
endpointUrl: state.endpointUrl,
Expand Down Expand Up @@ -3433,23 +3434,20 @@ async function handleRemoteProviderSelection(args: RemoteProviderSelectionArgs,
state.model = defaultModel;
state.assertRouteCompatible?.();
// biome-ignore format: keep src/lib/onboard.ts net-neutral for growth guardrail.
recoveredProviderReuse.resolveRecoveredProviderCredentialReuse(
if (useNoAuth) state.credentialEnv = OLLAMA_PROXY_CREDENTIAL_ENV; else recoveredProviderReuse.resolveRecoveredProviderCredentialReuse(
{ selected, remoteConfig, state, selectedCredentialEnv, recoveredFromSandbox, selectedModel: defaultModel, sandboxName, recoveredRegistryRoute },
{ resolveProviderCredential, readRecordedInferenceRoute: (name) => readRecordedInferenceRoute(name, args.recoverySessionId), readRecordedProviderEndpoints, readGatewayProviderMetadata: (provider) => onboardProviders.readGatewayProviderMetadata(provider, runOpenshell, args.gatewayName ?? GATEWAY_NAME), note },
);
} else {
const credentialResult = await credentialPrompt.ensureNamedCredential(
selectedCredentialEnv,
`${remoteConfig.label} API key`,
remoteConfig.helpUrl,
openrouterSelection.credentialValidatorForProvider(selected.key),
);
// biome-ignore format: keep src/lib/onboard.ts net-neutral for growth guardrail.
const credentialResult = await credentialPrompt.ensureNamedCredential(selectedCredentialEnv, compatibleNoAuth ? `${remoteConfig.label} API key (press Enter for no authentication)` : `${remoteConfig.label} API key`, remoteConfig.helpUrl, openrouterSelection.credentialValidatorForProvider(selected.key), compatibleNoAuth);
if (credentialPrompt.returningToProviderSelection(credentialResult)) {
return "retry-selection";
}
if (credentialResult === "") state.credentialEnv = OLLAMA_PROXY_CREDENTIAL_ENV;
}
// biome-ignore format: keep src/lib/onboard.ts net-neutral for growth guardrail.
openrouterSelection.validateNonInteractiveCredential({ selectedKey: selected.key, selectedCredentialEnv, isNonInteractive: isNonInteractive(), reuseGatewayCredentialWithoutLocalKey: state.reuseGatewayCredentialWithoutLocalKey, resolveProviderCredential, getCredential, error: (message) => console.error(message), exitProcess: (code) => process.exit(code) });
if (!useNoAuth) openrouterSelection.validateNonInteractiveCredential({ selectedKey: selected.key, selectedCredentialEnv, isNonInteractive: isNonInteractive(), reuseGatewayCredentialWithoutLocalKey: state.reuseGatewayCredentialWithoutLocalKey, resolveProviderCredential, getCredential, error: (message) => console.error(message), exitProcess: (code) => process.exit(code) });
let modelValidator: ((candidate: string) => ModelValidationResult) | null = null;
if (openrouterSelection.isOpenAiLikeRemoteProvider(selected.key)) {
const modelAuthMode = getProbeAuthMode(state.provider);
Expand Down
24 changes: 24 additions & 0 deletions src/lib/onboard/credential-navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import { describe, expect, it, vi } from "vitest";

import * as credentials from "../credentials/store";
import {
BACK_TO_SELECTION,
replaceNamedCredential,
returningToProviderSelection,
shouldReturnToProviderSelection,
} from "./credential-navigation";
Expand Down Expand Up @@ -50,4 +52,26 @@ describe("credential prompt navigation helpers", () => {

expect(logs).toEqual([" Returning to provider selection.", ""]);
});

it("keeps the prompt and accepts an empty optional credential (#7424)", async () => {
const prompt = vi
.spyOn(credentials, "readCredentialPrompt")
.mockResolvedValue({ kind: "credential", value: "" });
try {
await expect(
replaceNamedCredential({
envName: "NEMOCLAW_TEST_OPTIONAL_CREDENTIAL",
label: "API key (press Enter for no authentication)",
allowEmpty: true,
exitOnboardFromPrompt: () => process.exit(1),
}),
).resolves.toBe("");
expect(prompt).toHaveBeenCalledWith(
" API key (press Enter for no authentication): ",
expect.any(Function),
);
} finally {
vi.restoreAllMocks();
}
});
});
13 changes: 10 additions & 3 deletions src/lib/onboard/credential-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ export async function replaceNamedCredential({
label,
helpUrl = null,
validator = null,
allowEmpty = false,
exitOnboardFromPrompt,
}: {
envName: string;
label: string;
helpUrl?: string | null;
validator?: ((value: string) => string | null) | null;
allowEmpty?: boolean;
exitOnboardFromPrompt: () => never;
}): Promise<string | BackToSelection> {
if (helpUrl) {
Expand All @@ -85,6 +87,7 @@ export async function replaceNamedCredential({
const key = await readCredentialValue(` ${label}: `, exitOnboardFromPrompt);
if (isBackToSelection(key)) return key;
if (!key) {
if (allowEmpty) return "";
console.error(` ${label} is required.`);
continue;
}
Expand All @@ -107,12 +110,14 @@ export async function ensureNamedCredential({
label,
helpUrl = null,
validator = null,
allowEmpty = false,
exitOnboardFromPrompt,
}: {
envName: string | null;
label: string;
helpUrl?: string | null;
validator?: ((value: string) => string | null) | null;
allowEmpty?: boolean;
exitOnboardFromPrompt: () => never;
}): Promise<string | BackToSelection> {
if (!envName) {
Expand All @@ -128,7 +133,8 @@ export async function ensureNamedCredential({
}
console.error(validationError);
}
return replaceNamedCredential({ envName, label, helpUrl, validator, exitOnboardFromPrompt });
// biome-ignore format: keep optional credential forwarding together.
return replaceNamedCredential({ envName, label, helpUrl, validator, allowEmpty, exitOnboardFromPrompt });
}

export function createCredentialPromptHelpers(exitOnboardFromPrompt: () => never): {
Expand All @@ -144,6 +150,7 @@ export function createCredentialPromptHelpers(exitOnboardFromPrompt: () => never
label: string,
helpUrl?: string | null,
validator?: ((value: string) => string | null) | null,
allowEmpty?: boolean,
) => Promise<string | BackToSelection>;
shouldReturnToProviderSelection: (result: unknown) => boolean;
returningToProviderSelection: (result: unknown) => result is BackNavigationResult;
Expand All @@ -152,8 +159,8 @@ export function createCredentialPromptHelpers(exitOnboardFromPrompt: () => never
readValue: (question) => readCredentialValue(question, exitOnboardFromPrompt),
replaceNamedCredential: (envName, label, helpUrl = null, validator = null) =>
replaceNamedCredential({ envName, label, helpUrl, validator, exitOnboardFromPrompt }),
ensureNamedCredential: (envName, label, helpUrl = null, validator = null) =>
ensureNamedCredential({ envName, label, helpUrl, validator, exitOnboardFromPrompt }),
// biome-ignore format: keep optional credential forwarding together.
ensureNamedCredential: (envName, label, helpUrl = null, validator = null, allowEmpty = false) => ensureNamedCredential({ envName, label, helpUrl, validator, allowEmpty, exitOnboardFromPrompt }),
shouldReturnToProviderSelection: (result) =>
shouldReturnToProviderSelection(result, exitOnboardFromPrompt),
returningToProviderSelection: (result) =>
Expand Down
Loading
Loading