/** * `ocx provider` subcommand — non-interactive provider management. * * Subcommands: * list List configured and available registry providers * add Add a provider from the registry or with custom flags * remove Remove a configured provider * show Show provider config details (secrets masked) * set-default Change the default provider */ import { hasOwnProvider, isValidProviderName, loadConfig, readConfigAdmissionSnapshot, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config"; import { readCliConfigForMutation } from "./config-mutation"; import { getConfigDir, withConfigDirHardeningSettled } from "../config/paths"; import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation"; import { hasHelpFlag, printSubcommandUsage } from "./help"; import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry"; import { providerConfigSeed } from "../providers/derive"; import { assertAnthropicInstanceLoginConfig } from "../oauth/store-anthropic-instance"; import { dropProviderCustomModels } from "../providers/provider-id-rewrite"; import { cleanupRemovedGcpCredentials, commitProviderGcpCredentialRemoval, providerGcpCredentialMarkers } from "../providers/gcp-credential-cleanup"; import { providerCredentialInputError } from "../providers/api-keys"; import type { OcxProviderConfig } from "../types"; import { findLiveProxy } from "../server/proxy-liveness"; import { syncModelsToCodex } from "../codex/sync"; import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match"; import { modelSelectionGuidance, modelSelectionNextSteps } from "./model-selection-guidance"; import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers-destination"; import { providerRelativeSendPathConfigError } from "../config/provider-relative-send-path"; import type { RuntimeApiDeps } from "./runtime-api"; import { redactSecretArgs } from "./secret-args"; import { projectLocalSyncResult, type LocalSyncResult } from "./local-sync-result"; import { providerManagementConfigError } from "../server/auth-cors"; import { readClientConnectionState, type ClientConnectionState } from "../client/state"; import { SENSITIVE_KEY_PATTERN } from "../lib/redact"; export interface ProviderCommandDeps extends RuntimeApiDeps { syncModels?: typeof syncModelsToCodex; } // --------------------------------------------------------------------------- // Arg helpers // --------------------------------------------------------------------------- function consumeFlag(args: string[], flag: string): boolean { const idx = args.indexOf(flag); if (idx === -1) return false; args.splice(idx, 1); return true; } function consumeFlagValue(args: string[], flag: string): string | undefined { const idx = args.indexOf(flag); if (idx === -1 || idx + 1 >= args.length) return undefined; const value = args[idx + 1]; args.splice(idx, 2); return value; } /** Reject any leftover args (unknown flags or trailing values). */ function rejectUnknownArgs(args: string[], usage: string): void { if (args.length === 0) return; const shown = redactSecretArgs(args); // Flags plus redaction markers only: a stray positional may be a credential operand. const unknown = shown.filter(a => a.startsWith("-") || a === ""); if (unknown.length > 0) { console.error(`Unknown flag(s): ${unknown.join(", ")}`); } else { console.error(`Unexpected argument(s): ${shown.join(", ")}`); } console.error(usage); process.exit(1); } function maskSecret(value: string): string { if (value.length <= 8) return "****"; return `${value.slice(0, 4)}****${value.slice(-4)}`; } // --------------------------------------------------------------------------- // Validation helper (F1 fix: validate before saveConfig) // --------------------------------------------------------------------------- function validateAndSave(config: ReturnType): void { if (!config.providers || Object.keys(config.providers).length === 0) { console.error("Error: config would have no providers. Aborting."); process.exit(1); } if (!hasOwnProvider(config.providers, config.defaultProvider)) { console.error(`Error: defaultProvider "${config.defaultProvider}" does not exist in providers. Aborting.`); process.exit(1); } const result = validateConfigCandidate(config); if (!result.ok) { console.error(`Error: ${result.error}`); if (result.error.includes("set allowPrivateNetwork:true")) { console.error("For an intentionally local provider, add --allow-private-network."); } else { console.error("Nothing was saved. Fix the setting named above; if this command did not set it, run ocx config validate and repair it with ocx config set/unset."); } process.exit(1); } saveConfig(config); } // --------------------------------------------------------------------------- // provider list // --------------------------------------------------------------------------- /** * Does this device's own provider config serve requests? A device linked to a Home — a Remote * Link Child or a Hub-mode client — routes through the Home, so its local rows are shadowed. * A standalone device and a Hub serve their own rows. An unreadable or inconsistent client * state is not a link, the same reading `readLinkChildContext` takes. */ export function localProvidersActive(read: () => ClientConnectionState = readClientConnectionState): boolean { try { return read().kind !== "connected"; } catch { return true; } } const SENSITIVE_QUERY_KEY = /key|token|secret|password|passwd|signature|^sig$|auth|credential/i; /** * The baseUrl as `provider list` prints it. Validation already rejects embedded credentials; * this also covers a hand-edited config, dropping userinfo and masking credential-shaped query * values while keeping ordinary parameters such as `api-version`. */ export function providerListBaseUrl(baseUrl: string): string { let parsed: URL; try { parsed = new URL(baseUrl); } catch { return baseUrl; } let changed = false; if (parsed.username || parsed.password) { parsed.username = ""; parsed.password = ""; changed = true; } for (const key of [...new Set(parsed.searchParams.keys())]) { if (SENSITIVE_KEY_PATTERN.test(key) || SENSITIVE_QUERY_KEY.test(key)) { parsed.searchParams.set(key, "redacted"); changed = true; } } return changed ? parsed.toString() : baseUrl; } function handleList(args: string[], active: boolean = localProvidersActive()): void { const wantsJson = consumeFlag(args, "--json"); const wantsJsonl = consumeFlag(args, "--jsonl"); rejectUnknownArgs(args, "Usage: ocx provider list [--json|--jsonl]"); if (wantsJson && wantsJsonl) { console.error("Use only one of --json or --jsonl."); process.exit(1); } const config = loadConfig(); const configured = Object.keys(config.providers); const entries = configured.map(name => { const prov = config.providers[name]; const registryEntry = getProviderRegistryEntry(name); return { name, adapter: prov.adapter, baseUrl: providerListBaseUrl(prov.baseUrl), authMode: prov.authMode ?? "key", defaultModel: prov.defaultModel ?? null, isDefault: name === config.defaultProvider, source: registryEntry ? "registry" : "custom", models: prov.models ?? [], active, }; }); if (wantsJsonl) { for (const entry of entries) console.log(JSON.stringify(entry)); return; } if (wantsJson) { console.log(JSON.stringify({ configured: entries, registryCount: PROVIDER_REGISTRY.length }, null, 2)); return; } console.log("Configured providers:\n"); for (const name of configured) { const prov = config.providers[name]; const isDefault = name === config.defaultProvider ? " (default)" : ""; const registryEntry = getProviderRegistryEntry(name); const source = registryEntry ? "" : " [custom]"; const model = prov.defaultModel ? ` model=${prov.defaultModel}` : ""; console.log(` ${name}${isDefault}${source} adapter=${prov.adapter}${model}`); } const available = PROVIDER_REGISTRY.filter(e => !e.deprecated && !configured.includes(e.id)); if (available.length > 0) { console.log(`\nAvailable from registry (${available.length}):\n`); for (const entry of available) { const auth = entry.authKind === "forward" ? "chatgpt-login" : entry.authKind; console.log(` ${entry.id.padEnd(24)} ${entry.label} (${auth})`); } console.log(`\nAdd with: ocx provider add [--api-key ]`); } } // --------------------------------------------------------------------------- // provider add // --------------------------------------------------------------------------- const ADD_USAGE = "Usage: ocx provider add [--adapter ] [--base-url ] [--responses-path ] [--auth-mode ] [--api-key ] [--api-key-transport ] [--default-model ] [--model --text-only] [--google-tool-schema-policy ] [--allow-private-network] [--set-default] [--force] [--json] [--sync | --live]"; async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise { const name = args[0]; if (!name || name.startsWith("-")) { console.error(ADD_USAGE); process.exit(1); } if (!isValidProviderName(name)) { console.error(`Invalid provider name: "${name}". Use letters, numbers, dots, underscores, or hyphens.`); process.exit(1); } const restArgs = args.slice(1); const force = consumeFlag(restArgs, "--force"); const setDefault = consumeFlag(restArgs, "--set-default"); const wantsJson = consumeFlag(restArgs, "--json"); const wantsSync = consumeFlag(restArgs, "--sync"); const allowPrivateNetwork = consumeFlag(restArgs, "--allow-private-network"); const apiKey = consumeFlagValue(restArgs, "--api-key"); const apiKeyTransport = consumeFlagValue(restArgs, "--api-key-transport"); const adapter = consumeFlagValue(restArgs, "--adapter"); const baseUrl = consumeFlagValue(restArgs, "--base-url"); const defaultModel = consumeFlagValue(restArgs, "--default-model"); const responsesPath = consumeFlagValue(restArgs, "--responses-path"); const authMode = consumeFlagValue(restArgs, "--auth-mode"); const googleToolSchemaPolicy = consumeFlagValue(restArgs, "--google-tool-schema-policy"); const textOnly = consumeFlag(restArgs, "--text-only"); const capabilityModel = consumeFlagValue(restArgs, "--model"); rejectUnknownArgs(restArgs, ADD_USAGE); if (capabilityModel !== undefined && !textOnly) { console.error("Error: --model requires --text-only for provider add."); process.exit(1); } const pathError = providerRelativeSendPathConfigError("responsesPath", responsesPath); if (pathError || (authMode !== undefined && !["key", "forward", "oauth", "local"].includes(authMode))) { console.error(pathError ? `Error: ${pathError}.` : "Error: --auth-mode must be key, forward, oauth, or local."); process.exit(1); } const { initializeProviderModelSelection } = await import("../providers/initial-model-selection"); const admitted = await withConfigDirHardeningSettled(getConfigDir(), () => { const config = readCliConfigForMutation(); const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, name); if (namespaceCollision) { console.error(`Error: ${namespaceCollision}.`); process.exit(1); } if (hasOwnProvider(config.providers, name) && !force) { console.error(`Provider "${name}" already exists. Use --force to overwrite.`); process.exit(1); } let provConfig: OcxProviderConfig; const registryEntry = getProviderRegistryEntry(name); if (registryEntry) { if (registryEntry.deprecated) console.warn(`Warning: provider "${name}" is deprecated: ${registryEntry.deprecated}`); provConfig = providerConfigSeed(registryEntry); if (apiKey) { const candidateError = providerCredentialInputError({ apiKey }); if (candidateError) { console.error(`Error: ${candidateError}`); process.exitCode = 2; return; } if ((authMode ?? registryEntry.authKind) === "forward") { console.warn(`Warning: provider "${name}" uses ChatGPT login (forward auth); --api-key is ignored.`); } else if ((authMode ?? registryEntry.authKind) === "oauth") { console.warn(`Warning: provider "${name}" uses OAuth auth; --api-key is ignored. Run: ocx login ${name}`); } else { provConfig.apiKey = apiKey; } } if (defaultModel) provConfig.defaultModel = defaultModel; if (adapter) provConfig.adapter = adapter; if (baseUrl) provConfig.baseUrl = baseUrl; } else { if (!adapter || !baseUrl) { console.error(`Provider "${name}" is not in the registry. --adapter and --base-url are required.`); console.error("Usage: ocx provider add --adapter --base-url [--api-key ]"); process.exit(1); } provConfig = { adapter, baseUrl, ...(apiKey ? { apiKey } : {}), ...(defaultModel ? { defaultModel } : {}), }; } if (responsesPath !== undefined) provConfig.responsesPath = responsesPath; const credentialError = providerCredentialInputError(provConfig); if (credentialError) { console.error(`Error: ${credentialError}`); process.exitCode = 2; return; } if (authMode !== undefined) provConfig.authMode = authMode as OcxProviderConfig["authMode"]; if (name === "anthropic2") { if (provConfig.adapter !== "anthropic" || provConfig.authMode !== "oauth") delete provConfig.anthropicOAuthInstance; if (provConfig.anthropicOAuthInstance) { try { assertAnthropicInstanceLoginConfig(config, name); } catch { console.error("Error: cannot add Pool 2 over an existing custom or unreadable provider configuration; resolve it first."); process.exitCode = 2; return; } } } if (name === "openai" && (authMode !== undefined || responsesPath !== undefined) && !isCanonicalOpenAiForwardProvider(provConfig)) { console.error("Error: Canonical OpenAI must keep its built-in forward destination and authentication. Use a separate provider name for a custom endpoint."); process.exitCode = 2; return; } if (apiKeyTransport !== undefined) { if (apiKeyTransport !== "x-api-key" && apiKeyTransport !== "bearer") { console.error('Error: --api-key-transport must be "x-api-key" or "bearer".'); process.exit(1); } const transportError = apiKeyTransportConfigError({ ...provConfig, apiKeyTransport }); if (transportError) { console.error(`Error: ${transportError}.`); process.exit(1); } provConfig.apiKeyTransport = apiKeyTransport; } if (googleToolSchemaPolicy !== undefined) { if (googleToolSchemaPolicy !== "compatible" && googleToolSchemaPolicy !== "reject-lossy") { console.error('Error: --google-tool-schema-policy must be "compatible" or "reject-lossy".'); process.exit(1); } if (provConfig.adapter !== "google") { console.error("Error: --google-tool-schema-policy requires the google adapter."); process.exit(1); } provConfig.googleToolSchemaPolicy = googleToolSchemaPolicy; } const existingProvider = config.providers[name]; if (existingProvider?.modelCapabilities !== undefined && provConfig.modelCapabilities === undefined) { provConfig.modelCapabilities = structuredClone(existingProvider.modelCapabilities); } if (existingProvider?.modelContextTiers !== undefined && provConfig.modelContextTiers === undefined) { provConfig.modelContextTiers = structuredClone(existingProvider.modelContextTiers); } if (textOnly) { const modelId = capabilityModel ?? defaultModel ?? provConfig.defaultModel; if (!modelId) { console.error("Error: --text-only requires --model or a default model."); process.exit(1); } const declaration = { [modelId]: { inputModalities: ["text"] } }; const error = modelCapabilitiesConfigError(declaration); if (error) { console.error(`Error: ${error}.`); process.exit(1); } provConfig.modelCapabilities = mergeModelCapabilities(provConfig.modelCapabilities, declaration); } // A --force overwrite rotates the key/endpoint but must not drop a // user-configured price overlay (same rule as the /api/providers path and // the login paths); there is no explicit clear/replace flag yet. if (existingProvider?.modelCosts !== undefined && provConfig.modelCosts === undefined) { provConfig.modelCosts = existingProvider.modelCosts; } if (allowPrivateNetwork) provConfig.allowPrivateNetwork = true; // New auth/path overrides use the management owner's completed-row contract. // Validate overrides before registration state changes; the full candidate is // validated again by validateAndSave for every local save. if ((authMode !== undefined || responsesPath !== undefined) && providerManagementConfigError(name, provConfig)) { console.error("Error: Invalid provider configuration. Authentication, destination and provider options must satisfy the provider's management rules."); process.exitCode = 2; return; } let markerCollision = false; let removedCredentials: string[] = []; withConfigMutationLockSync(() => { if (provConfig.anthropicOAuthInstance) { try { assertAnthropicInstanceLoginConfig(config, name); } catch { markerCollision = true; return; } } initializeProviderModelSelection(name, provConfig, existingProvider, config); const durable = readConfigAdmissionSnapshot(); const durableProvider = durable.kind === "read" && durable.diagnostics.source === "file" && durable.diagnostics.error === null ? durable.diagnostics.config.providers[name] : undefined; removedCredentials = [config.providers[name], durableProvider].flatMap(provider => provider ? providerGcpCredentialMarkers(provider) : []); config.providers[name] = provConfig; if (setDefault) config.defaultProvider = name; validateAndSave(config); }); if (!markerCollision) cleanupRemovedGcpCredentials(removedCredentials); return { config, provConfig, registryEntry, markerCollision }; }); if (!admitted) return; const { config, provConfig, registryEntry, markerCollision } = admitted; if (markerCollision) { console.error("Error: provider configuration changed while adding Pool 2; resolve the ownership collision first."); process.exitCode = 2; return; } let sync: LocalSyncResult | undefined; if (wantsSync) { try { const live = await (deps.findLiveProxy ?? findLiveProxy)(); if (!live) sync = { status: "not-running", ok: false }; else { const result = await (deps.syncModels ?? syncModelsToCodex)(live.port, config, null); sync = projectLocalSyncResult(result); } } catch { // Dependency errors can contain keys, paths or request details. Keep a fixed outcome. sync = { status: "failed", ok: false }; } if (!sync.ok || sync.status === "refused") process.exitCode = 1; } const synced = sync?.status === "applied" && sync.ok; if (wantsJson) { console.log(JSON.stringify({ action: "added", modelSelection: modelSelectionNextSteps(name), provider: name, adapter: provConfig.adapter, baseUrl: provConfig.baseUrl, defaultModel: provConfig.defaultModel ?? null, isDefault: config.defaultProvider === name, source: registryEntry ? "registry" : "custom", needsSync: !synced, ...(sync ? { sync } : {}), }, null, 2)); return; } if (sync?.warning) console.log(` Warning: ${sync.warning}`); const registryLabel = registryEntry ? ` (${registryEntry.label})` : ""; console.log(`✅ Provider "${name}"${registryLabel} added.`); for (const line of modelSelectionGuidance(name)) console.log(line); if (setDefault) console.log(` Set as default provider.`); if (registryEntry?.authKind === "oauth") { console.log(` Authenticate with: ocx login ${name}`); } if (registryEntry?.authKind === "key" && !apiKey) { const envKey = `${name.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`; console.log(` Set API key with: ocx provider add ${name} --api-key --force`); console.log(` Or set env var: ${envKey}`); } if (synced) console.log(" Models synced to Codex."); else { if (sync) { console.log(sync.configApplied && !sync.ok ? " Provider saved; sync remains incomplete despite config injection." : ` Provider saved; client sync outcome: ${sync.status}.`); if (sync.catalog && !sync.catalog.converged) console.log(" Model catalog did not converge; inspect the target before retrying."); } console.log(" Apply to Codex: ocx sync"); } } // --------------------------------------------------------------------------- // provider remove // --------------------------------------------------------------------------- function handleRemove(args: string[]): void { const restArgs = [...args]; const wantsJson = consumeFlag(restArgs, "--json"); const name = restArgs[0]; if (!name || name.startsWith("-")) { console.error("Usage: ocx provider remove [--json]"); process.exit(1); } rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider remove [--json]"); const config = readCliConfigForMutation(); if (!hasOwnProvider(config.providers, name)) { console.error(`Provider "${name}" is not configured.`); process.exit(1); } if (name === config.defaultProvider) { console.error(`Cannot remove "${name}" — it is the default provider. Change the default first: ocx provider set-default `); process.exit(1); } if (Object.keys(config.providers).length <= 1) { console.error("Cannot remove the last provider."); process.exit(1); } const dependentCombos = Object.entries(config.combos ?? {}) .filter(([, combo]) => combo.targets.some(target => target.provider === name)) .map(([id]) => id) .sort(); if (dependentCombos.length > 0) { console.error(`Cannot remove "${name}" — combo(s) depend on it: ${dependentCombos.join(", ")}`); process.exit(1); } const droppedCustomModels = commitProviderGcpCredentialRemoval(config, name, () => { delete config.providers[name]; const dropped = dropProviderCustomModels(config, name); validateAndSave(config); return dropped; }); if (wantsJson) { console.log(JSON.stringify({ action: "removed", provider: name, remainingProviders: Object.keys(config.providers), defaultProvider: config.defaultProvider, needsSync: true, ...(droppedCustomModels > 0 ? { droppedCustomModels } : {}), }, null, 2)); return; } console.log(`✅ Provider "${name}" removed.`); if (droppedCustomModels > 0) { const plural = droppedCustomModels === 1 ? "model" : "models"; console.log(` Also removed ${droppedCustomModels} custom ${plural} that belonged to it.`); } } // --------------------------------------------------------------------------- // provider show // --------------------------------------------------------------------------- function handleShow(args: string[]): void { const restArgs = [...args]; const wantsJson = consumeFlag(restArgs, "--json"); const name = restArgs[0]; if (!name || name.startsWith("-")) { console.error("Usage: ocx provider show [--json]"); process.exit(1); } rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider show [--json]"); const config = loadConfig(); if (!hasOwnProvider(config.providers, name)) { console.error(`Provider "${name}" is not configured.`); process.exit(1); } const prov = config.providers[name]; const display = { ...prov, ...(prov.modelCosts !== undefined ? { modelCosts: sanitizeModelCostsForDisplay(prov.modelCosts) } : {}), ...(prov.apiKey ? { apiKey: maskSecret(prov.apiKey) } : {}), ...(prov.apiKeyPool ? { apiKeyPool: prov.apiKeyPool.map(e => ({ ...e, key: maskSecret(e.key) })) } : {}), }; if (wantsJson) { console.log(JSON.stringify({ name, isDefault: name === config.defaultProvider, ...display }, null, 2)); return; } console.log(`Provider: ${name}${name === config.defaultProvider ? " (default)" : ""}`); console.log(` adapter: ${display.adapter}`); console.log(` baseUrl: ${display.baseUrl}`); if (display.authMode) console.log(` authMode: ${display.authMode}`); if (display.apiKey) console.log(` apiKey: ${display.apiKey}`); if (display.defaultModel) console.log(` defaultModel: ${display.defaultModel}`); if (display.models?.length) console.log(` models: ${display.models.join(", ")}`); } // --------------------------------------------------------------------------- // provider set-default // --------------------------------------------------------------------------- function handleSetDefault(args: string[]): void { const restArgs = [...args]; const wantsJson = consumeFlag(restArgs, "--json"); const name = restArgs[0]; if (!name || name.startsWith("-")) { console.error("Usage: ocx provider set-default [--json]"); process.exit(1); } rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider set-default [--json]"); const config = readCliConfigForMutation(); if (!hasOwnProvider(config.providers, name)) { console.error(`Provider "${name}" is not configured. Add it first: ocx provider add ${name}`); process.exit(1); } if (config.defaultProvider === name) { if (wantsJson) { console.log(JSON.stringify({ action: "noop", provider: name, defaultProvider: name, needsSync: false }, null, 2)); } else { console.log(`"${name}" is already the default provider.`); } return; } config.defaultProvider = name; validateAndSave(config); if (wantsJson) { console.log(JSON.stringify({ action: "set-default", provider: name, defaultProvider: name, needsSync: true }, null, 2)); return; } console.log(`✅ Default provider set to "${name}".`); } // --------------------------------------------------------------------------- // Router (F2 fix: handle help flags internally, like service/codex-shim) // --------------------------------------------------------------------------- export async function handleProviderCommand(args: string[], deps: ProviderCommandDeps = {}): Promise { const sub = args[0]; if (!sub || sub === "help" || hasHelpFlag(args)) { printSubcommandUsage("provider"); process.exit(0); } const subArgs = args.slice(1); const liveArgs = subArgs.filter(arg => arg === "--live" || arg.startsWith("--live=")); if (liveArgs.length) { if (liveArgs.length !== 1 || liveArgs[0] !== "--live" || !["add", "remove", "set-default"].includes(sub)) { console.error("Error: --live is a single boolean flag for provider add, remove, or set-default."); process.exitCode = 2; return; } subArgs.splice(subArgs.indexOf("--live"), 1); const { handleProviderLifecycleRuntimeCommand } = await import("./provider-lifecycle-runtime"); process.exitCode = await handleProviderLifecycleRuntimeCommand(sub as "add" | "remove" | "set-default", subArgs, deps); return; } if (sub === "snapshot" || sub === "apply") { const { handleProviderBatchCommand } = await import("./provider-batch"); process.exitCode = await handleProviderBatchCommand(sub, subArgs, deps); return; } if (sub === "pacing") { const { handleProviderPacingCommand } = await import("./provider-settings"); process.exitCode = await handleProviderPacingCommand(subArgs, deps); return; } if (sub === "import-cc-switch") { const { cmdProviderCcSwitchImport } = await import("./provider-cc-switch-import"); process.exitCode = await cmdProviderCcSwitchImport(subArgs); return; } // #6957: a link Child's local providers are inactive; changing them is refused like any other // provider change, which belongs to the Home. if (sub === "add" || sub === "remove" || sub === "set-default") { const refusal = (await import("./link-child")).linkChildProviderChangeRefusal(`ocx provider ${sub}`); if (refusal) { console.error(refusal); process.exitCode = 1; return; } } switch (sub) { case "list": await (await import("./link-child")).printLinkChildLocalNotice("The providers"); handleList(subArgs); break; case "add": await handleAdd(subArgs, deps); break; case "remove": await withConfigDirHardeningSettled(getConfigDir(), () => handleRemove(subArgs)); break; case "show": await (await import("./link-child")).printLinkChildLocalNotice("The provider settings"); handleShow(subArgs); break; case "set-default": await withConfigDirHardeningSettled(getConfigDir(), () => handleSetDefault(subArgs)); break; default: { const { handleProviderRuntimeCommand } = await import("./provider-runtime"); const code = await handleProviderRuntimeCommand(sub, subArgs, deps); if (code !== null) { process.exitCode = code; break; } console.error(`Unknown provider subcommand: ${redactSecretArgs([sub ?? ""])[0]}`); printSubcommandUsage("provider", undefined, { write: console.error }); process.exit(1); } } }