From 4aa0c49372a3d5b9a94091a57e102405be217541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sun, 23 Nov 2025 19:46:47 +0100 Subject: [PATCH] feat: implement Phase 8 - Process Stdin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features added: - Created stdin API route for sending input to process stdin - Added useSendProcessStdin React Query hook - Created StdinInput modal component with: - Multi-line textarea for input - Ctrl+Enter keyboard shortcut - Optional newline append - Character count display - Info banner explaining stdin functionality - Added stdin button to ProcessCard (Terminal icon) - Button only enabled when process is running (state === 20) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../processes/[name]/stdin/route.ts | 36 +++++ components/process/ProcessCard.tsx | 18 ++- components/process/StdinInput.tsx | 132 ++++++++++++++++++ lib/hooks/useSupervisor.ts | 27 ++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 app/api/supervisor/processes/[name]/stdin/route.ts create mode 100644 components/process/StdinInput.tsx diff --git a/app/api/supervisor/processes/[name]/stdin/route.ts b/app/api/supervisor/processes/[name]/stdin/route.ts new file mode 100644 index 0000000..6901c35 --- /dev/null +++ b/app/api/supervisor/processes/[name]/stdin/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createSupervisorClient } from '@/lib/supervisor/client'; + +interface RouteParams { + params: Promise<{ name: string }>; +} + +// POST - Send input to process stdin +export async function POST(request: NextRequest, { params }: RouteParams) { + try { + const { name } = await params; + const body = await request.json(); + const { chars } = body; + + if (chars === undefined || chars === null) { + return NextResponse.json( + { error: 'Input characters are required' }, + { status: 400 } + ); + } + + const client = createSupervisorClient(); + const result = await client.sendProcessStdin(name, chars); + + return NextResponse.json({ + success: result, + message: `Input sent to ${name}`, + }); + } catch (error: any) { + console.error('Supervisor send stdin error:', error); + return NextResponse.json( + { error: error.message || 'Failed to send input to process' }, + { status: 500 } + ); + } +} diff --git a/components/process/ProcessCard.tsx b/components/process/ProcessCard.tsx index 25793f3..44d7dac 100644 --- a/components/process/ProcessCard.tsx +++ b/components/process/ProcessCard.tsx @@ -1,13 +1,14 @@ 'use client'; import { useState } from 'react'; -import { Play, Square, RotateCw, Activity, Zap } from 'lucide-react'; +import { Play, Square, RotateCw, Activity, Zap, Terminal } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ProcessInfo, ProcessStateCode, getProcessStateClass, formatUptime, canStartProcess, canStopProcess } from '@/lib/supervisor/types'; import { useStartProcess, useStopProcess, useRestartProcess } from '@/lib/hooks/useSupervisor'; import { SignalSender } from './SignalSender'; +import { StdinInput } from './StdinInput'; import { cn } from '@/lib/utils/cn'; interface ProcessCardProps { @@ -18,6 +19,7 @@ interface ProcessCardProps { export function ProcessCard({ process, isSelected = false, onSelectionChange }: ProcessCardProps) { const [showSignalModal, setShowSignalModal] = useState(false); + const [showStdinModal, setShowStdinModal] = useState(false); const startMutation = useStartProcess(); const stopMutation = useStopProcess(); const restartMutation = useRestartProcess(); @@ -143,6 +145,15 @@ export function ProcessCard({ process, isSelected = false, onSelectionChange }: > + {/* Description */} @@ -157,6 +168,11 @@ export function ProcessCard({ process, isSelected = false, onSelectionChange }: {showSignalModal && ( setShowSignalModal(false)} /> )} + + {/* Stdin Modal */} + {showStdinModal && ( + setShowStdinModal(false)} /> + )} ); } diff --git a/components/process/StdinInput.tsx b/components/process/StdinInput.tsx new file mode 100644 index 0000000..224706b --- /dev/null +++ b/components/process/StdinInput.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { useState } from 'react'; +import { useSendProcessStdin } from '@/lib/hooks/useSupervisor'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { X, Terminal, Info } from 'lucide-react'; + +interface StdinInputProps { + processName: string; + onClose: () => void; +} + +export function StdinInput({ processName, onClose }: StdinInputProps) { + const [input, setInput] = useState(''); + const [appendNewline, setAppendNewline] = useState(true); + + const stdinMutation = useSendProcessStdin(); + + const handleSend = () => { + if (!input) return; + + const chars = appendNewline ? input + '\n' : input; + + stdinMutation.mutate( + { name: processName, chars }, + { + onSuccess: () => { + setInput(''); + onClose(); + }, + } + ); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + // Allow Ctrl+Enter to send + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + handleSend(); + } + }; + + return ( +
+ + +
+
+ + + Send Input to Process + + + Send text to stdin of {processName} + +
+ +
+
+ + + {/* Info Banner */} +
+ +
+

+ This sends input directly to the process's standard input stream. + The process must be configured to read from stdin. +

+
+
+ + {/* Textarea */} +
+ +