Invoke-Claude.ps1

[CmdletBinding(PositionalBinding=$false)]
param(
  [switch]$Login,
  [switch]$ForceMemoryRedirect,
  [Parameter(ValueFromRemainingArguments)]
  [string[]]$ClaudeArgs = @()
)

begin {
  . (Join-Path $PSScriptRoot 'Get-ClaudeExecutable.ps1')
  . (Join-Path $PSScriptRoot 'Test-CorePowerShell.ps1')
  . (Join-Path $PSScriptRoot 'Test-ProfileSetup.ps1')
  . (Join-Path $PSScriptRoot 'Test-IsModuleUpToDate.ps1')
  . (Join-Path $PSScriptRoot 'Enable-ClaudeMemoryRedirect.ps1')
  . (Join-Path $PSScriptRoot 'Test-AnthropicProxy.ps1')

  Test-CorePowerShell -ScriptPath $MyInvocation.MyCommand.Path `
      -BoundParameters $PSBoundParameters -RemainingArgs $ClaudeArgs `
      -Verbose:($VerbosePreference -eq 'Continue')

  Test-ProfileSetup

  # Enable PowerShell tool in all cases.
  $env:CLAUDE_CODE_USE_POWERSHELL_TOOL = "1"

  # Use $script:onWindows (not $script:isWindows) to avoid colliding with the read-only automatic variable $IsWindows.
  $script:onWindows = $IsWindows -or $env:OS -eq 'Windows_NT'

  # Platform-appropriate home directory.
  $script:homeDir = [System.Environment]::GetFolderPath('UserProfile')

  # Redirect ~/.claude/projects to $env:CLAUDE_MEMORY_DIR if set (one-time tip if not).
  Enable-ClaudeMemoryRedirect -HomeDir $script:homeDir -Force:$ForceMemoryRedirect

  # Expose the profile.ps1 path so Claude's PowerShell tool can source it with -NoProfile.
  # Any pwsh subprocess spawned from this session inherits PWRCLAUDE_PROFILE automatically.
  $env:PWRCLAUDE_PROFILE = Join-Path $PSScriptRoot 'profile.ps1'

  # Prefer ~/.local/bin native install first
  $sep = [System.IO.Path]::PathSeparator
  $claudePath = Join-Path $HOME '.local' 'bin'
  if (Test-Path $claudePath) {
    $env:Path = "$claudePath$sep$env:Path"
  }

  Test-AnthropicProxy -Login:$Login
}

