@@ -16,6 +16,145 @@ if ($env:RUNNING_IN_DOCKER -ne "true")
1616 exit 1
1717}
1818
19+ # --- Output sanitization (matches ClaudeCodeHelper.SanitizeOutput) ---
20+ function Sanitize-ClaudeOutput {
21+ param ([string ]$Text )
22+
23+ if ([string ]::IsNullOrEmpty($Text )) { return " " }
24+
25+ # Strip ANSI escape sequences
26+ $stripped = $Text -replace ' \x1b\[[0-9;]*m' , ' ' -replace ' \[\d+(?:;\d+)*m' , ' '
27+
28+ $sb = [System.Text.StringBuilder ]::new($stripped.Length )
29+ foreach ($c in $stripped.ToCharArray ()) {
30+ $code = [int ]$c
31+ if (($code -ge 32 -and $code -le 126 ) -or $c -eq " `n " -or $c -eq " `r " -or $c -eq " `t " ) {
32+ [void ]$sb.Append ($c )
33+ }
34+ elseif ([char ]::IsWhiteSpace($c )) {
35+ [void ]$sb.Append (' ' )
36+ }
37+ # Skip all other characters (including extended Unicode)
38+ }
39+ return $sb.ToString ()
40+ }
41+
42+ # Tools whose output is silenced in the monitoring display
43+ $script :SilentTools = @ (' Read' , ' Glob' , ' Grep' )
44+ $script :SilentToolIds = @ {}
45+
46+ # --- JSON stream line parser (matches ClaudeCodeHelper.TranslateJsonToHumanReadable) ---
47+ function ConvertFrom-ClaudeJsonLine {
48+ param ([string ]$Line )
49+
50+ if ([string ]::IsNullOrWhiteSpace($Line )) { return }
51+
52+ try {
53+ $json = $Line | ConvertFrom-Json
54+ } catch {
55+ Write-Host (Sanitize- ClaudeOutput $Line )
56+ return
57+ }
58+
59+ switch ($json.type ) {
60+ ' system' {
61+ if ($json.subtype -eq ' init' ) {
62+ $model = if ($json.model ) { $json.model } else { " unknown" }
63+ Write-Host (Sanitize- ClaudeOutput " [Claude Code initialized - model: $model ]" ) - ForegroundColor Green
64+ }
65+ }
66+ ' assistant' {
67+ if ($json.message -and $json.message.content ) {
68+ foreach ($block in $json.message.content ) {
69+ if ($block.type -eq ' text' ) {
70+ Write-Host " "
71+ Write-Host (Sanitize- ClaudeOutput $block.text ) - ForegroundColor Cyan
72+ }
73+ elseif ($block.type -eq ' tool_use' ) {
74+ $toolName = if ($block.name ) { $block.name } else { " unknown" }
75+ if ($script :SilentTools -contains $toolName ) {
76+ if ($block.id ) { $script :SilentToolIds [$block.id ] = $true }
77+ continue
78+ }
79+ # Silence Bash calls for read-only commands (ls, grep)
80+ if ($toolName -eq ' Bash' -and $block.input.command -match ' ^\s*(ls|grep|find)\b' ) {
81+ if ($block.id ) { $script :SilentToolIds [$block.id ] = $true }
82+ continue
83+ }
84+ Write-Host " "
85+ Write-Host (Sanitize- ClaudeOutput " [Tool: $toolName ]" ) - ForegroundColor Yellow
86+ if ($block.input ) {
87+ # Display the most identifying property from the input
88+ $displayProps = @ (
89+ @ { Key = ' file_path' ; Label = ' File' }
90+ @ { Key = ' command' ; Label = ' $' }
91+ @ { Key = ' pattern' ; Label = ' Pattern' }
92+ @ { Key = ' query' ; Label = ' Query' }
93+ @ { Key = ' url' ; Label = ' URL' }
94+ @ { Key = ' skill' ; Label = ' Skill' }
95+ @ { Key = ' prompt' ; Label = ' Prompt' }
96+ @ { Key = ' description' ; Label = ' Task' }
97+ )
98+ $shown = $false
99+ foreach ($dp in $displayProps ) {
100+ $val = $block.input .($dp.Key )
101+ if ($val ) {
102+ $truncated = if ($val.Length -gt 120 ) { $val.Substring (0 , 120 ) + " ..." } else { $val }
103+ Write-Host (Sanitize- ClaudeOutput " $ ( $dp.Label ) : $truncated " ) - ForegroundColor Gray
104+ $shown = $true
105+ break
106+ }
107+ }
108+ if (-not $shown ) {
109+ # Fallback: show the property names so the user at least sees what was passed
110+ $keys = ($block.input.PSObject.Properties | Select-Object - ExpandProperty Name) -join ' , '
111+ if ($keys ) {
112+ Write-Host (Sanitize- ClaudeOutput " [$keys ]" ) - ForegroundColor Gray
113+ }
114+ }
115+ }
116+ }
117+ }
118+ }
119+ }
120+ ' user' {
121+ if ($json.message -and $json.message.content ) {
122+ foreach ($block in $json.message.content ) {
123+ if ($block.type -eq ' tool_result' ) {
124+ if ($block.tool_use_id -and $script :SilentToolIds.ContainsKey ($block.tool_use_id )) {
125+ $script :SilentToolIds.Remove ($block.tool_use_id )
126+ continue
127+ }
128+ $content = if ($block.content ) { $block.content } else { " " }
129+ # Strip system reminders and tool use markup
130+ $content = $content -replace ' (?s)<system-reminder>.*?</system-reminder>' , ' '
131+ $closingTag = ' </function_calls>'
132+ $content = $content -replace " (?s)<function_calls>.*?$closingTag " , ' '
133+ $sanitized = Sanitize- ClaudeOutput $content
134+ $lines = $sanitized -split " `n "
135+ $maxLines = 5
136+ $color = if ($block.is_error ) { " Red" } else { " DarkGray" }
137+ $prefix = if ($block.is_error ) { " [ERROR] " } else { " ->" }
138+ for ($i = 0 ; $i -lt [Math ]::Min($lines.Count , $maxLines ); $i ++ ) {
139+ Write-Host " $prefix $ ( $lines [$i ]) " - ForegroundColor $color
140+ }
141+ if ($lines.Count -gt $maxLines ) {
142+ Write-Host " ... ($ ( $lines.Count - $maxLines ) more lines)" - ForegroundColor $color
143+ }
144+ }
145+ }
146+ }
147+ }
148+ ' result' {
149+ Write-Host (Sanitize- ClaudeOutput " [Session completed]" ) - ForegroundColor Green
150+ }
151+ ' error' {
152+ $msg = if ($json.error.message ) { $json.error.message } elseif ($json.error ) { $json.error } else { " Unknown error" }
153+ Write-Host (Sanitize- ClaudeOutput " [ERROR] $msg " ) - ForegroundColor Red
154+ }
155+ }
156+ }
157+
19158# Configure MCP approval server if port is specified
20159$mcpConfigArg = " "
21160if ($McpPort -gt 0 )
@@ -59,9 +198,36 @@ if ($Prompt)
59198 $Prompt | Set-Content - Path $promptFile - Encoding UTF8 - NoNewline
60199 Write-Host " Running Claude with prompt from file: $promptFile " - ForegroundColor Cyan
61200
62- # Use stdin redirection to pass the prompt, avoiding command line length issues
63- $cmd = " Get-Content -Path '$promptFile ' -Raw | claude --model $Model --dangerously-skip-permissions $mcpConfigArg "
64- $exitCode = Invoke-Expression $cmd
201+ # Stream JSON output for human-readable real-time monitoring
202+ $processArgs = " -p --output-format stream-json --verbose --model $Model --dangerously-skip-permissions $mcpConfigArg "
203+
204+ $psi = [System.Diagnostics.ProcessStartInfo ]::new()
205+ $psi.FileName = " claude.cmd"
206+ $psi.Arguments = $processArgs
207+ $psi.RedirectStandardInput = $true
208+ $psi.RedirectStandardOutput = $true
209+ $psi.RedirectStandardError = $true
210+ $psi.UseShellExecute = $false
211+ $psi.CreateNoWindow = $true
212+
213+ $process = [System.Diagnostics.Process ]::Start($psi )
214+
215+ # Send prompt via stdin
216+ $promptContent = Get-Content - Path $promptFile - Raw
217+ $process.StandardInput.Write ($promptContent )
218+ $process.StandardInput.Close ()
219+
220+ # Read and parse stdout line by line (real-time streaming)
221+ while ($null -ne ($line = $process.StandardOutput.ReadLine ())) {
222+ ConvertFrom-ClaudeJsonLine - Line $line
223+ }
224+
225+ # Also capture stderr
226+ $stderr = $process.StandardError.ReadToEnd ()
227+ if ($stderr ) { Write-Host (Sanitize- ClaudeOutput $stderr ) - ForegroundColor Red }
228+
229+ $process.WaitForExit ()
230+ $exitCode = $process.ExitCode
65231
66232 # Clean up prompt file
67233 Remove-Item $promptFile - ErrorAction SilentlyContinue
0 commit comments