public/Initialize-NitrogenConfig.ps1
|
function Initialize-NitrogenConfig { <# .SYNOPSIS Creates a starter Nitrogen configuration file. .DESCRIPTION Writes a starter config.json for Nitrogen, including provider defaults and prompt mapping. Optionally configures SecretStore and prompts to set provider API keys using SecretManagement. .PARAMETER Scope Target config scope when -ConfigPath is not provided. Valid values: Global, Local. .PARAMETER ConfigPath Explicit path for the config file. Overrides -Scope. .PARAMETER ProfileName Profile name to create in the config. .PARAMETER Provider Default provider for the generated profile. .PARAMETER PromptKey Prompt key name to store under prompts. .PARAMETER PromptText Prompt text associated with PromptKey. .PARAMETER MaxItems Default max item limit to set in config defaults. .PARAMETER Force Overwrite existing config file. .PARAMETER ConfigureSecretVault Attempts to register SecretStore as default vault when available. .PARAMETER SetSecrets Interactively prompts for provider API keys and stores them by apiKeyRef. .EXAMPLE Initialize-NitrogenConfig .EXAMPLE Initialize-NitrogenConfig -Scope Local -ProfileName work -Provider Anthropic .EXAMPLE Initialize-NitrogenConfig -ConfigureSecretVault -SetSecrets #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)][ValidateSet('Global', 'Local')][string]$Scope = 'Global', [Parameter(Mandatory = $false)][string]$ConfigPath, [Parameter(Mandatory = $false)][string]$ProfileName = 'default', [Parameter(Mandatory = $false)][ValidateSet('OpenAI', 'Anthropic', 'AzureOpenAI', 'Google', 'Perplexity')][string]$Provider = 'OpenAI', [Parameter(Mandatory = $false)][string]$PromptKey = 'diagnostics.default', [Parameter(Mandatory = $false)][string]$PromptText = 'Analyze diagnostics and return concise, actionable findings.', [Parameter(Mandatory = $false)][ValidateRange(1, 5000)][int]$MaxItems = 100, [Parameter(Mandatory = $false)][switch]$Force, [Parameter(Mandatory = $false)][switch]$ConfigureSecretVault, [Parameter(Mandatory = $false)][switch]$SetSecrets ) if ([string]::IsNullOrWhiteSpace($ProfileName)) { throw "ProfileName cannot be empty." } if ([string]::IsNullOrWhiteSpace($PromptKey)) { throw "PromptKey cannot be empty." } $targetPath = if (-not [string]::IsNullOrWhiteSpace($ConfigPath)) { $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ConfigPath) } else { Get-NitrogenConfigPath -Scope $Scope } $targetDirectory = Split-Path -Path $targetPath -Parent if (-not (Test-Path -Path $targetDirectory -PathType Container)) { New-Item -Path $targetDirectory -ItemType Directory -Force | Out-Null } if ((Test-Path -Path $targetPath -PathType Leaf) -and -not $Force.IsPresent) { throw "Config file already exists at '$targetPath'. Use -Force to overwrite." } $providers = @{ OpenAI = @{ endpoint = 'https://api.openai.com/v1/responses' filesEndpoint = 'https://api.openai.com/v1/files' model = 'gpt-5' apiKeyRef = "nitrogen/$ProfileName/openai/api_key" } Anthropic = @{ endpoint = 'https://api.anthropic.com/v1/messages' filesEndpoint = 'https://api.anthropic.com/v1/files' model = 'claude-sonnet-5-5' apiVersion = '2023-06-01' maxTokens = 1024 apiKeyRef = "nitrogen/$ProfileName/anthropic/api_key" } AzureOpenAI = @{ endpoint = 'https://<resource>.openai.azure.com' deploymentId = 'gpt-5-prod' model = 'gpt-5' apiKeyRef = "nitrogen/$ProfileName/azureopenai/api_key" } Google = @{ endpoint = 'https://generativelanguage.googleapis.com/v1beta' filesEndpoint = 'https://generativelanguage.googleapis.com/v1beta/files' model = 'gemini-3.5-flash' apiKeyRef = "nitrogen/$ProfileName/google/api_key" } Perplexity = @{ endpoint = 'https://api.perplexity.ai/chat/completions' model = 'sonar-pro' apiKeyRef = "nitrogen/$ProfileName/perplexity/api_key" } } $config = @{ version = 1 defaults = @{ profile = $ProfileName provider = $Provider maxItems = $MaxItems } profiles = @{ $ProfileName = @{ provider = $Provider prompt = $PromptKey maxItems = $MaxItems providers = $providers } } prompts = @{ $PromptKey = $PromptText } } $config | ConvertTo-Json -Depth 15 | Set-Content -Path $targetPath -Encoding utf8 if (-not $IsWindows) { $chmod = Get-Command -Name chmod -ErrorAction SilentlyContinue if ($null -ne $chmod) { & $chmod.Source 600 $targetPath } } $vaultConfigured = $false if ($ConfigureSecretVault.IsPresent) { $registerSecretVault = Get-Command -Name Register-SecretVault -ErrorAction SilentlyContinue $getSecretVault = Get-Command -Name Get-SecretVault -ErrorAction SilentlyContinue if ($null -eq $registerSecretVault -or $null -eq $getSecretVault) { Write-Warning "SecretManagement commands are unavailable. Install Microsoft.PowerShell.SecretManagement and Microsoft.PowerShell.SecretStore." } else { $secretStoreVault = Get-SecretVault -Name 'SecretStore' -ErrorAction SilentlyContinue if ($null -eq $secretStoreVault) { Register-SecretVault -Name 'SecretStore' -ModuleName 'Microsoft.PowerShell.SecretStore' -DefaultVault -ErrorAction Stop } $vaultConfigured = $true } } $secretsSet = 0 if ($SetSecrets.IsPresent) { $setSecret = Get-Command -Name Set-Secret -ErrorAction SilentlyContinue if ($null -eq $setSecret) { throw "Set-Secret is unavailable. Install Microsoft.PowerShell.SecretManagement and register a vault before using -SetSecrets." } foreach ($providerName in @('OpenAI', 'Anthropic', 'AzureOpenAI', 'Google', 'Perplexity')) { $providerConfig = $providers[$providerName] $secretName = $providerConfig.apiKeyRef $secureValue = Read-Host "Enter API key for $providerName (leave blank to skip)" -AsSecureString $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureValue) try { $plainValue = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) } if ([string]::IsNullOrWhiteSpace($plainValue)) { continue } Set-Secret -Name $secretName -Secret $secureValue -ErrorAction Stop $secretsSet++ } } [pscustomobject]@{ ConfigPath = $targetPath Profile = $ProfileName DefaultProvider = $Provider SecretVault = if ($vaultConfigured) { 'Configured' } else { 'NotChanged' } SecretsStored = $secretsSet } } |