process {
  # Welcome banner
  $pwrClaudeVersion = (Get-Module PwrClaude -ErrorAction SilentlyContinue)?.Version ?? '?'
  Write-Host "Welcome to PwrClaude v$pwrClaudeVersion" -ForegroundColor Cyan

  Test-IsModuleUpToDate

  # --- System prompt assembly ---
  # Helper: shorten paths under MyDocuments to ${MyDocuments}\...
  $script:myDocs = [Environment]::GetFolderPath('MyDocuments')
  function Format-DirectivePath([string]$p) {
      if ($p.StartsWith($script:myDocs, [System.StringComparison]::OrdinalIgnoreCase)) {
          '${MyDocuments}\' + $p.Substring($script:myDocs.Length).TrimStart('\')
      } else { $p }
  }

  # Start with the built-in PwrClaude directives.
  $systemPromptParts = [System.Collections.Generic.List[string]]::new()
  $injectedPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
  $claudeMd = Join-Path $PSScriptRoot 'CLAUDE.md'
  if (Test-Path $claudeMd) {
      $claudeMdFull = (Resolve-Path $claudeMd).Path
      $null = $injectedPaths.Add($claudeMdFull)
      Write-Host "Injecting PwrClaude directives: $(Format-DirectivePath $claudeMdFull)"
      $systemPromptParts.Add((Get-Content $claudeMd -Raw))
  }

  # Append user-specific directives from CLAUDE_USER_MD tree.
  # Set this variable to a directory; every CLAUDE.md found recursively inside it is loaded.
  # Uses @<path> file-reference syntax so Claude Code resolves the content itself
  # rather than inlining the full text into the system prompt argument.
  if ($env:CLAUDE_USER_MD) {
      try {
          $resolvedUserMd = Invoke-Expression "`"$env:CLAUDE_USER_MD`""
      } catch {
          Write-Host "WARNING: CLAUDE_USER_MD expression failed to evaluate: $env:CLAUDE_USER_MD" -ForegroundColor Yellow
          Write-Host " Error: $_" -ForegroundColor Yellow
          $resolvedUserMd = $null
      }
      if ($resolvedUserMd -and (Test-Path $resolvedUserMd)) {
          Write-Verbose "Resolved CLAUDE_USER_MD: $resolvedUserMd"
          $userMdFiles = Get-ChildItem -Path $resolvedUserMd -Filter 'CLAUDE.md' -Recurse -ErrorAction SilentlyContinue
          foreach ($f in $userMdFiles) {
              Write-Host "Injecting user directives: $(Format-DirectivePath (Resolve-Path $f.FullName).Path)"
              $systemPromptParts.Add("@$($f.FullName)")
          }
      } elseif ($resolvedUserMd) {
          Write-Host "WARNING: CLAUDE_USER_MD path does not exist: $resolvedUserMd" -ForegroundColor Yellow
          if ($resolvedUserMd -ne $env:CLAUDE_USER_MD) {
              Write-Host " (resolved from expression: $env:CLAUDE_USER_MD)" -ForegroundColor Yellow
          }
      }
  } else {
      $suppressFile = Join-Path $script:homeDir '.claude' 'suppress_user_md_hint'
      if (-not (Test-Path $suppressFile)) {
          Write-Host ""
          Write-Host "Tip: set `$env:CLAUDE_USER_MD to a directory containing CLAUDE.md files with your"
          Write-Host " personal directives. Claude will load and obey them automatically every session."
          Write-Host ""
          Write-Host " The value is evaluated as a PowerShell expression, so you can use variables:"
          Write-Host " `$env:CLAUDE_USER_MD = '`${env:USERPROFILE}\my-claude-directives'"
          Write-Host " `$env:CLAUDE_USER_MD = '`${env:OneDrive}\Claude\directives'"
          Write-Host ""
          Write-Host " Place CLAUDE.md files anywhere in that directory tree — all are loaded recursively."
          Write-Host ""
          $answer = Read-Host "Show this tip next time? [Y/n/never/e(xit)]"
          if ($answer -match '^[nN]ever$') {
              New-Item -ItemType File -Path $suppressFile -Force | Out-Null
              Write-Host "Got it — tip suppressed permanently. Set CLAUDE_USER_MD any time to enable personal directives."
          } elseif ($answer -match '^[nN]$') {
              New-Item -ItemType File -Path $suppressFile -Force | Out-Null
              Write-Host "Got it — tip suppressed. Set CLAUDE_USER_MD any time to enable personal directives."
          } elseif ($answer -match '^([eE]|[eE]xit)$') {
              return
          }
          Write-Host ""
      }
  }

  # Inject CLAUDE.md from loaded modules and from PSModulePath so installed-but-not-imported
  # modules (e.g. PwrStash) are included without requiring the user to load them first.
  # Skip oh-my-posh-core — its ModuleBase points to a source tree that happens to have a CLAUDE.md.
  $skipModuleNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
  $null = $skipModuleNames.Add('oh-my-posh-core')

  # First pass: loaded modules — inject CLAUDE.md only
  $loadedModules = Get-Module | Where-Object { -not $skipModuleNames.Contains($_.Name) }
  foreach ($mod in $loadedModules) {
      $directiveFile = Join-Path $mod.ModuleBase 'CLAUDE.md'
      if (Test-Path $directiveFile) {
          $resolved = (Resolve-Path $directiveFile).Path
          if ($injectedPaths.Add($resolved)) {
              Write-Host "Loading module directives from $($mod.Name): $(Format-DirectivePath $resolved)"
              $systemPromptParts.Add("@$directiveFile")
          }
      }
  }

  # Second pass: scan PSModulePath for CLAUDE.md in installed-but-not-loaded modules.
  # Only CLAUDE.md is checked here (README files in arbitrary modules may not be directives).
  $psModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator
  foreach ($modulePath in $psModulePaths) {
      if (-not (Test-Path $modulePath)) { continue }
      foreach ($modDir in (Get-ChildItem -LiteralPath $modulePath -Directory -ErrorAction SilentlyContinue)) {
          if ($skipModuleNames.Contains($modDir.Name)) { continue }
          # Module dirs may be flat (ModuleName/CLAUDE.md) or versioned (ModuleName/1.0.0/CLAUDE.md).
          # Use Select-Object -ExpandProperty rather than .FullName on the collected result so that
          # an empty pipeline contributes nothing (avoids null elements that break Join-Path).
          $candidateDirs = @($modDir.FullName) + @(
              Get-ChildItem -LiteralPath $modDir.FullName -Directory -ErrorAction SilentlyContinue |
                  Where-Object { $_.Name -match '^\d+\.\d+' } |
                  Select-Object -ExpandProperty FullName
          )
          foreach ($dir in $candidateDirs) {
              $claudeMdPath = Join-Path $dir 'CLAUDE.md'
              if (-not (Test-Path $claudeMdPath)) { continue }
              $resolved = (Resolve-Path $claudeMdPath).Path
              if ($injectedPaths.Add($resolved)) {
                  Write-Host "Loading module directives from $($modDir.Name) (PSModulePath): $(Format-DirectivePath $resolved)"
                  $systemPromptParts.Add("@$claudeMdPath")
              }
          }
      }
  }

  $systemPromptArgs = if ($systemPromptParts.Count -gt 0) {
      Write-Host "System prompt assembled: $($systemPromptParts.Count) directive(s)"
      @('--append-system-prompt', ($systemPromptParts -join "`n`n"))
  } else { @() }
  # --- End system prompt assembly ---

  $claudeExe = Get-ClaudeExecutable
  if (-not $claudeExe) {
      Write-Error "Could not locate the 'claude' executable. Install Claude Code."
      return
  }
  & $claudeExe @systemPromptArgs @ClaudeArgs
}